【问题标题】:Java Generics super/extends - different behaviors (for OCP java 8 study purposes)Java Generics super/extends - 不同的行为(用于 OCP java 8 学习目的)
【发布时间】:2018-05-08 23:46:29
【问题描述】:

假设我有以下结构:

java.lang.Object
class A extends Object { public boolean mA() {return true;} }
class B extends A { public boolean mB() {return false;} }

现在我正在编写一个 main 方法来使用它。 我对以下概念很好:(从超级的get方法返回的'Object'类型)

public static void m1(List<? super A> l ) {
    l.get(0).toString();
}

因为我可以使用

来调用它
m1(new ArrayList<A>());
m1(new ArrayList<Object>());

我对此也很好:('A' 类型从扩展的 get 方法返回)

public static void m2(List<? extends A> l ) {
    l.get(0).mA();
}

因为我可以使用

来调用它
m2(new ArrayList<A>());
m2(new ArrayList<B>());

最后我的问题是:为什么下面带有“super”的定义接收类型“A”作为参数,而不是 Object?

Predicate<? extends A> p1 = a -> a.mA(); // ok
Predicate<? super A> p2 = a -> a.mA(); // why is it of type 'A' too??

我认为它会变成类似的东西(我知道下面的代码是无效的......我只是认为它会类似于那个)。

PredicateImpl<T super A> {
    public boolean test(T t);
}

我还检查了反编译的类,但没有得到任何新线索。谁能告诉我引擎盖下发生了什么?我已经阅读了 PECS 的讨论,但它在这个具体问题上对我没有帮助。

提前感谢您! ;)

===== 收到答案后用额外的代码编辑=====

Predicate<? super A> pSuper = a -> a.mA();
Predicate<? extends A> pExtends = a -> a.mA();      // ?? A ??

Predicate<Object> pObject = o -> o.equals(null);
Predicate<A> pA = a -> a.mA();
Predicate<B> pB = b -> b.mB();

pSuper = pObject;
pSuper = pA;
pSuper = pB;        // compiler error

pExtends = pObject; // compiler error
pExtends = pA;
pExtends = pB;

有趣的替代语法(虽然我认为 Predicate 非常没用,因为它只在测试方法中接受“null”)。无论如何,我正在寻找这样的练习,无论如何我都学会了。

Predicate<? extends A> pExtends2 = (B b) -> b.mB();

【问题讨论】:

  • 好问题!我从来没有注意到这一点,但它似乎真的很有趣!我希望我能提供帮助,但我对 Java 中遗传学的实现知之甚少。但是要意识到泛型都是编译时的,它们在字节码生成期间被简化为对象。泛型只是为了方便程序员使用。
  • 嗯,它不可能是Object,因为你打电话给mA()。尝试拨打toString() 看看你会得到什么。

标签: java generics lambda extends super


【解决方案1】:

JLS, Section 9.9,谈论 lambda 表达式的类型推断。其中一节讨论了存在通配符和边界时的类型推断。

当一个通用功能接口通过通配符参数化时,有许多不同的实例可以满足通配符并产生不同的函数类型。例如Predicate&lt;Integer&gt;(函数类型Integer->boolean)、Predicate&lt;Number&gt;(函数类型Number->boolean)和Predicate&lt;Object&gt;(函数类型Object->@)中的每一个987654332@) 是Predicate&lt;? super Integer&gt;。有时,可以从上下文(例如 lambda 表达式的参数类型)中知道预期的函数类型(第 15.27.3 节)。其他时候,需要选择一个;在这些情况下,使用边界。

(我的粗体强调)

在这里,目标类型为Predicate&lt;? super A&gt;,lambda 参数a 的多种可能类型是可能的:Object 和A。绑定的目标类型为? super A,所以使用绑定的A作为类型。

这对于任何通配符限制(上限或下限)都是最有意义的。有了上限,绑定类型就很有意义了。类型为A 或更低,但它可能只是A,因此A 方法应该可用。 Predicate&lt;A&gt; 可分配给 Predicate&lt;? extends A&gt;。

使用下限,类型为A 或更高。但是为什么不选择Object?因为A 工作得很好。

Predicate&lt;A&gt; 可以分配给Predicate&lt;? super A&gt;。 Predicate&lt;Object&gt; 也可以分配给 Predicate&lt;? super A&gt;,但这会造成不必要的限制。 lambda 表达式将被限制为调用 Object 方法,因为它可以很容易地调用您编写的 A 方法(方法 mA)。在这里选择最严格的类型是有意义的,这样程序员可以调用最多的方法。此外,根据边界最严格的功能接口类型必须是可分配的。 Predicate&lt;A&gt; 可分配给 Predicate&lt;? super A&gt;。

您甚至可以将 lambda 表达式 a -&gt; a.mA() 分配给 Predicate&lt;? extends B&gt;、Predicate&lt;? super B&gt;,甚至是 Predicate&lt;B&gt;。推断出的类型是B,而且这个类型肯定有mA的方法,继承自A。

如果您要尝试将 lambda 表达式 a -&gt; a.mA() 分配给 Predicate&lt;Object&gt; 或 Predicate&lt;?&gt;,则 Object 必须是推断类型。在这里,您会收到关于 mA 未在 Object 上定义的编译器错误。

【讨论】:

  • 这基本上是核心。我认为15.27.3 和18.5.3 也可能是相关的,但是......这已经很难理解了。
  • 谢谢@rgettman。当您提到“可分配”部分时,这对我来说很有意义。代码格式标记在这里对我不起作用,因此我将添加一些其他行,用于在问题部分测试您的答案,以及我在研究更多内容时发现的有趣语法。
猜你喜欢
  • 1970-01-01
  • 2014-12-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-03-01
  • 1970-01-01
  • 2016-06-19
  • 2015-10-14
相关资源
最近更新 更多