我正试图了解? super T
的工作原理,并停留在以下示例中:
class Thing {
AnotherThing change() { return null; }
}
class AnotherThing {}
interface Fn<A, B> {
B run(A a);
}
class Stream<T> {
<R> Stream<R> map(Fn<? super T, ? extends R> fn) {
return null;
}
}
void method() {
Stream<Thing> s = new Stream<>();
s.map(a -> a.change());
}
最奇怪的一点是Java可以推断a
是Thing
,因此可以调用change()
。
但这不是事实。在Fn<? super T, ? extends R> fn
中,a
可以是Thing
或java.lang.Object
;两者都是T
(或在本例中为Thing
)的“超级”。
我缺少有关? super T
的课程。我想知道是否有人可以解释为什么Java可以推断出a
是Thing
。谢谢!
答案 0 :(得分:2)
让我们谈谈Fn
,而不是使用自定义的java.util.function.Consumer<T>
接口。如果您不知道,则Consumer
接口只有一个抽象方法:accept(T)
。当您使用Consumer<? super T>
时,是说Consumer
的实现可以接受T
或 {{1}的超类型}。但是,这并不意味着T
的任何超类型都可以传递给T
方法-它必须是accept
类型。您可以通过以下方式看到它:
T
但是,如果您有类似的方法:
Consumer<? super CharSequence> con = System.out::println;
con.accept("Some string"); // String implements CharSequence
con.accept(new Object()); // compilation error
然后您可以这样称呼它:
void subscribe(Consumer<? super CharSequence> con) { ... }
这可以灵活地使用API。呼叫者可以传递旨在接受Consumer<Object> con = System.out::println;
subscribe(con);
(即Consumer
)的T
或CharSequence
的超类型(例如T
)。但是传递给Object
方法的 actual 类型仍将是accept
(即T
),它只是该方法的实现 CharSequence
可能更笼统。如果将Consumer
的参数改为声明为subscribe
,则上述方法将无效。
毫无疑问,Consumer<CharSequence>
是消费者。当可以产生时,通常最好使用Consumer
而不是? extends
。这就是所谓的“生产者扩展消费者超级用户”(PECS)。您可以在this question中详细了解它。
回到您的示例,您有一个名为? super
的类,具有方法Stream<T>
,并且您询问如何知道Stream<R> map(Fn<? super T, ? extends R>)
是T
。之所以知道这一点,是因为您已声明Thing
,从而使Stream<Thing>
成为T
。调用Thing
方法时,您正在实现map
内联。这使得实现对Fn
使用Thing
,并基于lambda内部的返回签名,对T
使用AnotherThing
。换句话说,您的代码等效于:
R
但是您可以将Fn<Thing, AnotherThing> f = a -> a.change(); // or you can use Thing::change
Stream<Thing> stream = new Stream<>();
stream.map(f);
传递给Fn<Object, AnotherThing>
方法。但是请注意,使用时:
map
Fn<Object, AnotherThing> f = a -> a.change();
Stream<Thing> stream = new Stream<>();
stream.map(f);
的声明的类型现在为a
,但 actual 的类型仍然为Object
(即{{ 1}})。
答案 1 :(得分:0)
但这不是事实。在
Fn<? super T, ? extends R> fn
中,a
可以是Thing
或java.lang.Object
;两者都是T
(或在本例中为Thing
)的“超级”。
您不应说“ a
可以是Thing
或某些超类型,”您应该说“ a
可以是Thing
或某种超类型”。在map
的定义中,以Fn<? super T, ? extends R> fn
为给定,fn
的调用者已经选择了map
的参数类型。在map
的实现内部,您应该使用过去时,因为其他人已经为您选择了类型,并且您必须确保map
中的代码不管它是什么。但是,当您呼叫 map
时,只要 a
的类型是Thing
,就可以选择。 {1}}。在您的代码中,a
的类型 可以为Thing
或Object
,这是您的选择。因为您尚未指定哪种方法,所以Java很不错,并且可以为您选择最具体的类型Thing
,这样您就可以拥有尽可能多的可用方法。
通常,如果您拥有一个F<? extends/super T> f
,则已经为您选择了类型参数,而您只需要处理它即可。绑定类型就是您的“朋友”;它为您提供了有关该参数可能是的其他信息。在相反的情况下,如果需要一个F<? extends/super T> f
,则可以选择type参数作为满足其需要的任何参数。这样,类型绑定就是您的“敌人”;它限制了您可以为参数选择的范围。