泛型究竟是如何工作的? [英] How exactly do Generics work?

查看:28
本文介绍了泛型究竟是如何工作的?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

While looking up (testing) information for another question, I came across something and had completely no clue why it was happening. Now, I know that there is no practical reason to do this, and that this is absolutely horrific code, but why is it that this works:

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

System.out.println(test.get(0));

所以,基本上,我将一个对象添加到 Quods 的 ArrayList.现在,我看到 java 无法有效地检查这一点,因为它必须查看所有引用,这些引用甚至可能没有存储在任何地方.但是,为什么 get() 有效.是不是 get() 假设返回 Quod 的一个实例,就像在 Eclipse 中将鼠标放在它上面时所说的那样?如果它在承诺返回 Quod 类型的对象时可以返回一个只是对象的对象,为什么我说我将返回一个 int 时不能返回一个 String?

So, basically, I am adding an Object to an ArrayList of Quods. Now, I see how java would have no way of efficiently checking for this, because it would have to look through all of the references, which probably aren't even stored anywhere. But then why is it that get() works. Isn't get() suppose to return an instance of Quod, like it says when you put your mouse over it in Eclipse? If it can return an object that is only an object when it promised to return an object of type Quod, why can't I return a String when I say I will return an int?

事情变得更奇怪了.这会崩溃,因为它会出现运行时错误(java.lang.ClassCastException 错误)(!?!?):

And things get even weirder. This crashes as it is suppose to with a run-time error(java.lang.ClassCastException error)(!?!?):

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

System.out.println(test.get(0).toString());

为什么我不能在对象上调用 toString?为什么 println() 方法可以调用它的 toString,而我不能直接调用?

Why can't I call the toString on an Object? And why is it fine for the println() method to call its toString, but not for me to directly?

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

I know that I am not doing anything with the first instance of ArrayList that I create, so it is essentially just a waste of processing time.

我在 Java 1.6 上使用 Eclipse 其他人说他们在运行 java 1.8 的 Eclipse 中得到相同的结果.但是,在其他一些编译器上,这两种情况都会引发 CCE 错误.

I am using Eclipse on Java 1.6 Others have said that they get the same results in Eclipse running java 1.8. However, on some other compilers, a CCE error is thrown on both cases.

推荐答案

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

Java generics are implemented through type erasure, i.e. type arguments are only used for compilation and linking, but erased for execution. That is, there is no 1:1 correspondence between compile time types and runtime types. In particular, all instances of a generic type share the same runtime class:

new ArrayList<Quod>().getClass() == new ArrayList<String>().getClass();

在编译时类型系统中,存在类型参数,用于类型检查.在运行时类型系统中,类型参数不存在,因此不会被检查.

In the compile time type system, type arguments are present, and used for type checking. In the runtime type system, type arguments are absent, and therefore not checked.

这对转换和原始类型没有问题.强制转换是类型正确性的断言,并将类型检查从编译时推迟到运行时.但是正如我们所见,编译时类型和运行时类型之间没有 1:1 的对应关系;类型参数在编译期间被擦除.因此,运行时无法完全检查包含类型参数的强制转换的正确性,错误的强制转换可能会成功,违反编译时类型系统.Java 语言规范将此称为堆污染.

This would be no problem but for casts and raw types. A cast is an assertion of type correctness, and defers the type check from compile time to runtime. But as we have seen, there is no 1:1 correspondence between compile time and runtime types; type arguments are erased during compilation. As such, the runtime can not fully check the correctness of casts containing type parameters, and an incorrect cast can succeed, violating the compile time type system. The Java Language Specification calls this heap pollution.

因此,运行时不能依赖类型参数的正确性.然而,它必须强制执行运行时类型系统的完整性以防止内存损坏.它通过将类型检查延迟到实际使用泛型引用来实现这一点,此时运行时知道它必须支持的方法或字段,并且可以检查它实际上是声明该字段或方法的类或接口的实例.

As a consequence, the runtime can not rely on the correctness of type arguments. Nevertheless, it must enforce the integrity of the runtime type system to prevent memory corruption. It accomplishes this by delaying the type check until the generic reference is actually used, at which point the runtime knows the method or field it must support, and can check that it actually is an instance of the class or interface that declares that field or method.

回到你的代码示例,我稍微简化了它(这不会改变行为):

With that, back to your code example, which I have slightly simplified (this doesn't change the behavior):

ArrayList<Quod> test = new ArrayList<Quod>();
ArrayList obj = test; 
obj.add(new Object());
System.out.println(test.get(0));

obj 的声明类型是原始类型ArrayList.原始类型在编译时禁用类型参数的检查.因此,我们可以将 Object 传递给它的 add 方法,即使 ArrayList 在编译时类型系统中可能只包含 Quod 实例.也就是说,我们成功地欺骗了编译器,实现了堆污染.

The declared type of obj is the raw type ArrayList. Raw types disable the checking of type arguments at compile time. As a consequence, we may pass an Object to its add method, even though the ArrayList may only hold Quod instances in the compile time type system. That is, we have successfully lied to the compiler and accomplished heap pollution.

这就离开了运行时类型系统.在运行时类型系统中,ArrayList 使用 Object 类型的引用,因此将 Object 传递给 add 方法是完全可以的.调用 get() 也是如此,它也返回 Object.这里有一些分歧:在你的第一个代码示例中,你有:

That leaves the runtime type system. In the runtime type system, the ArrayList works with references of type Object, so passing an Object to the add method is perfectly ok. So is invoking get(), which also returns Object. And here is were things diverge: In your first code example, you have:

System.out.println(test.get(0));

test.get(0)的编译时类型是Quod,唯一匹配的println方法是println(Object),并且因此,该方法的签名嵌入在类文件中.因此,在运行时,我们将一个 Object 传递给 println(Object) 方法.这完全没问题,因此不会抛出异常.

The compile time type of test.get(0) is Quod, the only matching println method is println(Object), and therefore it is that method's signature that is embedded in the class file. At runtime, we therefore pass an Object to the println(Object) method. That is perfectly ok, and hence no exception is thrown.

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

In your second code example, you have:

System.out.println(test.get(0).toString());

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

Again, the compile time type of test.get(0) is Quod, but now we are invoking its toString() method. The compiler therefore specifies that the toString method declared in (or inherited to) type Quod is to be invoked. Obviously, this method requires this to point to an instance of Quod, which is why the compiler inserts an additional cast to Quod into the byte code prior to invoking the method - and this cast throws a ClassCastException.

也就是说,运行时允许第一个代码示例,因为该引用不是以特定于 Quod 的方式使用,但拒绝第二个,因为该引用用于访问 Quod.

That is, the runtime permits the first code example because the reference is not used in a way specific to Quod, but rejects the second because the reference is used to access a method of type Quod.

也就是说,您不应该依赖于编译器何时会插入这个合成转换,而是通过编写类型正确的代码首先防止发生堆污染.每当您的代码可能导致堆污染时,Java 编译器都需要通过发出未经检查的原始类型警告来帮助您.去掉警告,你就不必了解这些细节了 ;-)

That said, you should not rely on when exactly the compiler will insert this synthetic cast, but prevent heap pollution from occurring in the first place by writing type correct code. Java compilers are required to assist you in this by emitting unchecked and raw type warnings whenever your code might cause heap pollution. Get rid of the warnings, and you won't have to understand those details ;-).

这篇关于泛型究竟是如何工作的?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆