泛型工作如何工作? [英] How exactly do Generics work?

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

问题描述

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

 的ArrayList<狴> 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中将鼠标放在Eclipse上时所说的那样?如果它可以返回一个只有一个对象的对象,当它答应返回一个类型为Quod的对象时,为什么我不能返回一个String,当我说我会返回一个int?


$ b $而且事情甚至更加恶劣。这会崩溃,因为它假设运行时错误(java.lang.ClassCastException错误)(!?!?):

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

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

为什么我不能在对象上调用toString?为什么println()方法调用它的toString是不错的,但不是直接给我?






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






编辑:我在Java上使用Eclipse 1.6其他人表示,他们在运行java 1.8的Eclipse中获得相同的结果。然而,在其他一些编译器上,这两种情况都会引发CCE错误。

解决方案

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

  new ArrayList&Quidget()。getClass() == new ArrayList< String>()。getClass(); 

在编译时类型系统中,键入参数存在,并用于类型检查。在运行时类型系统中,键入参数不存在,因此未检查。



这对于转换和原始类型来说是没有问题的。演员是类型正确性的断言,并将类型检查从编译时延到运行时。但是正如我们所看到的,编译时和运行时类型之间没有1:1的对应关系;类型参数在编译期间被清除。因此,运行时无法完全检查包含类型参数的转换的正确性,并且错误的转换可能会成功,违反了编译时类型系统。 Java语言规范调用这个堆污染



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



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

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

obj 的声明类型是原始的键入 ArrayList 。 Raw类型禁止在编译时检查类型参数。因此,即使 ArrayList 可能只保留<$ c>,我们可能会将 Object 传递给其添加方法$ c> Quod 在编译时类型系统中的实例。也就是说,我们已经成功地骗了编译器,并且完成堆污染。



这将留下运行时类型的系统。在运行时类型系统中,ArrayList使用类型为 Object 的引用,因此将 Object 传递给 add 方法完全可以。所以调用 get(),这也返回 Object 。而且这里是分歧的:在你的第一个代码示例中,你有:

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

的编译时类型test.get(0) Quod ,唯一匹配的println方法是 println(Object),因此它是该方法的签名它嵌入在类文件中。因此,在运行时,我们将 Object 传递给 println(Object)方法。这是完全可以的,因此没有例外。



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

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

再次,编译时间类型为 test.get(0) code>是 Quod ,但现在我们正在调用其toString()方法。因此,编译器指定要调用(或继承)类型 Quod 中的 toString 方法。显然,这个方法需要这个指向一个 Quod 的实例,这就是为什么编译器插入一个额外的转换 Quod 到调用方法之前的字节代码 - 而这个转换会抛出一个 ClassCastException



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



那就是说,你不应该依赖于编译器将插入这个合成转换,但是首先通过写入类型正确的代码来防止堆污染发生。当您的代码可能导致堆污染时,Java编译器需要通过发出未检查的和原始类型的警告来帮助您。摆脱警告,你不必理解这些细节; - )。


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));

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?

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());

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?


EDIT: 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.


EDIT: 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 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.

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));

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.

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));

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());

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.

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.

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天全站免登陆