Java 泛型究竟是如何工作的?

Java 泛型究竟是如何工作的?,java,eclipse,generics,arraylist,Java,Eclipse,Generics,Arraylist,在查找(测试)另一个问题的信息时,我遇到了一些问题,完全不知道为什么会发生。现在,我知道没有实际的理由这样做,这是绝对可怕的代码,但为什么这样做有效: ArrayList<Quod> test=new ArrayList<Quod>(); ArrayList obj=new ArrayList(); test=obj; obj.add(new Object()); System.out.println(test.get(0)); ArrayList test=new

在查找(测试)另一个问题的信息时,我遇到了一些问题,完全不知道为什么会发生。现在,我知道没有实际的理由这样做,这是绝对可怕的代码,但为什么这样做有效:

ArrayList<Quod> test=new ArrayList<Quod>();
ArrayList obj=new ArrayList();
test=obj;
obj.add(new Object());

System.out.println(test.get(0));
ArrayList test=new ArrayList();
ArrayList obj=新的ArrayList();
试验=obj;
添加(新对象());
System.out.println(test.get(0));
因此,基本上,我将向Quods的ArrayList添加一个对象。现在,我看到java无法有效地检查这一点,因为它必须查看所有引用,这些引用甚至可能没有存储在任何地方。但是为什么get()可以工作呢。get()不是应该返回Quod的一个实例吗,就像在Eclipse中将鼠标放在Quod上时所说的那样?如果它可以返回一个对象,而当它承诺返回一个Quod类型的对象时,它只能返回一个对象,那么当我说我将返回一个int时,为什么我不能返回一个字符串呢

事情变得更加奇怪。这将崩溃,因为运行时错误(java.lang.ClassCastException错误)(!?):

ArrayList test=new ArrayList();
ArrayList obj=新的ArrayList();
试验=obj;
添加(新对象());
System.out.println(test.get(0.toString());
为什么我不能调用对象上的toString?为什么println()方法可以调用它的toString,而我不能直接调用


编辑:我知道我没有对我创建的ArrayList的第一个实例做任何事情,所以这本质上只是浪费处理时间



编辑:我在Java1.6上使用Eclipse,其他人说他们在运行Java1.8的Eclipse中得到了相同的结果。但是,在其他一些编译器上,在这两种情况下都会抛出CCE错误。

Java泛型是通过类型擦除实现的,即类型参数仅用于编译和链接,但在执行时会被擦除。也就是说,编译时类型和运行时类型之间没有1:1的对应关系。特别是,泛型类型的所有实例共享相同的运行时类:

new ArrayList<Quod>().getClass() == new ArrayList<String>().getClass();
test.get(0)
的编译时类型是
qood
,唯一匹配的println方法是
println(Object)
,因此该方法的签名嵌入到类文件中。因此,在运行时,我们将
对象
传递给
println(Object)
方法。这完全可以,因此不会引发异常

在第二个代码示例中,您有:

System.out.println(test.get(0));
System.out.println(test.get(0).toString());
同样,test.get(0)的编译时类型是
qood
,但现在我们调用它的toString()方法。因此,编译器指定调用在类型
Quod
中声明(或继承到)的
toString
方法。显然,此方法需要
this
来指向
Quod
的实例,这就是为什么编译器在调用该方法之前向字节码中插入一个额外的对
Quod
的强制转换,而此强制转换会抛出一个
ClassCastException

也就是说,运行时允许第一个代码示例,因为引用没有以特定于
qood
的方式使用,但拒绝第二个代码示例,因为引用用于访问
qood
类型的方法


这就是说,您不应该依赖于编译器将插入此合成强制转换的确切时间,而应该首先通过编写类型正确的代码来防止堆污染的发生。Java编译器需要通过在代码可能导致堆污染时发出未检查和原始类型警告来帮助您做到这一点。去掉这些警告,您就不必了解这些细节;-)

问题的关键是:

为什么println()方法可以调用它的toString,但是 不是我直接说的吗

ClassCastException
异常不是由于调用
toString()
而发生的,而是由于编译器添加的显式强制转换。

一张图片胜过千言万语,所以让我们看看一些反编译代码

考虑以下代码:

public static void main(String[] args) {
    List<String> s = new ArrayList<String>();
    s.add("kshitiz");
    List<Integer> i = new ArrayList(s);

    System.out.println(i.get(0)); //This works
    System.out.println(i.get(0).toString()); // This blows up!!!
}
请参见显式转换为
整数
?现在,为什么编译器没有在前一行中添加强制转换?方法
println()
的签名为:

public void println(Object x)
因为
println
需要一个
对象
,而
i.get(0)
的结果是
对象
不添加强制转换

您也可以调用
toString()
,但前提是这样做不会生成强制转换:

public static void main(String[] args) {
    List<String> s = new ArrayList<String>();
    s.add("kshitiz");
    List<Integer> i = new ArrayList(s);

    myprint(i.get(0));
}

public static void myprint(Object arg) {
    System.out.println(arg.toString()); //Invoked toString but no exception
}
publicstaticvoidmain(字符串[]args){
列表s=新的ArrayList();
s、 添加(“kshitiz”);
列表i=新的阵列列表;
myprint(i.get(0));
}
公共静态void myprint(对象参数){
System.out.println(arg.toString());//调用了toString,但没有异常
}

您没有向
ArrayList
添加任何内容,您在重新分配
test
时丢弃了对它的引用。对于我来说,在java 1.6中的eclipse中,最上面的一个输出java.lang。Object@1e63e3d没有抛出任何错误你为什么删除了你的答案?这是迄今为止最好的一个,我打算在5分钟计时器结束时(使用sun-jdk-7)@pbabcdefp就接受它,这是因为有一个特殊版本的
println
字符串作为参数。所有其他类都使用接受
对象的版本。非常好的解释。我发现尝试
System.out.println(((Object)test.get(0)).toString()很有见地并看到它成功。我真的不明白这一点
toString()
不是特定于
Quod
toString()
是对象的一种方法。在运行时,根据
Quod
实例的运行时类型动态选择
toString()
方法。例如,如果类
Rod
扩展了
Quod
,编译器就无法知道是哪个
toString()
方法
public static void main(String[] args) {
    ArrayList s = new ArrayList();
    s.add("kshitiz");
    ArrayList i = new ArrayList(s);
    System.out.println(i.get(0));
    System.out.println(((Integer)i.get(0)).toString());
}
public void println(Object x)
public static void main(String[] args) {
    List<String> s = new ArrayList<String>();
    s.add("kshitiz");
    List<Integer> i = new ArrayList(s);

    myprint(i.get(0));
}

public static void myprint(Object arg) {
    System.out.println(arg.toString()); //Invoked toString but no exception
}