为什么涉及“?super T”的代码可以成功编译?

时间:2019-05-03 02:34:31

标签: java

我正试图了解? 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可以推断aThing,因此可以调用change()

但这不是事实。在Fn<? super T, ? extends R> fn中,a可以是Thingjava.lang.Object;两者都是T(或在本例中为Thing)的“超级”。

我缺少有关? super T的课程。我想知道是否有人可以解释为什么Java可以推断出aThing。谢谢!

2 个答案:

答案 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)的TCharSequence的超类型(例如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可以是Thingjava.lang.Object;两者都是T(或在本例中为Thing)的“超级”。

您不应说“ a 可以Thing或某些超类型,”您应该说“ a 可以Thing或某种超类型”。在map的定义中,以Fn<? super T, ? extends R> fn为给定,fn的调用者已经选择了map的参数类型。在map的实现内部,您应该使用过去时,因为其他人已经为您选择了类型,并且您必须确保map中的代码不管它是什么。但是,当您呼叫 map时,只要 a的类型是Thing,就可以选择。 {1}}。在您的代码中,a的类型 可以为ThingObject,这是您的选择。因为您尚未指定哪种方法,所以Java很不错,并且可以为您选择最具体的类型Thing,这样您就可以拥有尽可能多的可用方法。

通常,如果您拥有一个F<? extends/super T> f,则已经为您选择了类型参数,而您只需要处理它即可。绑定类型就是您的“朋友”;它为您提供了有关该参数可能是的其他信息。在相反的情况下,如果需要一个F<? extends/super T> f,则可以选择type参数作为满足其需要的任何参数。这样,类型绑定就是您的“敌人”;它限制了您可以为参数选择的范围。