Java 转换是否会引入开销?为什么?

2022-08-31 09:15:25

当我们将一种类型的对象转换为另一种类型的对象时,是否有任何开销?或者编译器只是解决所有问题,并且在运行时没有成本?

这是一般的事情,还是有不同的情况?

例如,假设我们有一个 Object[] 数组,其中每个元素可能具有不同的类型。但是我们总是可以肯定地知道,比如说,元素0是双精度,元素1是字符串。(我知道这是一个错误的设计,但让我们假设我必须这样做。

Java的类型信息在运行时是否仍然保留?或者编译后所有内容都被遗忘了,如果我们执行(Double)elements[0],我们只会跟随指针并将这8个字节解释为双精度,无论那是什么?

我非常不清楚类型是如何在Java中完成的。如果您对书籍或文章有任何建议,那么也谢谢。


答案 1

有 2 种类型的铸件:

隐式强制转换,当您从类型强制转换为更宽的类型时,这是自动完成的,并且没有开销:

String s = "Cast";
Object o = s; // implicit casting

显式转换,当您从较宽的类型变为较窄的类型时。对于这种情况,您必须显式使用如下的强制转换:

Object o = someObject;
String s = (String) o; // explicit casting

在第二种情况下,运行时中存在开销,因为必须检查这两种类型,并且在强制转换不可行的情况下,JVM 必须抛出一个 ClassCastException。

摘自 JavaWorld:铸造成本

强制转换用于在类型之间进行转换 - 特别是在引用类型之间,对于我们在这里感兴趣的强制转换操作的类型。

上行操作(在 Java 语言规范中也称为加宽转换)将子类引用转换为祖先类引用。此强制转换操作通常是自动的,因为它始终是安全的,并且可以由编译器直接实现。

Downcast 操作(在 Java 语言规范中也称为缩小转换)将祖先类引用转换为子类引用。此强制转换操作会产生执行开销,因为 Java 要求在运行时检查强制转换以确保其有效。如果引用的对象既不是强制转换的目标类型的实例,也不是该类型的子类的实例,则不允许尝试强制转换,并且必须引发 java.lang.ClassCastException。


答案 2

对于Java的合理实现:

每个对象都有一个标头,其中包含指向运行时类型的指针(例如 or ,但它永远不可能是 or )。假设运行时编译器(在Sun的情况下通常是HotSpot)不能静态地确定类型,那么生成的机器代码需要执行一些检查。DoubleStringCharSequenceAbstractList

首先,需要读取指向运行时类型的指针。无论如何,这对于在类似情况下调用虚拟方法都是必需的。

对于类类型的强制转换,在命中之前确切地知道有多少个超类,因此可以在从类型指针(实际上是HotSpot中的前八个)的恒定偏移处读取该类型。同样,这类似于读取虚拟方法的方法指针。java.lang.Object

然后,读取值只需要与强制转换的预期静态类型进行比较。根据指令集架构,另一条指令需要在不正确的分支上分支(或故障)。诸如32位ARM之类的ISA具有条件指令,并且可能能够使悲伤的路径通过快乐的路径。

由于接口的多重继承,接口更加困难。通常,接口的最后两个强制转换缓存在运行时类型中。在早期(十多年前),接口有点慢,但这不再重要。

希望你能看到这种事情在很大程度上与性能无关。您的源代码更重要。就性能而言,在您的方案中,最大的打击可能是由于到处追逐对象指针而导致的缓存未命中(类型信息当然会很常见)。


推荐