【问题标题】:How JDK 8's type inference works with generic?JDK 8 的类型推断如何与泛型一起使用?
【发布时间】:2017-06-04 07:59:13
【问题描述】:

我的代码使用 JDK 7 编译失败,但使用 JDK 8 编译成功。

抽象实际代码:

interface A {
...
}

class B implements A {
...
}

public void AAA(List<A> list) {...}

AAA(Collections.singletonList(new B()));

Collections.singletonList 定义为

public static <T> List<T> singletonList(T o) {
    return new SingletonList<>(o);
}

据我所知,基于泛型,T 将被推断为 B,因此 Collections.singletonList(new B()) 将是无法分配给 List 的 List,因为 Java 泛型是不变的。

但是在 JDK 8 中,T 被推断为 A 并且编译成功。

我想知道如何将 T 推断为 A,因为这里有两个变量用于类型 T:A 和 B。

有优先顺序吗?还是编译器找到共同的祖先类?

附上官方文档更感谢!

提前致谢!

ps1。 JDK 7的版本是Oracle 1.7.0_79,JDK 8的版本是Oracle 1.8.0_66。

ps2。以下是实际代码的链接:

https://github.com/apache/storm/blob/85a31e2fdec1ffef83e1ff438cd765a821fb06e4/examples/storm-opentsdb-examples/src/main/java/org/apache/storm/opentsdb/SampleOpenTsdbBoltTopology.java#L48

https://github.com/apache/storm/blob/85a31e2fdec1ffef83e1ff438cd765a821fb06e4/external/storm-opentsdb/src/main/java/org/apache/storm/opentsdb/bolt/OpenTsdbBolt.java#L77

https://github.com/apache/storm/blob/85a31e2fdec1ffef83e1ff438cd765a821fb06e4/external/storm-opentsdb/src/main/java/org/apache/storm/opentsdb/bolt/TupleOpenTsdbDatapointMapper.java#L37

【问题讨论】:

    标签: java generics java-8 type-inference


    【解决方案1】:

    这实际上在 Target Types 的末尾here 进行了解释。

    目标类型已扩展为包含方法参数...

    这意味着

     AAA(Collections.singletonList(new B())); // returns List<A> NOT List<B>
    

    在 jdk-7 方法参数中用于找出目标类型,这就是为什么 T 被推断为 B 并且这就是它失败的原因。

    【讨论】:

    • 感谢您的评论。我之前读过文档,但不清楚,因为该示例使用 Collections.emptyList(),它不输入​​另一个类型提示。根据您的描述,这只是一个优先顺序,A总是在B之前?
    【解决方案2】:

    嗯,在语言规范中有一个全新的章节,§18. Type Inference,但这并不容易阅读。即使是第一部分的摘要,准确地解决了您的问题,也很难:

    与 Java® 语言规范的 Java SE 7 版本相比,推理方面的重要变化包括:

    • 添加对 lambda 表达式和方法引用作为方法调用参数的支持。
    • 泛化以根据 poly 表达式定义推理,在推理完成之前,这些表达式可能没有明确定义的类型。这对于改进嵌套泛型方法和菱形构造函数调用的推理具有显着效果。
    • 描述如何使用推理来处理通配符参数化的功能接口目标类型和最具体的方法分析。
    • 阐明调用适用性测试(仅涉及调用参数)和调用类型推断(包含目标类型)之间的区别。
    • 延迟所有推理变量的解析,即使是那些具有下界的变量,直到调用类型推理,以获得更好的结果。
    • 改进相互依赖(或自依赖)变量的推理行为。
    • 消除错误和潜在的混乱来源。本次修订更加仔细和精确地处理了特定转换上下文和子类型之间的区别,并通过并行相应的非推理关系来描述归约。如果有意偏离非推理关系,则会明确指出这些偏离。
    • 为未来发展奠定基础:推理的增强或新应用将更容易集成到规范中。

    第二个项目符号对您的代码示例影响最大。您有一个泛型方法的嵌套方法调用,而没有指定显式类型参数,这使其成为所谓的 poly 表达式,其实际类型可能从 目标类型 中推断出来,在您的情况下,这是AAA 的参数类型。

    所以这是一个相当简单的星座,因为AAA 不是通用的,而且它的参数类型也没有歧义。总是List&lt;A&gt;。这里没有搜索“共同祖先类”,只需要检查参数表达式的类型(B)是否与推断的类型(A)兼容。

    【讨论】:

    • 感谢您的详细解答。是的,为了好奇,看 jls doc 似乎真的很感动。我已经按照链接进行了更深入的浏览,但根本无法理解。 :) 你的答案看起来有足够的深度。再次感谢。
    【解决方案3】:

    http://docs.oracle.com/javase/tutorial/java/generics/genTypeInference.html的“目标类型”部分

    上面写着

    这在 Java SE 8 中不再需要。什么是目标类型的概念已扩展为包括方法参数,例如方法 processStringList 的参数。

    您的方法 AAA 需要类型 A 的列表,所以我相信只要您为调用 AAA() 提供的类型是可用的类型转换或 A 的子类型,Java8 的目标类型就会根据提示选择 A 而不是 B你给代码?

    【讨论】:

    • 感谢您的回答,这似乎与尤金的回答重复。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-03-05
    • 1970-01-01
    • 2020-12-24
    • 2016-07-27
    • 2021-10-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多