Почему Java 8 Predicate <T> не расширяет функцию <T, Boolean>
Если бы я написал интерфейс Predicate
, я бы хотел закодировать в интерфейсе тот факт, что это просто функция, которая возвращает примитивное boolean
, например:
@FunctionalInterface
public interface Predicate<T> extends Function<T, Boolean> {
boolean test(T t);
@Override
default Boolean apply(T t) {
return Boolean.valueOf(test(t));
}
}
Мне было интересно, есть ли веская причина, по которой дизайнеры Java 8 API решили полностью отделить Predicate
от Function
? Есть ли какие-то доказательства того, что они подумали об этом и решили против этого? Я полагаю, что аналогичный вопрос касается всех других "специальных" функциональных интерфейсов, таких как Consumer
(может быть Function<T, Void>
), Supplier
(Function<Void, T>
) и примитивные функции, такие как IntFunction
(Function<Integer, T>
).
Я не очень глубоко и тщательно обдумывал все последствия этого, поэтому я, наверное, что-то упустил.
РЕДАКТИРОВАТЬ: Некоторые ответы подчеркивают семантическое различие между применением и тестированием. Я не говорю, что не ценю это различие, и я согласен, что это различие полезно. Я не понимаю, почему Predicate
, тем не менее, также не является Function in
же, как, например, List
- это Collection
или Double
- это Number
, которое является Object
.
Если бы Predicate
(и все другие специальные универсальные функциональные интерфейсы, такие как Consumer
, Supplier
, IntUnaryOperator
и т.д.) Имели такое отношение с Function
, это позволило бы использовать его там, где ожидается параметр Function
(что приходит на ум, это композиция с другие функции, например, вызов myFunction.compose(myPredicate)
или чтобы избежать написания нескольких специализированных функций в API, когда такой реализации автоматического (не) бокса, как описано выше, будет достаточно)
РЕДАКТИРОВАТЬ 2: Глядя на лямбда-проект openjdk, я обнаружил, что примитивные функциональные интерфейсы использовались для расширения Function
вплоть до этой фиксации от Brian Goetz 2012-12-19. Я не смог найти конкретные причины для изменений ни в одном из списков рассылки lambda-dev или JSR.
Ответы
Ответ 1
Метод в Predicate<T>
возвращает boolean
. Метод в Function<T, Boolean>
возвращает boolean
. Они не одинаковы. Хотя есть автобоксинг, Java-методы не используют классы-оболочки, когда будут выполняться примитивы. Кроме того, существуют такие различия, как boolean
может быть null
, а boolean
не может.
Это еще больше отличается в случае Consumer<T>
. Метод в Consumer<T>
имеет тип возврата void
, что означает, что он может неявно возвращать или возвращать с помощью return;
, но метод из Function<T, Void>
должен явно использовать return null;
.
Ответ 2
Нет необходимости в такой подозрительной иерархии наследования. Эти функциональные интерфейсы взаимозаменяемы.
Function<A,Boolean> f1=…;
Predicate<A> p1=…;
Predicate<A> p2=f1::apply;
Function<A,Boolean> f2=p1::test;
Это работает в обоих направлениях. Итак, почему должны существовать отношения наследования, рекламирующие одно конкретное направление?
Ответ 3
Это не прямой ответ на ваш вопрос, но для чего вы его используете?
Рассмотрим следующий сценарий: вы хотите сопоставить true/false со списком значений, для которых оно истинно, соответственно false.
С помощью вашего кода вы можете использовать:
@FunctionalInterface
interface CustomPredicate<T> extends Function<T, Boolean> {
boolean test(T value);
@Override
default Boolean apply(T t) {
return test(t);
}
}
List<String> stringList = new ArrayList<>();
stringList.add("a");
stringList.add("hg");
stringList.add("dsl");
stringList.add("sldi");
stringList.add("ilsdo");
stringList.add("jlieio");
CustomPredicate<String> customPredicate = str -> (str.length() >= 3);
Map<Boolean, List<String>> mapping = stringList.stream()
.collect(Collectors.groupingBy(customPredicate));
Следующее, однако, говорит мне, что они определенно подумали о чем-то подобном, поскольку они предлагают метод разбиения:
List<String> stringList = new ArrayList<>();
stringList.add("a");
stringList.add("hg");
stringList.add("dsl");
stringList.add("sldi");
stringList.add("ilsdo");
stringList.add("jlieio");
Predicate<String> predicate = str -> (str.length() >= 3);
Map<Boolean, List<String>> mapping = stringList.stream()
.collect(Collectors.partitioningBy(predicate));
Некоторые причины, о которых я могу думать, следующие:
- Было бы неинтересно иметь метод
apply()
, доступный в Predicate
, где вы ожидаете только метода test()
.
- Функциональные интерфейсы предназначены для обеспечения только базовых функций one (исключая цепочки или логические операции), в вашей ситуации
CustomPredicate
содержит два типа функциональных возможностей. Это лишь добавило бы путаницы.
Ответ 4
По-моему Function<T, R>
- это просто определение общей функции. Если бы все FunctionalInterfaces
выполнили Function
, единственный абстрактный метод должен был бы называться apply()
. В контексте конкретного FunctionalInterface
, подобного FilterFile
, абстрактный метод boolean accept(File pathname)
является гораздо лучшим именем, чем Boolean apply(File)
.
Аннотация @FunctionalInterface
уже отмечает интерфейс как предназначенный для использования в качестве FunctionalInterface
. Нет никакой выгоды, поскольку все они реализуют базовый интерфейс, а затем обрабатывают их общим способом. Я не вижу, когда вы не заботитесь о семантике FunctionalInterface
заранее, чтобы сделать их доступными для вызова apply
для всех них.