Спецификация Java 11: 9.8. Функциональные интерфейсы

Функциональный интерфейс - это интерфейс, который имеет только один абстрактный метод (помимо методов Object) и, таким образом, представляет собой контракт с одной функцией. Этот "единственный" метод может принимать форму нескольких абстрактных методов с замещающими эквивалентными сигнатурами, унаследованными от суперинтерфейсов; в этом случае унаследованные методы логически представляют один метод.

Для интерфейса I пусть M будет набором абстрактных методов, которые являются членами I, не имеющими такой же сигнатуры, как любой public метод экземпляра класса Object (§4.3.2). Тогда I является функциональным интерфейсом, если существует метод m в M, для которого выполняются оба следующих условия:

  • Сигнатура m - это субсигнатура (§8.4.2) каждой сигнатуры метода в M.
  • m заменяется возвращаемым типом (§8.4.5) для каждого метода в M.

В дополнение к обычному процессу создания экземпляра интерфейса путем объявления и создания экземпляра класса (§15.9), экземпляры функциональных интерфейсов могут быть созданы с помощью выражений ссылки на методы и лямбда-выражений (§15.13, §15.27).

Определение функционального интерфейса исключает методы в интерфейсе, которые также являются public методами в Object. Это сделано для того, чтобы разрешить функциональную обработку интерфейса, такого как java.util.Comparator<T>, который объявляет несколько абстрактных методов, из которых только один действительно "новый" - int compare(T, T). Другой - boolean equals(Object) - является явным объявлением абстрактного метода, который в противном случае был бы неявно объявлен в интерфейсе (§9.2) и автоматически реализован каждым классом, реализующим интерфейс.

Обратите внимание, что если непубличные методы Object, такие как clone(), явно объявлены в интерфейсе как public, они не реализуются автоматически каждым классом, реализующим интерфейс. Реализация, унаследованная от Object, защищена (protected), пока метод интерфейса является public, поэтому единственный способ реализовать интерфейс - это переопределить классом непубличный метод Object, public методом.

Примеры функциональных интерфейсов

Простой пример функционального интерфейса:

interface Runnable {
    void run();
}

Следующий интерфейс не работает, потому что он не декларирует ничего, что еще не является членом Object:

interface NonFunc {
    boolean equals(Object obj);
}

Однако его подинтерфейс может работать, объявляя абстрактный метод, не являющийся членом Object:

interface Func extends NonFunc {
    int compare(String o1, String o2);
}

Точно так же хорошо известный интерфейс java.util.Comparator<T> является функциональным, потому что он имеет один абстрактный не-Object метод:

interface Comparator<T> {
    boolean equals(Object obj);
    int compare(T o1, T o2);
}

Следующий интерфейс не работает, потому что, хотя он объявляет только один абстрактный метод, который не является членом Object, он объявляет два абстрактных метода, которые не являются public членами Object:

interface Foo {
    int m();
    Object clone();
}

Пример. Функциональные интерфейсы и стирание типа

В следующей иерархии интерфейсов Z является функциональным интерфейсом, потому что, хотя он наследует два абстрактных метода, которые не являются членами Object, они имеют одинаковую сигнатуру, поэтому унаследованные методы логически представляют один метод:

interface X { int m(Iterable<String> arg); }
interface Y { int m(Iterable<String> arg); }
interface Z extends X, Y {}

Точно так же Z является функциональным интерфейсом в следующей иерархии интерфейсов, потому что Y.m является подсигнатурой X.m и может заменять тип возвращаемого значения на X.m:

interface X { Iterable m(Iterable<String> arg); }
interface Y { Iterable<String> m(Iterable arg); }
interface Z extends X, Y {}

Определение функционального интерфейса учитывает тот факт, что интерфейс не может иметь двух членов, которые не являются субсигнатурами друг друга, но имеют одинаковое стирание типа (§9.4.1.2). Таким образом, в следующих трех иерархиях интерфейсов, где Z вызывает ошибку времени компиляции, Z не является функциональным интерфейсом: (потому что ни один из его абстрактных элементов не является субсигнатурами всех других абстрактных элементов)

interface X { int m(Iterable<String> arg); }
interface Y { int m(Iterable<Integer> arg); }
interface Z extends X, Y {}

interface X { int m(Iterable<String> arg, Class c); }
interface Y { int m(Iterable arg, Class<?> c); }
interface Z extends X, Y {}

interface X<T> { void m(T arg); }
interface Y<T> { void m(T arg); }
interface Z<A, B> extends X<A>, Y<B> {}

Точно так же определение "функционального интерфейса" учитывает тот факт, что интерфейс может иметь методы с заменяющими эквивалентными сигнатурами, только если один из них является заменяемым по типу возвращаемого значения для всех остальных. Таким образом, в следующей иерархии интерфейсов, где Z вызывает ошибку времени компиляции, Z не является функциональным интерфейсом: (поскольку ни один из его абстрактных элементов не может быть заменен типом возвращаемого значения для всех других абстрактных элементов)

interface X { long m(); }
interface Y { int  m(); }
interface Z extends X, Y {}

В следующем примере объявления Foo<T,N> и Bar допустимы: в каждом из них методы, называемые m, не являются субсигнатурами друг друга, но имеют разные стирания. Тем не менее, тот факт, что методы в каждом из них не являются субсигнатурами, означает, что Foo<T,N> и Bar не являются функциональными интерфейсами. Однако Baz является функциональным интерфейсом, потому что методы, которые он наследует от Foo<Integer,Integer>, имеют одинаковую сигнатуру и поэтому логически представляют один метод.

interface Foo<T, N extends Number> {
    void m(T arg);
    void m(N arg);
}
interface Bar extends Foo<String, Integer> {}
interface Baz extends Foo<Integer, Integer> {}

Наконец, следующие примеры демонстрируют те же правила, что и выше, но с общими методами:

interface Exec { <T> T execute(Action<T> a); }
  // Функциональный

interface X { <T> T execute(Action<T> a); }
interface Y { <S> S execute(Action<S> a); }
interface Exec extends X, Y {}
  // Функциональный: сигнатуры логически "одинаковы"

interface X { <T>   T execute(Action<T> a); }
interface Y { <S,T> S execute(Action<S> a); }
interface Exec extends X, Y {}
  // Ошибка: разные сигнатуры, одно и то же стирание

Пример. Общие функциональные интерфейсы

Функциональные интерфейсы могут быть общими, например java.util.function.Predicate<T>. Такой функциональный интерфейс может быть параметризован таким образом, чтобы создавать отдельные абстрактные методы, то есть несколько методов, которые нельзя валидно переопределить с помощью одного объявления. Например:

interface I    { Object m(Class c); }
interface J<S> { S m(Class<?> c); }
interface K<T> { T m(Class<?> c); }
interface Functional<S,T> extends I, J<S>, K<T> {}

Functional<S,T> - это функциональный интерфейс (I.m - это тип возвращаемого значения, заменяемый на J.m и K.m), но функциональный тип интерфейса Functional<String,Integer> явно не может быть реализован с помощью одного метода. Однако возможны другие параметризации Functional<S,T>, которые являются типами функционального интерфейса.

Объявление функционального интерфейса позволяет использовать в программе тип функционального интерфейса. Существует четыре типа функционального интерфейса:

  • Тип неуниверсального (§6.1) функционального интерфейса
  • Параметризованный тип, который является параметризацией (§4.5) универсального функционального интерфейса.
  • Необработанный тип (§4.8) универсального функционального интерфейса
  • Тип пересечения (§4.9), который индуцирует условный функциональный интерфейс

В особых случаях полезно рассматривать тип пересечения как функциональный тип интерфейса. Обычно это будет выглядеть как пересечение функционального типа интерфейса с одним или несколькими типами интерфейса маркера, такими как Runnable & java.io.Serializable. Такое пересечение можно использовать в приведениях (§15.16), которые заставляют лямбда-выражение соответствовать определенному типу. Если один из типов интерфейса на пересечении - java.io.Serializable, запускается специальная поддержка сериализации во время выполнения (§15.27.4).


Читайте также:


Комментарии

Популярные сообщения из этого блога

Методы класса Object в Java

Как получить текущий timestamp в Java

Основные опции JVM для повышения производительности и отладки