有人问下面这段代码里,main()方法里的outer.new Inner()部分为什么会生成了一个对outer.getClass()的调用:
public class Outer {
public class Inner { }
public static void main(String[] args) {
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();
}
}
javac编译它生成的main方法的代码是:
public static void main(java.lang.String[]);
Code:
Stack=4, Locals=3, Args_size=1
0: new #2; //class Outer
3: dup
4: invokespecial #3; //Method "<init>":()V
7: astore_1
8: new #4; //class Outer$Inner
11: dup
12: aload_1
13: dup
14: invokevirtual #5; //Method java/lang/Object.getClass:()Ljava/lang/Class;
17: pop
18: invokespecial #6; //Method Outer$Inner."<init>":(LOuter;)V
21: astore_2
22: return
LineNumberTable:
line 4: 0
line 5: 8
line 6: 22
其中,对应outer.new Inner()的部分是:
[code] 8: new #4; //class Outer$Inner
11: dup
12: aload_1
13: dup
14: invokevirtual #5; //Method java/lang/Object.getClass:()Ljava/lang/Class;
17: pop
18: invokespecial #6; //Method Outer$Inner."<init>":(LOuter;)V[/code]
可以看到里面有一处对outer.getClass()的调用,然而得到的结果却马上被pop指令抛弃掉了。
这个问题通过调试javac很容易解决。调试javac的方法可以参考[url=http://hllvm.group.iteye.com/group/topic/24553]以前一帖[/url]。
设置好调试环境后,在调试器里把上面的代码交给javac去编译。
简单猜测就可以知道,生成的对outer.getClass()方法的调用是在最终生成代码的时候才做的,而不是在更早阶段被解除的语法糖,所以我们要注意的目标就是com.sun.tools.javac.jvm.Gen类,其中的visitNewClass(JCNewClass tree)方法。
在这个方法设上断点,然后开始调试。
第一次碰到断点会是main()方法里的new Outer(),这个跳过。
然后第二次进来的时候,观察调试器的变量窗口,可以看到:
[img]http://dl.iteye.com/upload/attachment/498417/c862cbdd-e4b5-3a8b-a449-467ba3f68ab6.png[/img]
从javac的角度来看,源码里的
outer.new Inner()
被改写成了这种形式:
[code]new Outer$Inner(outer<*nullchk*>)[/code]
(内部类被改名和改写为顶层类、隐式的外部类参数改写为显式参数)
接下来,有趣的点就是那个<*nullchk*>注释。
传给Inner()构造器的实际参数并不是原本的outer局部变量,而是outer局部变量外加一个空指针检查——要的值还是outer的值,不过如果outer为null的话,这里要抛出NullPointerException。
从javac的角度看,直接读outer局部变量可以用一个JCTree.JCIdent节点来表示,而这里则多包装了一个tag为JCTree.NULLCHK的JCTree.JCUnary节点。
正是在生成这个outer<*nullchk*>节点的代码时,会执行到Gen.visitUnary()的下述部分:
public void visitUnary(JCUnary tree) {
// ...
Item od = genExpr(tree.arg, operator.type.getParameterTypes().head);
switch (tree.tag) {
// ...
case JCTree.NULLCHK:
result = od.load();
code.emitop0(dup);
genNullCheck(tree.pos());
break;
}
}
其中的genNullCheck()是:
/** Generate a null check from the object value at stack top. */
private void genNullCheck(DiagnosticPosition pos) {
callMethod(pos, syms.objectType, names.getClass,
List.<Type>nil(), false);
code.emitop0(pop);
}
也就是说那个对getClass()的调用只不过是借invokevirtual指令来帮忙做null检查而已。getClass()本身得到的值其实是没用到的。
这个行为在Java语言规范里有相应的规定。在Java语言规范第三版,
15.9.2 Determining Enclosing Instances
该小节规定了应该使用什么对象作为外部类的实例
15.9.3 Choosing the Constructor and its Arguments
该小节规定了外部类实例在参数列表中的位置
15.9.4 Run-time Evaluation of Class Instance Creation Expressions
该小节规定了上面提到的空指针检查的行为:
[quote="Java Language Specification, 3rd Edition"]At run time, evaluation of a class instance creation expression is as follows.
First, if the class instance creation expression is a qualified class instance creation expression, the qualifying primary expression is evaluated. If the qualifying expression evaluates to [b]null[/b], a [b]NullPointerException[/b] is raised, and the class instance creation expression completes abruptly. If the qualifying expression completes abruptly, the class instance creation expression completes abruptly for the same reason.[/quote]
规范里明确了“要抛出NullPointerException”的行为,至于是如何实现null检查的则没规定,可以由实现自由发挥。用普通的if...else来做这个检查当然也可以,只不过生成的字节码就比调用getClass()的办法更长一些。
====================================================================
无独有偶,ECJ(Eclipse Compiler for Java)在编译这种代码的时候同样会生成对getClass()方法的调用:
public static void main(java.lang.String[]);
Code:
Stack=3, Locals=2, Args_size=1
0: new #1; //class Outer
3: dup
4: invokespecial #13; //Method "<init>":()V
7: astore_1
8: new #14; //class Outer$Inner
11: aload_1
12: dup
13: invokevirtual #16; //Method java/lang/Object.getClass:()Ljava/lang/Class;
16: pop
17: invokespeci8al #20; //Method Outer$Inner."<init>":(LOuter;)V
20: return
LineNumberTable:
line 4: 0
line 5: 8
line 6: 20
这当然不是偶然,因为getClass()方法是在Object上声明的(因此所有对象上必然存在),而且是final的(保证了它有确定的行为),而且运行开销比较低。
同样是Object上声明的方法,toString()、hashCode()之类其实也可以用,但它们都不是final的,有潜在可能性会引发较大的运行开销;这么分析一圈下来,Object上最好用的就剩下getClass()了。