Java 8谓词-为什么不能将通配符泛型谓词连接在一起?

时间:2018-08-29 15:31:45

标签: java generics java-8

考虑以下代码:

public class Main {
    private static Predicate<? extends TestObject> predicate = testObject -> true;
    private static Predicate<? extends TestObject> predicate1 = testObject -> true;

    public static void main( String[] args ) {

         List<TestObject> objects = Lists.newArrayList( new TestObject(), new TestObject() );

         objects.stream().filter( predicate.or( predicate1 ) ).findFirst();
    }
}

它无法编译,并显示错误:

Error:(17, 48) java: incompatible types: java.util.function.Predicate<capture#1 of ? extends test.test.TestObject> cannot be converted to java.util.function.Predicate<? super capture#2 of ? extends test.test.TestObject>

似乎我们无法将这样的谓词与“或”或“与”之类的逻辑运算符结合在一起,但是Java为什么不能处理它们呢?

它使用Predicate<TestObject>之类的简单谓词进行编译,但不能使用Predicate<? super TestObject>Predicate<? extends TestObject>进行谓词。

4 个答案:

答案 0 :(得分:6)

您可能知道谓词是兼容的,但是编译器不兼容。

想象一下这个例子:

Predicate<? extends Collection<Object>> p1 = (Set<Object> s) -> s.isEmpty();
Predicate<? extends Collection<Object>> p2 = (List<Object> l) -> l.get(0) != null;

我们的开发人员可以看到,第一个谓词在技术上可以处理所有集合,而第二个谓词只能处理列表。但是,想象一下谓词在其他地方已初始化,或者在此期间将被更改。编译器无法确定谓词对象是针对哪种收集类型的。结果,您根本无法使用它们:

Set<Object> set = new HashSet<>();
List<Object> list = new ArrayList<>();
p1.test(set);
p2.test(list);
p1.test(list);
p2.test(set);

所有这些调用都不会编译,因为编译器无法说出p1p2之后的实际对象是否完全适合于这些类型的集合。这就是? extends Collection<>的含义:您知道它是一种特定的子类型,但是您不能告诉编译器确切的子类型。


用一个简单的例子来说明这一点:

Collection<Apple> appleBasket = ...;
appleBasket.add(new Apple());  // works
appleBasket.add(new Orange()); // does not work (obviously)

Collection<Fruit> mixedFruitBasket = ...;
mixedFruitBasket.add(new Apple());  // works
mixedFruitBasket.add(new Orange()); // works

// Now the tricky part
Collection<? extends Fruit> unknownButPureFruitBasket = ...;
unknownButPureFruitBasket.add(new Apple());  // does not work 
unknownButPureFruitBasket.add(new Orange()); // does not work

您不能将任何一种水果添加到您不知道类型的篮子中。实际上,它可能是一个可以容纳所有水果的篮子,但也可能是一个纯苹果篮子,橘色backet甚至是您甚至都不知道的香蕉篮子。

在您的IDE中尝试:

List<? extends String> l1 = new ArrayList<>();
List<? extends String> l2 = new ArrayList<>();
l1.addAll(l2);

Eclipse告诉我:

  

类型为List 1 -of?的方法addAll(Collection1-of?扩展String>)。扩展String>不适用于参数(List 2 -of?extended String>)

请注意不同的类型:addAll需要capture#1的集合,l2capture#2的集合。

答案 1 :(得分:4)

filter需要Predicate<? super TestObject>,而不是Predicate<? extends TestObject>

为什么super TestObject?由于此处的TestObject是输入参数,因此是消费者。根据{{​​3}}规则,应将其标记为super。

Predicate<? extends TestObject>Predicate<? super TestObject>完全不同。前者可以接受TestObjectTestObject的所有子类。后者可以接受TestObjectTestObject的所有超类。显然,它们是不同的且不兼容。

答案 2 :(得分:3)

这实际上与Java如何处理谓词无关,而只是泛型的问题。

简而言之,答案是predicatepredicate1不一定兼容。假设两个or对象中的Predicate<? super T>是相同的具体类型参数,则T的参数类型为Predicate。但是事实并非如此。

为说明问题,假设:

class TestObject2 extends TestObject {
    boolean isTrue() {
        return true;
    }
}
class TestObject3 extends TestObject {
    boolean isTrue3() {
        return true;
    }
}

并且(请注意,声明的类型没有改变,但是实际的lambda表达式却发生了改变):

private static Predicate<? extends TestObject> predicate = 
        (TestObject2 testObject) -> testObject.isTrue();
private static Predicate<? extends TestObject> predicate1 = 
        (TestObject3 testObject) -> testObject.isTrue3();

在此示例中,or将获得错误的值(predicate接受TestObject2对象,但是在这种情况下,可以使用TestObject3对象调用它)。

您认为predicate1将在此filter中工作吗?

Stream.of(new TestObject2(), new TestObject2()).filter(predicate.or(predicate1));

事实是编译器保证Predicate<? extends TestObject>Predicate<TestObject2>Predicate<TestObject3>都兼容(这是根据泛型法则)

答案 3 :(得分:1)

这又是PECS,因为您通过TestObject 消费一些Predicate,所以您需要? super TestObject