Why doesn't Java 8's Predicate extend F

2020-05-21 07:58发布

问题:

If I wrote the Predicate interface, I'd want to encode in the interface the fact that it's just a function that returns a primitive boolean, like this:

@FunctionalInterface
public interface Predicate<T> extends Function<T, Boolean> {

    boolean test(T t);

    @Override
    default Boolean apply(T t) {
        return Boolean.valueOf(test(t));
    }
}

I was wondering, is there a compelling reason Java 8 API designers chose to keep the Predicate completely separate from Function? Is there some evidence that they considered doing so and decided against it? I guess similar question goes for all the other 'special' functional interfaces like Consumer (could be Function<T, Void>), Supplier (Function<Void, T>) and primitive functions like IntFunction (Function<Integer, T>).

I haven't thought very deeply and thoroughly about all the ramifications of this, so I'm probably missing something.

EDIT: Some of the answers emphasize the semantic distinction between apply and test. I'm not saying I don't appreciate the distinction, and I agree that it's beneficial to have this distinction. What I don't understand is why a Predicate is nevertheless not also a Function in the same way as, e.g., a List is a Collection or Double is a Number, which is an Object.

If Predicate (and all the other special generic functional interfaces, such as Consumer, Supplier, IntUnaryOperator etc.) had this relation with Function, it would allow one to use it in place where Function parameter is expected (what comes to mind is composition with other functions, e.g. calling myFunction.compose(myPredicate) or to avoid writing several specialized functions in an API when such auto(un)boxing implementation as described above would be sufficient)

EDIT 2: Looking at openjdk lambda project I found that primitive functional interfaces used to extend Function up until this commit from Brian Goetz on 2012-12-19. I couldn't find specific reasons for the change on any of the lambda-dev or JSR experts group mailing lists around that time.

回答1:

The method in Predicate<T> returns boolean. The method in Function<T, Boolean> returns Boolean. They are not the same. Although there is autoboxing, Java methods don't use wrapper classes when primitives would do. Also, there are differences like Boolean can be null while boolean can't.

It's even more different in the case of Consumer<T>. The method in Consumer<T> has return type void, which means it can implicitly return or return using return;, but the method in Function<T, Void> must return using return null; explicitly.



回答2:

There is no need for such a suspicious inheritance hierarchy. These functional interfaces are inter-changable.

Function<A,Boolean> f1=…;
Predicate<A>        p1=…;

Predicate<A>        p2=f1::apply;
Function<A,Boolean> f2=p1::test;

This works in both directions. So why should there be an inheritance relationship advertising one specific direction?



回答3:

It is not a direct answer to your question, but for what would you be using it for?

Consider the following scenario: You want to map true/false to the list of values for which it is true respectively false.

With your code you could use:

@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));

The following however tells me that they have definately thought about something similar, as they offer the partitioning method:

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));

Some reasons I can think about are:

  • It would not be intuitive to have a apply() method available in a Predicate where you only expect a test() method.
  • Functional interfaces are designed to provide only one type of basic functionality (excluding chaining or logical operations), in your situation CustomPredicate contains two types of functionality. It would only add to confusion.


回答4:

In my opinion Function<T, R> is just the definition of a generic function. If all FunctionalInterfaces would implement Function, the only abstract method would have to be named apply(). In the context of a concrete FunctionalInterface like FilterFile, the abstract method boolean accept(File pathname) is a much better name then Boolean apply(File).

The annotation @FunctionalInterface already marks an interface as being intended to be usable as a FunctionalInterface. There is no benefit having them all implement a base interface other then processing them in a generic way. I do not see when you would not care about the semantics of a FunctionalInterface in advance to make them available to call apply for them all.