Java Compiler不会想当然

原创 2011年12月07日 00:02:33

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

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

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


总结:

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

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

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


一个老程序员的忠告:不要一辈子靠技术生存

我现在是自己做,但我此前有多年在从事软件开发工作,当回过头来想一想自己,觉  得特别想对那些初学JAVA/DOT。NET技术的朋友说点心里话,希望你们能从我们的体  会中,多少受点启发(也许我说的...
  • oney139
  • oney139
  • 2014年10月24日 14:04
  • 8588

myeclipse10配置了jdk8,却用不了jdk8的新特性?

myeclipse开发时,保证编译时和运行时的jdk版本一致
  • qq_16568205
  • qq_16568205
  • 2016年11月19日 12:40
  • 6232

eclipse突然不自动提示了,几种情况如下所示

最近公司电脑上的Eclipse没有了自动提示功能,也不是全部不提示,大多数情况下按下“alt+/”键还会产生提示,但是当我在java项目中邪main方法和syso的时候,“alt+/”则会失效,今天在...
  • BrotherDong90
  • BrotherDong90
  • 2015年11月03日 11:37
  • 2825

Java compiler level does not match

从别的地方导入一个项目的时候,经常会遇到eclipse/Myeclipse报Description Resource Path Location Type Java compiler level d...
  • zouchao8952
  • zouchao8952
  • 2016年03月19日 13:25
  • 332

gradle复习(4)-Cannot find System Java Compiler

这个问题的产生原因是你没有在gradle配置中设置JDK。 1.首先在build.gradle右键点击,选择Run As中第二个选项[2.Gradle build....]进入gradle配置。 ...
  • qhshiniba
  • qhshiniba
  • 2015年01月21日 09:59
  • 5269

Java compiler level does not match修改jdk版本

从别的地方导入一个项目的时候,经常会遇到eclipse/Myeclipse报Description  Resource Path Location Type Java compiler level d...
  • TOP__ONE
  • TOP__ONE
  • 2017年08月07日 10:05
  • 180

Java compiler level does not match解决方法

Java compiler level does not match解决方法
  • fenqingxingpeiyue
  • fenqingxingpeiyue
  • 2016年08月08日 16:37
  • 134

Java Compiler 应用实例

一直在用JDK1.5, 一直搞不清楚JDK1.6有啥特性, 就翻了翻, 发现这个Compiler API(JSR 199)动态编译Java源文件功能很有意思. Compiler API如果和反射功能一...
  • hejiangtao
  • hejiangtao
  • 2012年02月09日 01:01
  • 2424

Java compiler level does not match the version of the installed Java 及MyEclipse中没有ProjectFacets

解决办法 在项目上右键Properties-》Project Facets,在打开的Project Facets页面中的Java下拉列表中,选择相应版本。 有可能是java1.6 改成java6之...
  • yuzongtao
  • yuzongtao
  • 2014年09月30日 14:13
  • 789

java compiler level does not match the version of the installed java project facet

解决方法1: 在资源管理器下,找到项目所在的目录,在 .settings 子目录里面,用 文本编辑器打开 org.eclipse.wst.common.project.facet.core.xm...
  • lianshangxiakong
  • lianshangxiakong
  • 2014年03月11日 09:49
  • 534
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Java Compiler不会想当然
举报原因:
原因补充:

(最多只允许输入30个字)