Java Generics:关于使用泛型方法进行类型捕获和生成推断的问题

时间:2011-09-28 04:12:52

标签: java generics

这是我上一个问题的后续内容,但由于前一个帖子很长,我决定启动另一个与几乎相同主题有关的线程。

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)将产生&lt;? extends Object&gt; test1&lt;? extends Object,? extends Object&gt;
 湾第(2)款将完全产生实际类型参数中定义的内容  C。条款(3)将产生&lt; Object&gt; test3&lt; Object,List&lt; Object&gt;&gt;

问题:

  1. 为什么第(1)条没有产生&lt; Object&gt;?由于&lt; Object&gt;如第(2)项所示,为什么&lt;? extends Object&gt;正在生产?
  2. 为什么第(3)条产生&lt;对象&gt;而不是&lt;?扩展对象&gt;?
  3. 由于第(4)条使用相同的变量,为什么2个不同类型的捕获产生的事件虽然使用的参数是相同的变量d?

2 个答案:

答案 0 :(得分:4)

  

为什么第(1)条没有产生&lt; Object&gt;?由于&lt; Object&gt;如第(2)项所示,为什么&lt;? extends Object&gt;正在生产?

这是三个中最好的问题。我的想法是编译器/ Eclipse不想假设Object必然是在TString之间推断的类型Integer,所以它可以安全地运行。正如@bringer128所指出的那样,StringInteger也都实现了SerializableComparable - 因此这些类型也是方法的推断类型的候选者。 / p>

值得注意的是,以下代码给出了编译器错误“非法启动类型”:

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

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

请记住,Java的泛型实现仅仅是为了程序员的方便/理智。一旦编译成字节码,type erasure就会摆脱T的任何概念。因此,在检查时,编译器只需要确保可以推断出 a 有效T,但不一定是这样。


  

为什么第(3)条产生&lt; Object&gt;而不是&lt;? extends Object&gt;?

因为在这种情况下,预期List<Object>传递List<T>的事实告诉编译器T正是Object


  

由于第(4)条使用相同的变量,为什么2个不同类型的捕获产生的事件尽管使用的参数是相同的变量d?

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

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

在这种情况下,List<Integer>将传递到第一个参数,而List<String>将传递到第二个参数 - 均来自d。由于这种情况是可能的,因此编译器更容易安全地使用它。

答案 1 :(得分:2)

test1()案件实际上非常险恶。见JLS3 15.12.2.7。

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

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

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

String <: Comparable<String>Integer <: Comparable<Integer>后,这会产生lci({Comparable<String>, Comparable<Integer>}),产生Comparable<? extends lub(String,Integer)>,即Compable<? extends T>

最后,我们有T = Serializable & Compable<? extends T>,一个自我引用的定义! 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<?>