Java 转换会引入开销吗?为什么?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/2170872/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Does Java casting introduce overhead? Why?
提问by Phil
Is there any overhead when we cast objects of one type to another? Or the compiler just resolves everything and there is no cost at run time?
当我们将一种类型的对象转换为另一种类型时,是否有任何开销?或者编译器只是解决所有问题而在运行时没有成本?
Is this a general things, or there are different cases?
这是一般的事情,还是有不同的情况?
For example, suppose we have an array of Object[], where each element might have a different type. But we always know for sure that, say, element 0 is a Double, element 1 is a String. (I know this is a wrong design, but let's just assume I had to do this.)
例如,假设我们有一个 Object[] 数组,其中每个元素可能有不同的类型。但是我们总是很确定,比如说,元素 0 是一个 Double,元素 1 是一个字符串。(我知道这是一个错误的设计,但让我们假设我必须这样做。)
Is Java's type information still kept around at run time? Or everything is forgotten after compilation, and if we do (Double)elements[0], we'll just follow the pointer and interpret those 8 bytes as a double, whatever that is?
Java 的类型信息在运行时是否仍然保留?或者在编译后一切都被遗忘了,如果我们做 (Double)elements[0],我们只会跟随指针并将这 8 个字节解释为双精度值,不管是什么?
I'm very unclear about how types are done in Java. If you have any reccommendation on books or article then thanks, too.
我不太清楚 Java 中的类型是如何完成的。如果您对书籍或文章有任何建议,也谢谢。
采纳答案by Alex Ntousias
There are 2 types of casting:
有两种类型的铸造:
Implicitcasting, when you cast from a type to a wider type, which is done automatically and there is no overhead:
隐式转换,当您从一个类型转换为更宽的类型时,这是自动完成的并且没有开销:
String s = "Cast";
Object o = s; // implicit casting
Explicitcasting, when you go from a wider type to a more narrow one. For this case, you must explicitly use casting like that:
显式转换,当您从较宽的类型转换为较窄的类型时。对于这种情况,您必须像这样显式使用强制转换:
Object o = someObject;
String s = (String) o; // explicit casting
In this second case, there is overhead in runtime, because the two types must be checked and in case that casting is not feasible, JVM must throw a ClassCastException.
在第二种情况下,运行时存在开销,因为必须检查这两种类型,并且在强制转换不可行的情况下,JVM 必须抛出 ClassCastException。
Taken from JavaWorld: The cost of casting
Castingis used to convert between types -- between reference types in particular, for the type of casting operation in which we're interested here.
Upcastoperations (also called widening conversions in the Java Language Specification) convert a subclass reference to an ancestor class reference. This casting operation is normally automatic, since it's always safe and can be implemented directly by the compiler.
Downcastoperations (also called narrowing conversions in the Java Language Specification) convert an ancestor class reference to a subclass reference. This casting operation creates execution overhead, since Java requires that the cast be checked at runtime to make sure that it's valid. If the referenced object is not an instance of either the target type for the cast or a subclass of that type, the attempted cast is not permitted and must throw a java.lang.ClassCastException.
转换用于类型之间的转换——特别是引用类型之间的转换,对于我们在这里感兴趣的转换操作的类型。
Upcast操作(在 Java 语言规范中也称为扩展转换)将子类引用转换为祖先类引用。这种转换操作通常是自动的,因为它总是安全的并且可以由编译器直接实现。
向下转换操作(在 Java 语言规范中也称为缩小转换)将祖先类引用转换为子类引用。此转换操作会产生执行开销,因为 Java 要求在运行时检查转换以确保其有效。如果被引用的对象不是转换的目标类型或该类型的子类的实例,则不允许尝试转换并且必须抛出 java.lang.ClassCastException。
回答by Stephen C
For example, suppose we have an array of Object[], where each element might have a different type. But we always know for sure that, say, element 0 is a Double, element 1 is a String. (I know this is a wrong design, but let's just assume I had to do this.)
例如,假设我们有一个 Object[] 数组,其中每个元素可能有不同的类型。但是我们总是很确定,比如说,元素 0 是一个 Double,元素 1 是一个字符串。(我知道这是一个错误的设计,但让我们假设我必须这样做。)
The compiler does not note the types of the individual elements of an array. It simply checks that the type of each element expression is assignable to the array element type.
编译器不会记录数组中各个元素的类型。它只是检查每个元素表达式的类型是否可分配给数组元素类型。
Is Java's type information still kept around at run time? Or everything is forgotten after compilation, and if we do (Double)elements[0], we'll just follow the pointer and interpret those 8 bytes as a double, whatever that is?
Java 的类型信息在运行时是否仍然保留?或者在编译后一切都被遗忘了,如果我们做 (Double)elements[0],我们只会跟随指针并将这 8 个字节解释为双精度值,不管是什么?
Some information is kept around at run time, but not the static types of the individual elements. You can tell this from looking at the class file format.
一些信息在运行时保留,但不是单个元素的静态类型。您可以通过查看类文件格式来判断这一点。
It is theoretically possible that the JIT compiler could use "escape analysis" to eliminate unnecessary type checks in some assignments. However, doing this to the degree you are suggesting would be beyond the bounds of realistic optimization. The payoff of analysing the types of individual elements would be too small.
理论上,JIT 编译器可以使用“转义分析”来消除某些赋值中不必要的类型检查。但是,按照您建议的程度执行此操作将超出实际优化的范围。分析单个元素的类型的收益太小。
Besides, people should not write application code like that anyway.
此外,人们无论如何都不应该编写这样的应用程序代码。
回答by JesperE
The byte code instruction for performing casting at runtime is called checkcast
. You can disassemble Java code using javap
to see what instructions are generated.
在运行时执行转换的字节码指令称为checkcast
。您可以使用反汇编 Java 代码javap
来查看生成了哪些指令。
For arrays, Java keeps type information at runtime. Most of the time, the compiler will catch type errors for you, but there are cases where you will run into an ArrayStoreException
when trying to store an object in an array, but the type does not match (and the compiler didn't catch it). The Java language specgives the following example:
对于数组,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);
}
}
}
Point[] pa = cpa
is valid since ColoredPoint
is a subclass of Point, but pa[0] = new Point()
is not valid.
Point[] pa = cpa
有效,因为它ColoredPoint
是 Point 的子类,但pa[0] = new Point()
无效。
This is opposed to generic types, where there is no type information kept at runtime. The compiler inserts checkcast
instructions where necessary.
这与泛型类型相反,泛型类型在运行时没有保留类型信息。编译器checkcast
在必要时插入指令。
This difference in typing for generic types and arrays makes it often unsuitable to mix arrays and generic types.
泛型类型和数组的类型差异使得通常不适合混合使用数组和泛型类型。
回答by Tom Hawtin - tackline
For a reasonable implementation of Java:
对于 Java 的合理实现:
Each object has a header containing, amongst other things, a pointer to the runtime type (for instance Double
or String
, but it could never be CharSequence
or AbstractList
). Assuming the runtime compiler (generally HotSpot in Sun's case) cannot determine the type statically a some checking needs to be performed by the generated machine code.
每个对象都有一个标头,其中包含一个指向运行时类型的指针(例如Double
or String
,但它永远不会是CharSequence
or AbstractList
)。假设运行时编译器(在 Sun 的情况下通常是 HotSpot)无法静态确定类型,因此生成的机器代码需要执行一些检查。
First that pointer to the runtime type needs to be read. This is necessary for calling a virtual method in a similar situation anyway.
首先需要读取指向运行时类型的指针。无论如何,这对于在类似情况下调用虚拟方法是必要的。
For casting to a class type, it is known exactly how many superclasses there are until you hit java.lang.Object
, so the type can be read at a constant offset from the type pointer (actually the first eight in HotSpot). Again this is analogous to reading a method pointer for a virtual method.
对于转换为类类型,在您点击 之前确切知道有多少个超类java.lang.Object
,因此可以从类型指针(实际上是 HotSpot 中的前八个)的恒定偏移量处读取该类型。同样,这类似于读取虚拟方法的方法指针。
Then the read value just needs a comparison to the expected static type of the cast. Depending upon instruction set architecture, another instruction will need to branch (or fault) on an incorrect branch. ISAs such as 32-bit ARM have conditional instruction and may be able to have the sad path pass through the happy path.
然后读取值只需要与强制转换的预期静态类型进行比较。根据指令集架构,另一条指令需要在不正确的分支上分支(或出错)。诸如 32 位 ARM 之类的 ISA 具有条件指令,并且可能能够让悲伤路径通过快乐路径。
Interfaces are more difficult due to multiple inheritance of interface. Generally the last two casts to interfaces are cached in the runtime type. IN the very early days (over a decade ago), interfaces were a bit slow, but that is no longer relevant.
由于接口的多重继承,接口更加困难。通常,对接口的最后两个强制转换缓存在运行时类型中。在早期(十多年前),接口有点慢,但这不再相关。
Hopefully you can see that this sort of thing is largely irrelevant to performance. Your source code is more important. In terms of performance, the biggest hit in your scenario is liable to be cache misses from chasing object pointers all over the place (the type information will of course be common).
希望你能看到这种事情在很大程度上与性能无关。你的源代码更重要。在性能方面,您的场景中最大的打击可能是由于到处追逐对象指针而导致的缓存未命中(类型信息当然很常见)。
回答by HesNotTheStig
In theory, there is overhead introduced. However, modern JVMs are smart. Each implementation is different, but it is not unreasonable to assume that there could exist an implementation that JIT optimized away casting checks when it could guarantee that there would never be a conflict. As for which specific JVMs offer this, I couldn't tell you. I must admit I'd like to know the specifics of JIT optimization myself, but these are for JVM engineers to worry about.
理论上,引入了开销。但是,现代 JVM 很智能。每个实现都是不同的,但假设可能存在一个实现,当它可以保证永远不会发生冲突时,JIT 优化了强制转换检查并不是没有道理的。至于哪个特定的 JVM 提供了这个,我不能告诉你。我必须承认我想自己了解 JIT 优化的细节,但这些是 JVM 工程师需要担心的。
The moral of the story is to write understandable code first. If you're experiencing slowdowns, profile and identify your problem. Odds are good that it won't be due to casting. Never sacrifice clean, safe code in an attempt to optimize it UNTIL YOU KNOW YOU NEED TO.
这个故事的寓意是首先编写可理解的代码。如果您遇到速度变慢的情况,请分析并确定您的问题。很可能不是因为铸造。永远不要为了优化代码而牺牲干净、安全的代码,直到您知道自己需要这样做。