Может кто-нибудь объяснить, что такое функтор, и предоставить простой пример?
Помогите с пониманием функционального объекта или функтора в Java
Ответ 1
Объект функции - это просто. То, что является одновременно объектом и функцией.
Помимо этого: вызов функционального объекта "функтор" является серьезным злоупотреблением термином: различные "функторы" - это центральное понятие в математике, которое имеет непосредственную роль в информатике (см. "Haskell Functors" "). Этот термин также используется несколько иначе в ML, поэтому, если вы не реализуете одну из этих концепций на Java (что вы можете!), Пожалуйста, прекратите использовать эту терминологию. Это делает простые вещи сложными.
Вернуться к ответу: Java не имеет "функций первого класса", то есть вы не можете передавать функцию в качестве аргумента функции. Это истинно на нескольких уровнях, синтаксически, в представлении байтового кода и в том, что система типа не имеет "конструктора функций"
Другими словами, вы не можете написать что-то вроде этого:
public static void tentimes(Function f){
for(int i = 0; i < 10; i++)
f();
}
...
public static void main{
...
tentimes(System.out.println("hello"));
...
}
Это действительно раздражает, поскольку мы хотим иметь возможность делать такие вещи, как библиотеки графического интерфейса пользователя, где вы можете связать функцию "обратного вызова" с нажатием кнопки.
Итак, что мы делаем?
Ну, общее решение (обсуждаемое другими плакатами) заключается в том, чтобы определить интерфейс с помощью одного метода, который мы можем назвать. Например, Java использует интерфейс под названием Runnable
для таких вещей все время, это выглядит так:
public interface Runnable{
public void run();
}
теперь мы можем переписать мой пример сверху:
public static void tentimes(Runnable r){
for(int i = 0; i < 10; i++)
r.run();
}
...
public class PrintHello implements Runnable{
public void run{
System.out.println("hello")
}
}
---
public static void main{
...
tentimes(new PrintHello());
...
}
Очевидно, этот пример надуман. Мы могли бы сделать этот код немного приятнее, используя анонимные внутренние классы, но это дает общую идею.
Здесь это разрывается: Runnable
используется только для функций, которые не принимают никаких аргументов, и не возвращают ничего полезного, поэтому вы должны определить новый интерфейс для каждого задания. Например, интерфейс Comparator
в ответе Мохаммада Фейсала. Предоставление более общего подхода, а также синтаксиса, является главной целью для Java 8 (следующая версия Java) и в значительной степени было включено для включения в Java 7. Это называется "лямбдой" после механизма абстракции функции в исчислении лямбда. Lambda Calculus - это (возможно) самый старый язык программирования и теоретическая основа значительной части компьютерных наук. Когда Алонсо Церковь (один из главных основателей информатики) придумала это, он использовал греческую букву лямбда для функций, отсюда и название.
Другие языки, включая функциональный язык (Lisp, ML, Haskell, Erlang и т.д.), большинство основных динамических языков (Python, Ruby, JavaScript и т.д.) и другие языки приложений (С#, Scala, Go, D и т.д.) Поддерживают некоторую форму "Lambda Literal". Даже С++ имеет их сейчас (начиная с С++ 11), хотя в этом случае они несколько сложнее, потому что С++ не имеет автоматического управления памятью и не сохранит ваш стек для вас.
Ответ 2
Функтор - это объект, который выполняет функцию.
У Java их нет, потому что функции не являются первоклассными объектами в Java.
Но вы можете аппроксимировать их интерфейсами, что-то вроде объекта Command:
public interface Command {
void execute(Object [] parameters);
}
Обновлено 18 марта 2017 года:
Поскольку я впервые написал этот JDK 8, он добавил lambdas. Пакет java.util.function имеет несколько полезных интерфейсов.
Ответ 3
Из всех проверок времени, в Функторы, на Java 8 Lambdas (вроде)
Проблема
Возьмите этот примерный класс, который адаптирует к Применяется в Писатель:
import java.io.Closeable;
import java.io.Flushable;
import java.io.IOException;
import java.io.Writer;
import java.util.Objects;
/**
<P>{@code java WriterForAppendableWChecksInFunc}</P>
**/
public class WriterForAppendableWChecksInFunc extends Writer {
private final Appendable apbl;
public WriterForAppendableWChecksInFunc(Appendable apbl) {
if(apbl == null) {
throw new NullPointerException("apbl");
}
this.apbl = apbl;
}
//Required functions, but not relevant to this post...START
public void write(char[] a_c, int i_ndexStart, int i_ndexEndX) throws IOException {
public Writer append(char c_c) throws IOException {
public Writer append(CharSequence text) throws IOException {
public Writer append(CharSequence text, int i_ndexStart, int i_ndexEndX) throws IOException {
//Required functions, but not relevant to this post...END
public void flush() throws IOException {
if(apbl instanceof Flushable) {
((Flushable)apbl).flush();
}
}
public void close() throws IOException {
flush();
if(apbl instanceof Closeable) {
((Closeable)apbl).close();
}
}
}
Не все Appendable
являются Flushable
или Closeable
, но те, которые есть, также должны быть закрыты и сброшены. Поэтому фактический тип объекта Appendable
должен быть проверен при каждом вызове flush()
и close()
, и, когда это действительно так, type, он вызывается и вызывается функция.
По общему признанию, это не самый лучший пример, поскольку close()
вызывается только один раз для каждого экземпляра, а flush()
необязательно часто называли это. Кроме того, instanceof
, хотя и отражает, не так уж плохо, учитывая этот конкретный пример использования. Тем не менее, концепция , которая должна что-то проверять каждый раз, когда вам нужно сделать что-то еще, является реальной и избегает этих "каждый раз"; проверки, когда это действительно имеет значение, дает значительные преимущества.
Переместить все "сверхмощные" проверяет конструктор
Итак, с чего вы начинаете? Как избежать этих проверок без ущерба для вашего кода?
В нашем примере самым простым шагом является переместить все теги instanceof
в конструктор.
public class WriterForAppendableWChecksInCnstr extends Writer {
private final Appendable apbl;
private final boolean isFlshbl;
private final boolean isClsbl;
public WriterForAppendableWChecksInCnstr(Appendable apbl) {
if(apbl == null) {
throw new NullPointerException("apbl");
}
this.apbl = apbl;
isFlshbl = (apbl instanceof Flushable);
isClsbl = (apbl instanceof Closeable);
}
//write and append functions go here...
public void flush() throws IOException {
if(isFlshbl) {
((Flushable)apbl).flush();
}
}
public void close() throws IOException {
flush();
if(isClsbl) {
((Closeable)apbl).close();
}
}
}
Теперь, когда эти "тяжелые" проверки выполняются только один раз, необходимо выполнить только булевские проверки с помощью flush()
и close()
. Хотя, безусловно, это улучшение, как можно полностью исключить эти проверки функций?
Если бы вы могли каким-то образом определить функцию , которая может быть сохранена классом, а затем используется с помощью flush()
и закрыть()
...
public class WriterForAppendableWChecksInCnstr extends Writer {
private final Appendable apbl;
private final FlushableFunction flshblFunc; //If only!
private final CloseableFunction clsblFunc; //If only!
public WriterForAppendableWChecksInCnstr(Appendable apbl) {
if(apbl == null) {
throw new NullPointerException("apbl");
}
this.apbl = apbl;
if(apbl instanceof Flushable) {
flshblFunc = //The flushable function
} else {
flshblFunc = //A do-nothing function
}
if(apbl instanceof Closeable) {
clsblFunc = //The closeable function
} else {
clsblFunc = //A do-nothing function
}
}
//write and append functions go here...
public void flush() throws IOException {
flshblFunc(); //If only!
}
public void close() throws IOException {
flush();
clsblFunc(); //If only!
}
}
Но передача функций невозможна... по крайней мере, пока Java 8 Lambdas. Итак, как вы это делаете в до 8 версиях Java?
функторы
С Functor. Функтор - это, в основном, Лямбда, но тот, который завернут в объект. Хотя функции не могут передаваться в другие функции в качестве параметров, объекты могут. По сути, Функторы и Лямбда - это способы передать функции.
Итак, как мы можем реализовать Functor в нашем адаптере-адаптере? Мы знаем, что close()
и flush()
полезны только с объектами Closeable
и Flushable
. И что некоторые Appendable
являются Flushable
, некоторые Closeable
, некоторые из них, некоторые из них.
Поэтому мы можем сохранить объект Flushable
и Closeable
в верхней части класса:
public class WriterForAppendable extends Writer {
private final Appendable apbl;
private final Flushable flshbl;
private final Closeable clsbl;
public WriterForAppendable(Appendable apbl) {
if(apbl == null) {
throw new NullPointerException("apbl");
}
//Avoids instanceof at every call to flush() and close()
if(apbl instanceof Flushable) {
flshbl = apbl; //This Appendable *is* a Flushable
} else {
flshbl = //?????? //But what goes here????
}
if(apbl instanceof Closeable) {
clsbl = apbl; //This Appendable *is* a Closeable
} else {
clsbl = //?????? //And here????
}
this.apbl = apbl;
}
//write and append functions go here...
public void flush() throws IOException {
flshbl.flush();
}
public void close() throws IOException {
flush();
clsbl.close();
}
}
"каждый раз" проверки теперь устранены. Но когда Appendable
является not a Flushable
или not a Closeable
, что должно храниться?
Не делайте ничего Функторы
Ничего не делайте...
class CloseableDoesNothing implements Closeable {
public void close() throws IOException {
}
}
class FlushableDoesNothing implements Flushable {
public void flush() throws IOException {
}
}
... который может быть реализован как анонимный внутренний класс:
public WriterForAppendable(Appendable apbl) {
if(apbl == null) {
throw new NullPointerException("apbl");
}
this.apbl = apbl;
//Avoids instanceof at every call to flush() and close()
flshbl = ((apbl instanceof Flushable)
? (Flushable)apbl
: new Flushable() {
public void flush() throws IOException {
}
});
clsbl = ((apbl instanceof Closeable)
? (Closeable)apbl
: new Closeable() {
public void close() throws IOException {
}
});
}
//the rest of the class goes here...
}
Чтобы быть наиболее эффективными, эти do-nothing функторы должны быть реализованы как статические конечные объекты. И с этим, вот окончательная версия нашего класса:
package xbn.z.xmpl.lang.functor;
import java.io.Closeable;
import java.io.Flushable;
import java.io.IOException;
import java.io.Writer;
public class WriterForAppendable extends Writer {
private final Appendable apbl;
private final Flushable flshbl;
private final Closeable clsbl;
//Do-nothing functors
private static final Flushable FLUSHABLE_DO_NOTHING = new Flushable() {
public void flush() throws IOException {
}
};
private static final Closeable CLOSEABLE_DO_NOTHING = new Closeable() {
public void close() throws IOException {
}
};
public WriterForAppendable(Appendable apbl) {
if(apbl == null) {
throw new NullPointerException("apbl");
}
this.apbl = apbl;
//Avoids instanceof at every call to flush() and close()
flshbl = ((apbl instanceof Flushable)
? (Flushable)apbl
: FLUSHABLE_DO_NOTHING);
clsbl = ((apbl instanceof Closeable)
? (Closeable)apbl
: CLOSEABLE_DO_NOTHING);
}
public void write(char[] a_c, int i_ndexStart, int i_ndexEndX) throws IOException {
apbl.append(String.valueOf(a_c), i_ndexStart, i_ndexEndX);
}
public Writer append(char c_c) throws IOException {
apbl.append(c_c);
return this;
}
public Writer append(CharSequence c_q) throws IOException {
apbl.append(c_q);
return this;
}
public Writer append(CharSequence c_q, int i_ndexStart, int i_ndexEndX) throws IOException {
apbl.append(c_q, i_ndexStart, i_ndexEndX);
return this;
}
public void flush() throws IOException {
flshbl.flush();
}
public void close() throws IOException {
flush();
clsbl.close();
}
}
Этот конкретный пример исходит от этого вопроса на /fooobar.com/.... Полную рабочую и полностью документированную версию этого примера (включая функцию тестирования) можно найти в нижней части этого вопроса-сообщения (над ответом).
Реализация функторов с перечислением
Оставив пример Writer
- Appendable
, рассмотрим другой способ реализации функторов: с Enum.
В качестве примера это перечисление имеет функцию move
для каждого основного направления:
public enum CardinalDirection {
NORTH(new MoveNorth()),
SOUTH(new MoveSouth()),
EAST(new MoveEast()),
WEST(new MoveWest());
private final MoveInDirection dirFunc;
CardinalDirection(MoveInDirection dirFunc) {
if(dirFunc == null) {
throw new NullPointerException("dirFunc");
}
this.dirFunc = dirFunc;
}
public void move(int steps) {
dirFunc.move(steps);
}
}
Для его конструктора требуется MoveInDirection
объект (который является интерфейсом, но может также быть абстрактным классом):
интерфейс MoveInDirection { void move (int steps); }
Существует, естественно, четыре конкретных реализации этого интерфейса, по одному на направление. Вот тривиальная реализация для севера:
класс MoveNorth реализует MoveInDirection { public void move (int steps) { System.out.println( "Перемещено" + шаги + "шаги на север" ); } }
Использование этого Functor выполняется с помощью этого простого вызова:
CardinalDirection.WEST.move(3);
Что в нашем примере выводит это на консоль:
Перемещено на 3 шага на запад.
И вот полный рабочий пример:
/**
<P>Demonstrates a Functor implemented as an Enum.</P>
<P>{@code java EnumFunctorXmpl}</P>
**/
public class EnumFunctorXmpl {
public static final void main(String[] ignored) {
CardinalDirection.WEST.move(3);
CardinalDirection.NORTH.move(2);
CardinalDirection.EAST.move(15);
}
}
enum CardinalDirection {
NORTH(new MoveNorth()),
SOUTH(new MoveSouth()),
EAST(new MoveEast()),
WEST(new MoveWest());
private final MoveInDirection dirFunc;
CardinalDirection(MoveInDirection dirFunc) {
if(dirFunc == null) {
throw new NullPointerException("dirFunc");
}
this.dirFunc = dirFunc;
}
public void move(int steps) {
dirFunc.move(steps);
}
}
interface MoveInDirection {
void move(int steps);
}
class MoveNorth implements MoveInDirection {
public void move(int steps) {
System.out.println("Moved " + steps + " steps north.");
}
}
class MoveSouth implements MoveInDirection {
public void move(int steps) {
System.out.println("Moved " + steps + " steps south.");
}
}
class MoveEast implements MoveInDirection {
public void move(int steps) {
System.out.println("Moved " + steps + " steps east.");
}
}
class MoveWest implements MoveInDirection {
public void move(int steps) {
System.out.println("Moved " + steps + " steps west.");
}
}
Вывод:
[C:\java_code] java EnumFunctorXmpl Перемещено на 3 шага на запад. Перемещено на 2 ступени на север. Перемещено на 15 шагов на восток.
Я еще не начал работу с Java 8, поэтому я еще не могу написать раздел Lambdas:)
Ответ 4
Возьмем концепцию применения функций
f.apply(x)
Обратные
x.map(f)
Вызвать x
функтор
interface Functor<T> {
Functor<R> map(Function<T, R> f);
}