引用类型中的有界通配符
Bounded wildcards in reference types
我无法理解以下有关两个 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
我不明白的是,为什么可以从 class String
调用方法,例如startsWith()
?为什么我只能将 String
对象传递给 p1.test()
,我希望能够为 Number
和 Object
对象调用它。
作为 p1
的行为,我认为 p2
会,但事实并非如此。我什至无法将 String
对象传递给 p2.test()
。这对我来说没有意义,因为我们期望一个对象继承自 String
(包括 String
)。
我想这可能与我们指定引用类型而不是对象本身的类型有关。但是对象使用什么类型?
当您有 Predicate<? super String>
时,这意味着 Predicate
的实际实现保证能够处理 String
的实例。实现是否将 String
视为 String
或 CharSequence
甚至 Object
都无关紧要,只要它可以处理类型即可。这为 API 使用 接口提供了灵活性。例如:
void addFilter(Predicate<? super String> filter) { ... }
您可以使用 Predicate<CharSequence>
或 Predicate<Object>
实例调用 addFilter
,这对 API 无关紧要。例如:
addFilter((CharSequence cs) -> cs.length() % 2 == 0);
但是,当 API 实际调用 filter
时,它 必须 将 String
的实例传递给 test
.如果允许 API 调用 filter.test(new Object())
,则上面传递给 addFilter
的 Predicate<CharSequence>
将失败并显示 ClassCastException
.
当你有 Predicate<? extends CharSequence>
(使用 CharSequence
因为 String
是最终的 class)你不能调用 test
因为实现可能不会能够处理任何类型的CharSequence
。例如:
Predicate<? extends CharSequence> predicate = (String s) -> s.startsWith("...");
predicate.test(new StringBuilder());
A ClassCastException
将被抛出,因为 predicate
的 "real type" 实际上是 Predicate<String>
,而不是 Predicate<StringBuilder>
。因此编译器拒绝对 test
的调用,因为它不是类型安全的。
我也推荐阅读What is PECS (Producer Extends Consumer Super)?。
您为 p1
调用 startsWith
是合法的,即使 p1
是使用下限 ? super String
键入的,因为类型参数被推断为String
。 lambda 表达式 s -> s.startsWith("a");
被推断为 Predicate<String>
,将其赋值给 Predicate<? super String>
.
类型的变量是合法的
这样编译:
Predicate<String> ps = s -> s.startsWith("a");
Predicate<? super String> p1 = ps;
这不是:
// no "startsWith" on Object
Predicate<? super String> p1 = (Object s) -> s.startsWith("a");
JLS 参考位于 Section 15.27.3、"Type of a Lambda Expression"。
If T is a wildcard-parameterized functional interface type and the lambda expression is implicitly typed, then the ground target type is the non-wildcard parameterization (§9.9) of T.
这里,ground target type是lambda表达式的类型,T
是target type,这里是你的下界变量数据类型。这允许编译器将 Predicate<String>
指定为地面目标类型,成为 lambda 表达式的类型。
另请注意,您不能将 superclass 对象传递给 p1.test
,因为您可以(并且已经)将 Predicate<String>
分配给 p1
,需要 String
.
p1.test(new Object()); // Error: can't pass something higher than String
至于为什么不能传一个String
给p2.test
,当你有上界通配符如? extends String
时,这意味着类型参数可以是任意的class 即 String
或子类型。 (编译器忽略 String
在这里是 final
并且不能有 String
的任何子 class。)谓词 p2
可以被分配一个Predicate<SillyString>
,假设 SillyString
是 String
的子 class。但是您不能将 String
传递给可能需要 SillyString
.
的方法
我无法理解以下有关两个 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
我不明白的是,为什么可以从 class String
调用方法,例如startsWith()
?为什么我只能将 String
对象传递给 p1.test()
,我希望能够为 Number
和 Object
对象调用它。
作为 p1
的行为,我认为 p2
会,但事实并非如此。我什至无法将 String
对象传递给 p2.test()
。这对我来说没有意义,因为我们期望一个对象继承自 String
(包括 String
)。
我想这可能与我们指定引用类型而不是对象本身的类型有关。但是对象使用什么类型?
当您有 Predicate<? super String>
时,这意味着 Predicate
的实际实现保证能够处理 String
的实例。实现是否将 String
视为 String
或 CharSequence
甚至 Object
都无关紧要,只要它可以处理类型即可。这为 API 使用 接口提供了灵活性。例如:
void addFilter(Predicate<? super String> filter) { ... }
您可以使用 Predicate<CharSequence>
或 Predicate<Object>
实例调用 addFilter
,这对 API 无关紧要。例如:
addFilter((CharSequence cs) -> cs.length() % 2 == 0);
但是,当 API 实际调用 filter
时,它 必须 将 String
的实例传递给 test
.如果允许 API 调用 filter.test(new Object())
,则上面传递给 addFilter
的 Predicate<CharSequence>
将失败并显示 ClassCastException
.
当你有 Predicate<? extends CharSequence>
(使用 CharSequence
因为 String
是最终的 class)你不能调用 test
因为实现可能不会能够处理任何类型的CharSequence
。例如:
Predicate<? extends CharSequence> predicate = (String s) -> s.startsWith("...");
predicate.test(new StringBuilder());
A ClassCastException
将被抛出,因为 predicate
的 "real type" 实际上是 Predicate<String>
,而不是 Predicate<StringBuilder>
。因此编译器拒绝对 test
的调用,因为它不是类型安全的。
我也推荐阅读What is PECS (Producer Extends Consumer Super)?。
您为 p1
调用 startsWith
是合法的,即使 p1
是使用下限 ? super String
键入的,因为类型参数被推断为String
。 lambda 表达式 s -> s.startsWith("a");
被推断为 Predicate<String>
,将其赋值给 Predicate<? super String>
.
这样编译:
Predicate<String> ps = s -> s.startsWith("a");
Predicate<? super String> p1 = ps;
这不是:
// no "startsWith" on Object
Predicate<? super String> p1 = (Object s) -> s.startsWith("a");
JLS 参考位于 Section 15.27.3、"Type of a Lambda Expression"。
If T is a wildcard-parameterized functional interface type and the lambda expression is implicitly typed, then the ground target type is the non-wildcard parameterization (§9.9) of T.
这里,ground target type是lambda表达式的类型,T
是target type,这里是你的下界变量数据类型。这允许编译器将 Predicate<String>
指定为地面目标类型,成为 lambda 表达式的类型。
另请注意,您不能将 superclass 对象传递给 p1.test
,因为您可以(并且已经)将 Predicate<String>
分配给 p1
,需要 String
.
p1.test(new Object()); // Error: can't pass something higher than String
至于为什么不能传一个String
给p2.test
,当你有上界通配符如? extends String
时,这意味着类型参数可以是任意的class 即 String
或子类型。 (编译器忽略 String
在这里是 final
并且不能有 String
的任何子 class。)谓词 p2
可以被分配一个Predicate<SillyString>
,假设 SillyString
是 String
的子 class。但是您不能将 String
传递给可能需要 SillyString
.