Java Compiler不会想当然

对于一些长表达式,我们总会想当然认为它们会这样执行,返回这样的结果,可编译器会这样想当然么?请看下面一个例子:

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());

而你发现了哪里改动了么?


总结:

通过上面分析,我们至少有两个经验:

第一:不要想当然编译器会按照我们期望的方式执行语句,事实上由于编译器要优化代码,往往有自己的执行顺序。

第二:当你的不确定编译器会如何编译执行时,请你显示指定语句的执行顺序。


阅读更多
个人分类: 技术钻研
想对作者说点什么? 我来说一句

JavaCC

2008年01月15日 2.76MB 下载

没有更多推荐了,返回首页

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭