据我了解,如果可以在编译期间进行类型检查,则类型转换将在编译期间完成,不会产生任何运行时开销。
例如
public Child getChild() {
Parent o = new Child();
return (Child) o;
}
类型转换是在编译期间还是在运行时完成的?
是否有任何一般规则来决定类型转换是由 javac 编译器还是由 VM 完成的?
实际上,这种情况有三种可能:
javac
可以执行优化。我希望它是选项 1. 或 2. 但这可能是特定于平台的。
事实上,在我的系统上,字节码并没有优化。如果要进行任何优化,它将由 JIT 编译器来完成。(这符合我所听到的……大多数 Java 字节码编译器在生成字节码之前几乎没有做任何优化。)
Compiled from "Test.java"
public class Test extends java.lang.Object{
public Test();
Code:
0: aload_0
1: invokespecial #8; //Method java/lang/Object."<init>":()V
4: return
public Child getChild();
Code:
0: new #16; //class Child
3: dup
4: invokespecial #18; //Method Child."<init>":()V
7: astore_1
8: aload_1
9: checkcast #16; //class Child
12: areturn
}
当正在运行的程序尝试将对象引用转换为另一种类型时,虚拟机必须检查被转换为的类型是被引用对象的实际类还是它的超类型之一。当程序执行 instanceof 操作时,它必须执行相同类型的检查。
在任何一种情况下,虚拟机都必须查看被引用对象的类数据。当程序调用实例方法时,虚拟机必须执行动态绑定:它必须选择调用的方法不是基于引用的类型,而是基于对象的类。为此,它必须再次访问仅给定对象引用的类数据。
编辑:
Java 编译器不负责检查转换是否正确,就像某些绑定只在运行时发生一样。Java 虚拟机在运行时进行检查以确定实际引用对象是否是新类型的合法对象。如果没有,就会出现运行时异常:ClassCastException。
当我编译
public class Test {
public Child getChildVersion1() {
Parent o = new Child();
return (Child) o;
}
public Child getChildVersion2() {
return new Child();
}
}
javap -c Test
并使用Java 7(Windows 7 64bit)反编译该代码,它给了我这个结果
Compiled from "Test.java"
public class Test {
public Test();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public Child getChildVersion1();
Code:
0: new #2 // class Child
3: dup
4: invokespecial #3 // Method Child."<init>":()V
7: astore_1
8: aload_1
9: checkcast #2 // class Child
12: areturn
public Child getChildVersion2();
Code:
0: new #2 // class Child
3: dup
4: invokespecial #3 // Method Child."<init>":()V
7: areturn
}
因此,编译器似乎没有getChildVersion1
像getChildVersion2
这样优化方法,除了在编译时检查类型之外,还在运行时检查(9: checkcast #2
)。但正如 Stephen C提到的,它可能与平台(操作系统、Java 版本)有关。
对于不需要在运行时进行测试的转换,编译器可能会进行一些优化以避免在运行时进行强制转换。
我建议阅读JLS 第 5 章。转换和促销以了解更多关于需要在运行时进行测试的转换类型。
示例 5.0-1。编译时和运行时的转换
A conversion from type Object to type Thread requires a run-time check to make sure that the run-time value is actually an instance of class Thread or one of its subclasses; if it is not, an exception is thrown.
A conversion from type Thread to type Object requires no run-time action; Thread is a subclass of Object, so any reference produced by an expression of type Thread is a valid reference value of type Object.
A conversion from type int to type long requires run-time sign-extension of a 32-bit integer value to the 64-bit long representation. No information is lost.
A conversion from type double to type long requires a nontrivial translation from a 64-bit floating-point value to the 64-bit integer representation. Depending on the actual run-time value, information may be lost.
这种转换需要在运行时进行测试,以确定实际引用值是否是新类型的合法值。如果不是,则抛出 ClassCastException。
5.1.8。拆箱转换;转换在运行时进行。
确定转换发生的时间并不容易,例如:
public class Main {
private static class Child extends Parent{
public Child() {
}
}
private static class Parent {
public Parent() {
}
}
private static Child getChild() {
Parent o = new Child();
return (Child) o;
}
public static void main(final String[] args) {
Child c = getChild();
}
}
给出的结果javap -c Main
是:
public class Main extends java.lang.Object{
public Main();
Code:
0: aload_0
1: invokespecial #1; //Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: invokestatic #4; //Method getChild:()LMain$Child;
3: astore_1
4: return
}
如果将方法声明更改public static Child getChild()
为结果为:
public class Main extends java.lang.Object{
public Main();
Code:
0: aload_0
1: invokespecial #1; //Method java/lang/Object."<init>":()V
4: return
public static Main$Child getChild();
Code:
0: new #2; //class Main$Child
3: dup
4: invokespecial #3; //Method Main$Child."<init>":()V
7: astore_0
8: aload_0
9: checkcast #2; //class Main$Child
12: areturn
public static void main(java.lang.String[]);
Code:
0: invokestatic #4; //Method getChild:()LMain$Child;
3: astore_1
4: return
}
您会看到,仅更改访问器就会对可能的优化产生很大影响。
我猜它在两个阶段都完成了。在编译时,编译器将强制您进行正确的强制转换,以确保您没有混淆类型,就像在任何强类型语言中一样。
但是,如果你将一个Object
你得到的参数转换为String
(这将适用于实际上是的对象instanceof
String
),JVM 仍然必须确保实现类Object
真正扩展或是,如果它不是String
,你将得到一个ClassCastException
吨。