Java Cast会引入开销吗? 为什么?
当我们将一种types的对象投射到另一种types时,是否有任何开销 或者编译器只是解决了一切,运行时没有成本?
这是一个普通的事情,还是有不同的情况?
例如,假设我们有一个Object []的数组,其中每个元素可能具有不同的types。 但是我们总是知道,比如说元素0是一个Double,元素1是一个String。 (我知道这是一个错误的devise,但让我们假设我必须这样做。)
Java的types信息在运行时是否仍然存在? 或者编译后忘了所有的东西,如果我们做了(Double)元素[0],我们只要按照指针将这8个字节解释为一个双精度,不pipe是什么?
我很不清楚在Java中如何完成types。 如果您对书籍或文章有任何推荐,那么也要感谢。
有两种types的铸造:
隐式投射,当你从一个types投射到一个更宽的types,这是自动完成,没有开销:
String s = "Cast"; Object o = s; // implicit casting
明确的投射,当你从一个更宽的types到更窄的一个。 对于这种情况,你必须明确地使用像这样的铸造:
Object o = someObject; String s = (String) o; // explicit casting
在第二种情况下,运行时会有开销,因为必须检查这两种types,并且在转换不可行的情况下,JVM必须抛出ClassCastException。
采取从JavaWorld:铸造的费用
铸造用于在types之间进行转换 – 特别是在引用types之间进行转换,用于我们感兴趣的铸造操作的types。
Upcast操作(也称为Java语言规范中的扩展转换)将子类引用转换为祖先类引用。 这个铸造操作通常是自动的,因为它总是安全的,并且可以由编译器直接实现。
向下转换操作(也称为Java语言规范中的缩小转换)将祖先类引用转换为子类引用。 这种投射操作会产生执行开销,因为Java需要在运行时检查投射以确保其有效性。 如果引用的对象不是该types的目标types或该types的子类的实例,则不允许尝试转换,并且必须抛出java.lang.ClassCastException。
为了合理实现Java:
每个对象都有一个头,其中包含一个指向运行时types的指针(例如Double
或String
,但不能是CharSequence
或AbstractList
)。 假设运行时编译器(通常是Sun的HotSpot)不能静态地确定types,一些检查需要由生成的机器代码执行。
首先需要读取运行时types的指针。 无论如何,这在调用类似情况下的虚拟方法时是必要的。
为了转换为一个类types,直到遇到java.lang.Object
时才知道有多less个超类,所以types可以从types指针(实际上是HotSpot中的前八个)的一个常量偏移处读取。 这又类似于读取虚拟方法的方法指针。
然后读取值只需要比较预期的演员的静态types。 根据指令集体系结构的不同,另一条指令需要在不正确的分支上分支(或错误)。 像32位ARM这样的ISA有条件指令,可能会让悲伤的path通过快乐的path。
由于接口的多重inheritance,接口更加困难。 一般来说,最后两次强制转换为接口将被caching在运行时types中。 在很早的时候(十多年前),接口有点慢,但是不再相关。
希望你能看到,这种事情在很大程度上与绩效无关。 你的源代码更重要。 在性能方面,在你的场景中最大的打击是caching追逐对象指针的地方(types信息当然是常见的)。
例如,假设我们有一个Object []的数组,其中每个元素可能具有不同的types。 但是我们总是知道,比如说元素0是一个Double,元素1是一个String。 (我知道这是一个错误的devise,但让我们假设我必须这样做。)
编译器不会注意到数组中各个元素的types。 它只是检查每个元素expression式的types是否可以分配给数组元素types。
Java的types信息在运行时是否仍然存在? 或者编译后忘了所有的东西,如果我们做了(Double)元素[0],我们只要按照指针将这8个字节解释为一个双精度,不pipe是什么?
运行时会保留一些信息,但不是单个元素的静态types。 您可以通过查看类文件格式来说明这一点。
从理论上讲,JIT编译器可以使用“逃逸分析”来消除某些作业中不必要的types检查。 但是,这样做的程度,你所build议的将超出现实优化的界限。 分析单个元素types的收益太小了。
此外,人们不应该像这样写应用程序代码。
在运行时执行转换的字节码指令称为checkcast
。 您可以使用javap
反汇编Java代码来查看生成的指令。
对于数组,Java在运行时保持types信息。 大多数情况下,编译器会为您捕获types错误,但是在尝试将对象存储在数组中时却会遇到ArrayStoreException
,但types不匹配(并且编译器没有捕获它)。 Java语言规范给出了以下示例:
class Point { int x, y; } class ColoredPoint extends Point { int color; } class Test { public static void main(String[] args) { ColoredPoint[] cpa = new ColoredPoint[10]; Point[] pa = cpa; System.out.println(pa[1] == null); try { pa[0] = new Point(); } catch (ArrayStoreException e) { System.out.println(e); } } }
Point[] pa = cpa
是有效的,因为ColoredPoint
是Point的一个子类,但是pa[0] = new Point()
是无效的。
这与通用types相反,在运行时没有保存types信息。 编译器在必要时插入checkcast
指令。
genericstypes和数组的types差异使得数组和genericstypes混合在一起是不合适的。
理论上,有开销介绍。 不过,现代的JVM很聪明。 每个实现都是不同的,但假设可能存在一个实现,即JIT优化了可以保证不会发生冲突的时间检查,这并不是不合理的。 至于哪些特定的JVM提供这个,我不能告诉你。 我必须承认我想知道JIT优化的具体细节,但是这些是JVM工程师担心的。
故事的寓意是首先编写可理解的代码。 如果您遇到速度减慢,configuration文件并确定您的问题。 赔率是好的,这不会是由于铸造。 不要牺牲干净,安全的代码,试图优化它,直到你知道你需要。