Java构建会引入开销吗?为什么?

Phi*_*hil 95 java performance casting static-typing

当我们将一种类型的对象转换为另一种类型时会有任何开销吗?或者编译器只解析所有内容并且在运行时没有成本?

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

例如,假设我们有一个Object []数组,其中每个元素可能有不同的类型.但我们总是知道,例如,元素0是一个Double,元素1是一个String.(我知道这是一个错误的设计,但我们假设我必须这样做.)

Java的类型信息是否仍然在运行时保留?或者编译后一切都被遗忘了,如果我们做(Double)元素[0],我们只需跟随指针并将这8个字节解释为double,不管是什么?

我不太清楚Java中的类型是如何完成的.如果您对书籍或文章有任何建议,那么也要感谢.

Ale*_*lex 72

铸造有两种类型:

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

String s = "Cast";
Object o = s; // implicit casting
Run Code Online (Sandbox Code Playgroud)

显式铸造,当你从更宽的类型转向更窄的类型.对于这种情况,您必须明确使用类似的转换:

Object o = someObject;
String s = (String) o; // explicit casting
Run Code Online (Sandbox Code Playgroud)

在第二种情况下,运行时会产生开销,因为必须检查这两种类型,并且如果转换不可行,JVM必须抛出ClassCastException.

摘自JavaWorld:铸造成本

Casting用于在类型之间进行转换 - 特别是在引用类型之间,对于我们感兴趣的转换操作类型.

上传操作(在Java语言规范中也称为扩展转换)将子类引用转换为祖先类引用.这种转换操作通常是自动的,因为它总是安全的并且可以由编译器直接实现.

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

  • 这篇JavaWorld文章已经有10多年的历史了,所以我会用你最好的盐来表达它对性能的任何陈述. (90认同)
  • 现在这篇文章快20岁了。答案也有很多年了。这个问题需要一个现代的答案。 (5认同)

Tom*_*ine 43

对于Java的合理实现:

每个对象都有一个标题,其中包含一个指向运行时类型的指针(例如,Double或者String它永远不会是CharSequence或者AbstractList).假设运行时编译器(在Sun的情况下通常是HotSpot)无法静态地确定类型,则需要由生成的机器代码执行某些检查.

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

为了转换为类类型,确切地知道有多少个超类,直到你命中java.lang.Object,因此可以在类型指针的常量偏移处读取类型(实际上是HotSpot中的前八个).这类似于读取虚拟方法的方法指针.

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

由于接口的多重继承,接口更加困难.通常,接口的最后两个强制类型转换为运行时类型.在很早的时候(十多年前),接口有点慢,但这已经不再适用了.

希望您可以看到这种事情与性能无关.您的源代码更重要.在性能方面,你的场景中最大的打击可能是在整个地方追逐对象指针的缓存未命中(类型信息当然是常见的).


Ste*_*n C 7

例如,假设我们有一个Object []数组,其中每个元素可能有不同的类型.但我们总是知道,例如,元素0是一个Double,元素1是一个String.(我知道这是一个错误的设计,但我们假设我必须这样做.)

编译器不会记录数组的各个元素的类型.它只是检查每个元素表达式的类型是否可分配给数组元素类型.

Java的类型信息是否仍然在运行时保留?或者编译后一切都被遗忘了,如果我们做(Double)元素[0],我们只需跟随指针并将这8个字节解释为double,不管是什么?

某些信息在运行时保留,但不是单个元素的静态类型.您可以通过查看类文件格式来判断这一点.

理论上,JIT编译器可以使用"转义分析"来消除某些赋值中不必要的类型检查.但是,按照您建议的程度执行此操作将超出实际优化的范围.分析单个元素类型的收益太小.

此外,人们不应该编写类似的应用程序代码.

  • 一些原始类型的转换会产生开销。它是否重要取决于上下文。 (2认同)

Jes*_*erE 5

调用用于在运行时执行转换的字节代码指令checkcast.您可以使用反汇编Java代码javap来查看生成的指令.

对于数组,Java在运行时保留类型信息.大多数情况下,编译器会为您捕获类型错误,但是在某些情况下,当您ArrayStoreException尝试将对象存储在数组中但是类型不匹配(并且编译器没有捕获它)时,您会遇到错误.在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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

Point[] pa = cpa是有效的,因为它ColoredPoint是Point的子类,但pa[0] = new Point()无效.

这与泛型类型相反,泛型类型在运行时没有保留类型信息.编译器checkcast在必要时插入指令.

泛型类型和数组的类型差异使得它通常不适合混合数组和泛型类型.