Статьи

Состояние на основе замыкания: Java

Хотя 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 для предоставления основанного на замыкании состояния (или, по крайней мере, некоторой его формы), но обычно цель прокси-сервера заключается в том, чтобы «обернуть» ( стиль- декоратор ) другой объект, и здесь нет цели объект для вложения.