【问题标题】:Java Generics: Question regarding type capture and generated inference using generic methodsJava 泛型:关于使用泛型方法进行类型捕获和生成推理的问题
【发布时间】:2011-09-29 11:53:03
【问题描述】:

这是我上一个问题的后续,但由于上一个线程很长,我决定开始另一个与几乎相同主题有关的线程。

public class GenericMethodInference {

static <T> void test1(T t1, T t2) {}
static <T> void test3(T t1, List <T> t2) {}  
static <T> void test4(List <T> t1, List <T> t2) {}

public static void main(String [] args) {

    List <Object> c = new LinkedList<Object>();
    List <? extends Object> d = new ArrayList<Integer>();
    List e = new ArrayList<Integer>();

    test1("Hello", new Integer(1)); // ok clause (1)
    GenericMethodInference.<Object>test1("Hello", new Integer(1)); // ok clause (2)
    test3("Hello", c); // ok clause (3)
    test4(d,d) // clause (4) Error due to different type capture generated

}

注意:如果将光标移到每个子句上,您将看到正在生成并显示在 Eclipse 上的推理:

一个。第 (1) 条将产生 test1
湾。第 (2) 条将准确生成实际类型参数中定义的内容
C。第 (3) 条将产生 test3 >

问题:

  1. 为什么子句 (1) 没有产生 ?既然 如第 (2) 条所示工作,为什么是 代替生产?
  2. 为什么子句 (3) 产生 而不是
  3. 既然子句(4)使用同一个变量,为什么即使使用的参数是同一个变量d,也会生成2个不同类型的捕获?

【问题讨论】:

  • "如果您将光标移到每个子句上" - 请问是哪个 IDE? (更新:感谢您的编辑)
  • @TheEliteGentleman - 那个给出了编译错误,所以我假设没有推理工具提示?
  • 请参阅更新编辑 1。还有另一个问题。谢谢

标签: java generics


【解决方案1】:

为什么子句 (1) 没有产生 ?既然 如第 (2) 条所示工作,为什么是 extends Object> 正在生产?

这是三个问题中最好的一个。我的想法是编译器/Eclipse 不想假设 Object 一定是在 StringInteger 之间推断的 T 类型,所以它很安全。正如@bringer128 指出的那样,StringInteger 也都实现了SerializableComparable - 所以这些类型也是该方法的推断类型的候选者。

值得注意的是,下面的代码给出了编译器错误“illegal start of type”:

GenericMethodInference.<? extends Object>test1("Hello", new Integer(1));

这是因为指定通配符作为方法的类型参数是无效的。因此,您在工具提示中看到的事实与编译器/Eclipse 报告此信息的工具的微妙之处有关 - 它仅确定 T 在其范围内,而不是它的范围内。

请记住,Java 的泛型实现只是为了程序员的方便/理智。一旦编译成字节码,type erasure 将摆脱任何T 的概念。所以在它的检查中,编译器只需要保证a有效的T可以被推断出来,但不一定是什么。


为什么子句 (3) 产生 而不是 ?

因为在这种情况下,List&lt;Object&gt; 在预期 List&lt;T&gt; 的位置被传递的事实告诉编译器 T 正是 Object


既然子句(4)使用了同一个变量,为什么即使使用的参数是同一个变量d,也会生成2个不同类型的捕获?

编译器假设d 实际上引用同一个对象是不安全的,即使在评估参数之间也是如此。例如:

test4(d,(d = new ArrayList<String>()));

在这种情况下,List&lt;Integer&gt; 将传递给第一个参数,List&lt;String&gt; 传递给第二个参数 - 两者都来自 d。由于这种情况是可能的,因此编译器更容易安全地使用它。

【讨论】:

  • FYI 对于第 1 条,它们有共同的父接口 Serializable 和 Comparable。编译器总是会尝试推断他们能找到的最具体的接口,并且如果它可以帮助它,它不喜欢解析到 Object。
  • 哦,我在 IntelliJ 中对此进行了测试,方法是将方法重新定义为 static &lt;T&gt; List&lt;T&gt; test1(T t1, T t2) { return null;},然后使用 IDE 根据方法的返回值生成变量(Ctrl+Alt+v)。我认为 Netbeans 可以做类似的事情。
  • @Bringer128 - 是的,你是对的。有趣 - NetBeans 似乎随机选择 Serializable 作为生成的变量。但是当我将变量更改为无效的东西时,比如StringBuilder,编译错误是required: java.lang.StringBuilder found: java.lang.Object&amp;java.io.Serializable&amp;java.lang.Comparable&lt;? extends java.lang.Object&amp;java.io.Serializable&amp;java.lang.Comparable&lt;?&gt;&gt; 所以我猜这将是推断类型的正式表达式。
【解决方案2】:

test1() 的情况实际上是相当险恶的。请参阅 JLS3 15.12.2.7。

我们不应该知道类型推断的细节——在大多数情况下,直觉与算法是一致的。唉,情况并非总是如此,就像在看似微不足道的 test1() 示例中一样。

我们的约束是T :&gt; StringT :&gt; Integer(“:&gt;”表示超类型)

这导致T=lub(String,Integer)lub 表示“最小上限”。

由于String &lt;: Comparable&lt;String&gt;Integer &lt;: Comparable&lt;Integer&gt;,这导致lci({Comparable&lt;String&gt;, Comparable&lt;Integer&gt;}),产生Comparable&lt;? extends lub(String,Integer)&gt;,即Compable&lt;? extends T&gt;

最后,我们得到了T = Serializable &amp; Compable&lt;? extends T&gt;,一个自引用的定义! Spec 称之为“无限类型”:

上面的过程有可能产生一个无限类型。这是允许的,Java 编译器必须识别这种情况并使用循环数据结构适当地表示它们。

我们来看看javac是怎么表示的:(javac 7)

static <T> T test1(T t1, T t2) {}

public static void main(String[] args)
{
    Void x = test1("Hello", new Integer(1)); 
}

error: incompatible types
required: Void
found:    INT#1
where INT#1,INT#2 are intersection types:
INT#1 extends Object,Serializable,Comparable<? extends INT#2>
INT#2 extends Object,Serializable,Comparable<?>

这似乎不对;它不是真正的递归;似乎 javac 检测到lub() 中的递归并放弃,导致Comparable&lt;?&gt; 的类型不太具体

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多