参考类型中的有界通配符

时间:2019-10-08 22:03:22

标签: java generics bounded-wildcard

我很难理解以下有关两个Predicate对象的代码。第一个使用下界通配符,第二个使用上界通配符。

Predicate<? super String> p1 = s -> s.startsWith("a"); // why can I call startsWith()?
Predicate<? extends String> p2 = s -> s.startsWith("a");

p1.test("a"); // works
p2.test("a"); // doesn't work (why?)

我对p1不了解,为什么可以从类String调用方法,例如startsWith()?为什么我只能将String个对象传递到p1.test()中,我希望能够同时为NumberObject对象调用它。

按照p1的行为,我以为p2可以,但是事实并非如此。我什至无法将String对象传递到p2.test()中。这对我来说没有任何意义,因为我们希望有一个对象继承自String(包括String)。

我认为这可能与以下事实有关:我们指定引用类型而不是对象本身的类型。但是该对象使用什么类型?

2 个答案:

答案 0 :(得分:5)

即使startsWith的输入下限为p1,也可以为p1调用? super String,因为推断类型参数为{{ 1}}。 λ表达式String推断为s -> s.startsWith("a");,可以合法地将其分配给类型为Predicate<String>的变量。

它将编译:

Predicate<? super String>

这不是:

Predicate<String> ps = s -> s.startsWith("a");
Predicate<? super String> p1 = ps;

JLS参考位于Section 15.27.3“ Lambda表达式的类型”中。

  

如果T是通配符参数化的功能接口类型,并且lambda表达式是隐式键入的,则基本目标类型是T的非通配符参数化(第9.9节)。

在这里,基本目标类型是lambda表达式的类型,而// no "startsWith" on Object Predicate<? super String> p1 = (Object s) -> s.startsWith("a"); 是目标类型,这是您的下界变量数据类型。这样,编译器就可以将T分配为基本目标类型,该目标类型将成为lambda表达式的类型。

还请注意,您无法将超类对象传递给Predicate<String>,因为您可以(并且已经)将p1.test分配给Predicate<String>,这需要{{1 }}。

p1

关于为什么不能将String传递给p1.test(new Object()); // Error: can't pass something higher than String 的原因,当您使用String这样的上限通配符时,这意味着type参数可以是任何是p2.test或子类型。 (编译器会忽略? extends String在此处是String,并且String不会有任何子类。)可以为谓词final分配一个String,假设p2Predicate<SillyString>的子类。但是您不能将SillyString传递给可能期望String的方法。

答案 1 :(得分:2)

当您拥有Predicate<? super String>时,这意味着Predicate的实际实现可以保证能够处理String的实例。实现是否将String视为StringCharSequence或什至Object无关紧要,只要它可以处理类型即可。这在使用接口的API中提供了灵活性。例如:

void addFilter(Predicate<? super String> filter) { ... }

您可以使用addFilterPredicate<CharSequence>实例调用Predicate<Object>,这与API无关。例如:

addFilter((CharSequence cs) -> cs.length() % 2 == 0);

但是,当API实际调用filter时,它有 String的实例传递给test。如果允许该API调用filter.test(new Object()),则传递给上述Predicate<CharSequence>的{​​{1}}将失败,并返回addFilter

当您拥有ClassCastException时(由于Predicate<? extends CharSequence>是最终课程,因此请使用CharSequence),您将无法调用String,因为该实现可能无法处理任何类型test中的。例如:

CharSequence

将抛出Predicate<? extends CharSequence> predicate = (String s) -> s.startsWith("..."); predicate.test(new StringBuilder()); ,因为ClassCastException的“实型”实际上是predicate,而不是Predicate<String>。因此,编译器拒绝对Predicate<StringBuilder>的调用,因为它不是类型安全的。

我还建议您阅读What is PECS (Producer Extends Consumer Super)?