当前位置: 首页 > 面试题库 >

泛型到底如何工作?

罗翰
2023-03-14
问题内容

在查找(测试)信息时,我遇到了一些问题,完全不知道为什么会发生。现在,我知道没有实际的理由执行此操作,这绝对是可怕的代码,但是为什么行得通呢?

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的实例上所说的那样?如果它答应返回Quod类型的对象时可以返回仅是一个对象的对象,为什么当我说要返回int时为什么不能返回String?

事情变得更奇怪了。这会因为发生运行时错误(java.lang.ClassCastException错误)(!?!?)而崩溃:

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可以,但是我不能直接调用它呢?

编辑:我知道我对我创建的ArrayList的第一个实例不做任何事情,因此,这实际上只是在浪费处理时间。

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


问题答案:

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

new ArrayList<Quod>().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。原始类型会在编译时禁用类型参数检查。结果,Object即使ArrayList可能仅将Quod实例保存在编译时类型系统中,我们也可以将其传递给其add方法。也就是说,我们已经成功地对编译器说谎并实现了堆污染。

剩下的是运行时类型系统。在运行时类型系统中,ArrayList与type的引用一起使用Object,因此将an传递Objectadd方法是完全可以的。调用get()也是如此,它也会返回Object。这是有分歧的:在您的第一个代码示例中,您有:

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

的编译时间类型test.get(0)Quod,唯一匹配的println方法println(Object),因此嵌入在类文件中的是该方法的签名。因此,在运行时,我们将传递Objectprintln(Object)方法。完全可以,因此不会引发异常。

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

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

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

也就是说,运行时允许使用第一个代码示例,因为未以特定于Quod引用的方式使用引用,但是由于引用被用于访问type方法而拒绝了第二个示例Quod

就是说,您不应该依赖编译器何时确切地插入此合成强制转换,而应该通过编写类型正确的代码来防止堆污染的发生。需要Java编译器通过在代码可能导致堆污染的情况下发出未经检查的原始警告来帮助您。摆脱警告,您将不必了解那些细节;-)。



 类似资料:
  • 问题内容: 我真的很难理解Django的内容类型的概念。感觉非常骇人听闻,并且最终与Python趋向于做事相反。话虽如此,如果我要使用Django,则必须在框架范围内进行工作。 因此,我来​​这里想知道是否有人可以给出有关内容类型如何工作以及如何实现的实际示例。我评论过的几乎所有教程(大部分在博客上)都无法真正涵盖这个概念。他们似乎从Django文档遗漏的地方接手(似乎无处可去)。 问题答案: 因

  • 问题内容: 虽然我确实了解泛型的一些特殊情况,但以下示例缺少一些内容。 我有以下课程 第4行给我错误 显然,编译器认为差异实际上并不相等。虽然我的直觉告诉我,这是正确的。 谁能提供一个示例,如果第4行合法,我会遇到运行时错误? 编辑: 为避免混淆,我将第3行中的in 替换为一个具体的作业 问题答案: 正如肯尼(Kenny)在评论中指出的那样,您可以通过以下方法解决此问题: 这立即告诉我们,该操作并

  • 问题内容: 我正在研究Spring Core认证,我对Spring如何处理bean的生命周期,尤其是bean后处理器有疑问。 所以我有这个架构: 我很清楚这是什么意思: 在“ 装入Bean定义”阶段执行以下步骤: @Configuration类被处理和/或@Components被扫描和/或XML文件进行解析。 Bean定义已添加到BeanFactory(每个索引都在其ID下建立索引) 调用特殊的B

  • 问题内容: 注释如何工作? 如果我有这样的事情: 将如何影响testNumber?它甚至会影响testNumber吗? 谢谢。让我知道我是否使用错了。 问题答案: 不会影响电话号码。它仅用于制作javadocs。 有关Javadoc的更多信息:http : //www.oracle.com/technetwork/java/javase/documentation/index-137868.htm

  • 我们知道主存域很少:年轻的、终生的(旧的gen)和PermGen。 年轻领域分为伊甸园和幸存者(有两个)。 OldGen用于生存的对象。 MaxTenuringThreshold防止对象过早地被最终复制到OldGen空间。这很清楚,也很容易理解。 但是它是如何工作的呢?垃圾回收器如何处理这些在MaxTenuringThreshold之前仍然存在的对象,以何种方式?它们位于何处? 对象被复制回幸存者

  • 问题内容: 我一直在阅读其手册页,但尚未成功弄清其工作原理。在调用system()时,是否分叉了一个新的子进程,并在其中添加了shell二进制文件exec()?但这可能是一个愚蠢的猜测。 问题答案: 是的,system()本质上是传递的命令字符串的fork()和exec()“ sh -c”。可以在此处找到示例实现(来自eglibc,最近来自glibc)。