Подтвердить что ты не робот

Почему 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.

4b9b3361

Ответ 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 для всех них.