Хотя Java только недавно получила функциональные литералы (лямбды) как часть формального определения языка в Java8, основанное на замыкании состояние было тем, что Java была в состоянии сделать в течение некоторого времени, пока выполнялись определенные ограничения (или обходились ). Соответственно, мы разделим эту реализацию на две части: одна Java8 и выше, а другая до Java8.
Java8
Java8 предоставляет несколько различных интерфейсов для захвата экземпляров функций, ядром которых является интерфейс Function, принимающий параметр типа T и возвращающий значение типа R. ( Кстати, все это в пакете java.util.function. .) Функциональные литералы могут быть записаны в ссылки с помощью этих интерфейсов, поэтому создание только для функции версии Closed-based State будет выглядеть примерно так:
public class CloState
{
static class IntHolder {
public int value;
}
public static void main(String... args) {
Function<Integer, Integer> operation =
( (Supplier<Function<Integer,Integer>>) ( () -> {
IntHolder state = new IntHolder();
state.value = 100;
Function<Integer, Integer> func = (adj) -> {
state.value += adj;
return state.value;
};
return func;
})).get();
System.out.println(operation.apply(100));
}
}
IntHolder
Тип необходим , поскольку функция литералы Java, когда они «близко над» связанной переменными (например , «состоянием»), требуют, чтобы замкнутый над переменным быть фактически final
или, как в Java8, «эффективен final
» (то есть неизменны). Мы можем закрыть Integer
экземпляр, но поскольку типы оберток сами по себе неизменны, нам нужен новый «тип обертки», который учитывает изменчивость; в этом случае мы просто создаем простой заполнитель. (Что касается примечания, было бы неплохо иметь возможность обобщать этот тип «Holder» с помощью параметризованных типов, но, поскольку универсальные шаблоны Java просто стирают тип в ссылках на объект, при этом мы возвращаемся к исходной точке.)
Также обратите внимание, что хотя внутренняя функция — это a Function<Integer,Integer>
, внешняя оболочка — это Supplier<Function<Integer,Integer>>
, что кажется уместным, поскольку внешняя оболочка предоставляет экземпляр этого типа функции. (Прагматически говоря, не Supplier
принимает никаких параметров и возвращает тип, который отвечает всем требованиям здесь.)
Жаль, что Java не поддерживает прямой вызов возвращенного объекта Function, но это не конец света.
Pre-Java8
До Java8 подобные результаты могли быть получены с использованием анонимных экземпляров внутреннего класса интерфейса, и это на самом деле имеет то преимущество, что отсутствует чисто функционально-литеральный подход, а именно в том, что состояние можно «переносить» в анонимный экземпляр внутреннего класса, а не «закрытый» им:
public class CloState
{
public interface Function<T, R> {
public R apply(T t);
}
public static void main(String... args) {
Function<Integer,Integer> operation =
(new Function<Void, Function<Integer,Integer>>() {
public Function<Integer,Integer> apply(Void v) {
Function<Integer,Integer> func = new Function<Integer,Integer>() {
int state = 100;
public Integer apply(Integer adj) {
state += adj;
return state;
}
};
return func;
}
}).apply(null);
System.out.println(operation.apply(100));
}
}
(В педагогических целях я оставил имена интерфейсов такими же, как мы видим в Java8; поэтому, если этот код используется, убедитесь, что функция java.util.function не импортирована в этот модуль компиляции.)
Это не совсем соответствует всем желаемым последствиям состояния на основе замыкания, поскольку теперь состояние является полем объекта и, следовательно, может быть обнаружено с помощью отражения, но оно несколько упрощает картину и ясно демонстрирует взаимосвязь / близость закрытого состояния и стратегии.
Если состояние выходит за пределы внутренней реализации функции, то возникают те же «эффективно окончательные» проблемы из реализации Java8.
Объекты
Использование основанного на замыкании состояния в Java на самом деле проще, чем в некоторых других языках, из-за его способности возвращать анонимные реализации внутренних классов интерфейсов или классов. Рассмотрим следующий класс Product, специально обозначенный как реферат:
public class CloState
{
static public abstract class Product {
public abstract int Operation(int adjust);
public static Product New() {
return new Product() {
int state = 100;
public int Operation(int adjust) {
state += adjust;
return state;
}
};
}
}
public static void main(String... args) {
Product p = Product.New();
System.out.println(p.Operation(100));
}
}
(Продукт помечен как «статический» класс, потому что он полностью встроен в CloState для простоты компиляции примера. Обычно это будет класс верхнего уровня.)
Как и в реализации до Java8, мы решили сохранить состояние как поле в объекте, для простоты (чтобы избежать «эффективно финальной» проблемы), что снова делает его доступным через Reflection, но упрощает общую картину. Новый метод действует как функция конструктора ; выбор имен может вводить в заблуждение одних и интуитивно понятен другим, но это не важно для общей картины.
вариации
Можно использовать динамические прокси-серверы Java для предоставления основанного на замыкании состояния (или, по крайней мере, некоторой его формы), но обычно цель прокси-сервера заключается в том, чтобы «обернуть» ( стиль- декоратор ) другой объект, и здесь нет цели объект для вложения.