Спецификация 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).
Читайте также:
- Спецификация Java 11: 12.4. Инициализация классов и интерфейсов
- Спецификация Java 11: 12.4.2. Подробная процедура инициализации
- Спецификация Java 11: 12.5. Создание экземпляров новых классов
Комментарии
Отправить комментарий