对于一些长表达式,我们总会想当然认为它们会这样执行,返回这样的结果,可编译器会这样想当然么?请看下面一个例子:
package com.gauss.study;
/**
* 测试对长表达式,JVM的执行顺序。
* @author Gauss.Deng
*
*/
public class LongExpression {
/**
* @param args
*/
public static void main(String[] args) {
getX();
}
public static void getX(){
Double min = 1000.0;
Double max = 2000.0;
Object x = min==null?0:min.intValue() + "-" + max==null?0:max.intValue();
System.out.println("x == "+x.toString());
}
}
运行上面程序,控制台会输出什么?会是如我们期望的的“1000-2000”么?不会,事实上它永远输出“2000”。这是为什么呢?也许发现了问题,getX方法内部对x赋值那行语句出了问题,可是出了啥问题呢?既然代码层面看不出那么清晰的道理,让我们走到代码下面去:
还是运用javap工具,对源代码进行反编译,一探源码编译后的执行细节。在控制台中执行命令:javap LongExpression,可以看到输出结果:
C:\>javap -verbose LongExpression
Compiled from "LongExpression.java"
public class com.gauss.study.LongExpression extends java.lang.Object
SourceFile: "LongExpression.java"
minor version: 0
major version: 50
Constant pool:
const #1 = Method #22.#34; // java/lang/Object."<init>":()V
const #2 = Method #21.#35; // com/gauss/study/LongExpression.getX:()V
const #3 = double 1000.0d;
const #5 = Method #36.#37; // java/lang/Double.valueOf:(D)Ljava/lang/Double;
const #6 = double 2000.0d;
const #8 = class #38; // java/lang/StringBuilder
const #9 = Method #8.#34; // java/lang/StringBuilder."<init>":()V
const #10 = Method #36.#39; // java/lang/Double.intValue:()I
const #11 = Method #8.#40; // java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
const #12 = String #41; // -
const #13 = Method #8.#42; // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
const #14 = Method #8.#43; // java/lang/StringBuilder.append:(Ljava/lang/Object;)Ljava/lang/StringBuilder;
const #15 = Method #8.#44; // java/lang/StringBuilder.toString:()Ljava/lang/String;
const #16 = Method #45.#46; // java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
const #17 = Field #47.#48; // java/lang/System.out:Ljava/io/PrintStream;
const #18 = String #49; // x ==
const #19 = Method #22.#44; // java/lang/Object.toString:()Ljava/lang/String;
const #20 = Method #50.#51; // java/io/PrintStream.println:(Ljava/lang/String;)V
const #21 = class #52; // com/gauss/study/LongExpression
const #22 = class #53; // java/lang/Object
const #23 = Asciz <init>;
const #24 = Asciz ()V;
const #25 = Asciz Code;
const #26 = Asciz LineNumberTable;
const #27 = Asciz main;
const #28 = Asciz ([Ljava/lang/String;)V;
const #29 = Asciz getX;
const #30 = Asciz StackMapTable;
const #31 = class #54; // java/lang/Double
const #32 = Asciz SourceFile;
const #33 = Asciz LongExpression.java;
const #34 = NameAndType #23:#24;// "<init>":()V
const #35 = NameAndType #29:#24;// getX:()V
const #36 = class #54; // java/lang/Double
const #37 = NameAndType #55:#56;// valueOf:(D)Ljava/lang/Double;
const #38 = Asciz java/lang/StringBuilder;
const #39 = NameAndType #57:#58;// intValue:()I
const #40 = NameAndType #59:#60;// append:(I)Ljava/lang/StringBuilder;
const #41 = Asciz -;
const #42 = NameAndType #59:#61;// append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
const #43 = NameAndType #59:#62;// append:(Ljava/lang/Object;)Ljava/lang/StringBuilder;
const #44 = NameAndType #63:#64;// toString:()Ljava/lang/String;
const #45 = class #65; // java/lang/Integer
const #46 = NameAndType #55:#66;// valueOf:(I)Ljava/lang/Integer;
const #47 = class #67; // java/lang/System
const #48 = NameAndType #68:#69;// out:Ljava/io/PrintStream;
const #49 = Asciz x == ;
const #50 = class #70; // java/io/PrintStream
const #51 = NameAndType #71:#72;// println:(Ljava/lang/String;)V
const #52 = Asciz com/gauss/study/LongExpression;
const #53 = Asciz java/lang/Object;
const #54 = Asciz java/lang/Double;
const #55 = Asciz valueOf;
const #56 = Asciz (D)Ljava/lang/Double;;
const #57 = Asciz intValue;
const #58 = Asciz ()I;
const #59 = Asciz append;
const #60 = Asciz (I)Ljava/lang/StringBuilder;;
const #61 = Asciz (Ljava/lang/String;)Ljava/lang/StringBuilder;;
const #62 = Asciz (Ljava/lang/Object;)Ljava/lang/StringBuilder;;
const #63 = Asciz toString;
const #64 = Asciz ()Ljava/lang/String;;
const #65 = Asciz java/lang/Integer;
const #66 = Asciz (I)Ljava/lang/Integer;;
const #67 = Asciz java/lang/System;
const #68 = Asciz out;
const #69 = Asciz Ljava/io/PrintStream;;
const #70 = Asciz java/io/PrintStream;
const #71 = Asciz println;
const #72 = Asciz (Ljava/lang/String;)V;
{
public com.gauss.study.LongExpression();
Code:
Stack=1, Locals=1, Args_size=1
0: aload_0
1: invokespecial #1; //Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 8: 0
public static void main(java.lang.String[]);
Code:
Stack=0, Locals=1, Args_size=1
0: invokestatic #2; //Method getX:()V
3: return
LineNumberTable:
line 14: 0
line 15: 3
public static void getX();
Code:
Stack=3, Locals=3, Args_size=0
0: ldc2_w #3; //double 1000.0d
3: invokestatic #5; //Method java/lang/Double.valueOf:(D)Ljava/lang/Double;
6: astore_0
7: ldc2_w #6; //double 2000.0d
10: invokestatic #5; //Method java/lang/Double.valueOf:(D)Ljava/lang/Double;
13: astore_1
14: aload_0
15: ifnonnull 22 /*此处判断栈顶操作数如果为非null,则调到22行*/
18: iconst_0
19: goto 59
22: new #8; //class java/lang/StringBuilder /*新建一个对象,对象类型从第8号常量池中取得*/
25: dup
26: invokespecial #9; //Method java/lang/StringBuilder."<init>":()V
29: aload_0
30: invokevirtual #10; //Method java/lang/Double.intValue:()I
33: invokevirtual #11; //Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder; /*对于上面创建的对象执行追加操作*/
36: ldc #12; //String - /*把12号常量池信息压入栈顶,此处对应源码中的“-”*/
38: invokevirtual #13; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
41: aload_1
42: invokevirtual #14; //Method java/lang/StringBuilder.append:(Ljava/lang/Object;)Ljava/lang/StringBuilder;
45: invokevirtual #15; //Method java/lang/StringBuilder.toString:()Ljava/lang/String; /*这里非常关键,已经完全了与“-”连成串*/
48: ifnonnull 55 /*判断栈顶值是否为null,此处现在不为null,而是“1000-”,所以跳转到55行*/
51: iconst_0
52: goto 59
55: aload_1 /*将制定的引用类型本地变量推送至栈顶:此处显然是max变量*/
56: invokevirtual #10; //Method java/lang/Double.intValue:()I /*调用实例方法,此处显然是取得max.intValue()也即为2000*/
59: invokestatic #16; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
62: astore_2
63: getstatic #17; //Field java/lang/System.out:Ljava/io/PrintStream;
66: new #8; //class java/lang/StringBuilder
69: dup
70: invokespecial #9; //Method java/lang/StringBuilder."<init>":()V /*此处已经确定x的值为2000了,下面是接下来的打印操作。*/
73: ldc #18; //String x ==
75: invokevirtual #13; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
78: aload_2
79: invokevirtual #19; //Method java/lang/Object.toString:()Ljava/lang/String;
82: invokevirtual #13; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
85: invokevirtual #15; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
88: invokevirtual #20; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
91: return
LineNumberTable:
line 18: 0
line 19: 7
line 20: 14
line 21: 63
line 22: 91
StackMapTable: number_of_entries = 3
frame_type = 253 /* append */
offset_delta = 22
locals = [ class java/lang/Double, class java/lang/Double ]
frame_type = 32 /* same */
frame_type = 67 /* same_locals_1_stack_item */
stack = [ int ]
}
细看上面反编译的代码,谜底终于揭开:编译器并没有按照我们第一眼相当然的执行顺序:先去min的整型值,再取max的整型值,最终用“-”把他们连接起来,组成一个String赋值给x。
真正的执行顺序是:
首先执行:
min==null?0:min.intValue()
结果是1000,接着执行:
1000 + "-" + max
执行结果是:"1000-2000.0"
再用这个结果与null比较,显然非null,所以接下来执行:
max.intValue()
把这个结果赋值给x,在运行期间x的运行期类型是int,值为2000.
到此也就很清楚,程序运行的结果最后为何为2000了。
如果要想程序按照我们相当然的返回结果,只需在对x的赋值语句中做小改动(也许你已经知道了),如下:
Object x = (min==null?0:min.intValue()) + "-" + (max==null?0:max.intValue());
而你发现了哪里改动了么?
总结:
通过上面分析,我们至少有两个经验:
第一:不要想当然编译器会按照我们期望的方式执行语句,事实上由于编译器要优化代码,往往有自己的执行顺序。
第二:当你的不确定编译器会如何编译执行时,请你显示指定语句的执行顺序。