原因:今天项目中出了一个问题,问题定位以后,发现是由于自己为了代码的整洁,使用了if的三式表达式,当判断条件真时,相当于执行的是i=i++; 导致i一直不致增。特此,专门查了相关资料,做了实验。记录此问题,

java的jvm中使用了字节码,正因为此技术才有实现全平台通用的可能性,依靠jvm的读取字节码,执行逻辑程序。本次实验依靠字节码的来查看java的执行顺序。

以下是实现i++的代码。执行结果为0

public class CalcuTest {
        public static void main(String[] args){
                int i = 0;
                i=i++;
                System.out.println(i);
        }


}

通过javap指令,可以得到转换的字节码:

Last modified Sep 28, 2015; size 403 bytes
  MD5 checksum a8a9f6113d1c6dd4d04cd579005772da
  Compiled from "CalcuTest.java"
public class CalcuTest
  SourceFile: "CalcuTest.java"
  minor version: 0
  major version: 51
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #5.#14         //  java/lang/Object."<init>":()V
   #2 = Fieldref           #15.#16        //  java/lang/System.out:Ljava/io/PrintStream;
   #3 = Methodref          #17.#18        //  java/io/PrintStream.println:(I)V
   #4 = Class              #19            //  CalcuTest
   #5 = Class              #20            //  java/lang/Object
   #6 = Utf8               <init>
   #7 = Utf8               ()V
   #8 = Utf8               Code
   #9 = Utf8               LineNumberTable
  #10 = Utf8               main
  #11 = Utf8               ([Ljava/lang/String;)V
  #12 = Utf8               SourceFile
  #13 = Utf8               CalcuTest.java
  #14 = NameAndType        #6:#7          //  "<init>":()V
  #15 = Class              #21            //  java/lang/System
  #16 = NameAndType        #22:#23        //  out:Ljava/io/PrintStream;
  #17 = Class              #24            //  java/io/PrintStream
  #18 = NameAndType        #25:#26        //  println:(I)V
  #19 = Utf8               CalcuTest
  #20 = Utf8               java/lang/Object
  #21 = Utf8               java/lang/System
  #22 = Utf8               out
  #23 = Utf8               Ljava/io/PrintStream;
  #24 = Utf8               java/io/PrintStream
  #25 = Utf8               println
  #26 = Utf8               (I)V
{
  public CalcuTest();
    flags: ACC_PUBLIC
    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[]);
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=1
         0: iconst_0
         1: istore_1
         2: iload_1
         3: iinc          1, 1
         6: istore_1
         7: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
        10: iload_1
        11: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
        14: return
      LineNumberTable:
        line 10: 0
        line 11: 2
        line 12: 7
        line 13: 14
}

上面部分主要是程序的常量池,也就是程序入口。通常称为常量池的入口位置。当程序需要常量池时,通常需要记录常量池入口位置的标识符,主要分析的是下面这段代码。也就是main方法里的字节码

public static void main(java.lang.String[]);
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
<span style="white-space: pre;">	</span>//<span style="font-family: Arial, Helvetica, sans-serif;">stack代表本地栈的slot个数为2个,进栈出栈的次数为3; </span>
<span style="white-space:pre">	</span>//locals=2 代表本地变量为2个
<span style="white-space:pre">	</span>//args_size 代表入口参数为1
      stack=2, locals=2, args_size=1<span style="font-family: Arial, Helvetica, sans-serif;">  /</span>
0: iconst_0<span style="white-space:pre">			</span>//将int类型的常量值1推送至栈顶
         1: istore_1<span style="white-space:pre">			</span>//将栈顶的抛出的数据赋值给第一个slot所在的int类型的本地变量中
         2: iload_1<span style="white-space:pre">			</span>//将第一个slot所在的int类型的本地变量放入栈顶中
         3: iinc          1, 1<span style="white-space:pre">		</span>//将第一个slot所在的int类型的本地变量自家1;
         6: istore_1<span style="white-space:pre">			</span>//将栈顶抛出的数据赋值给第一个slot所在的int类型的本地变量中
         7: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream; //获取静态域,放在栈顶
        10: iload_1<span style="white-space:pre">			</span>//将第一个slot所在的int类型的本地变量放入栈顶
        11: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V //执行out对象的println方法
14: return<span style="white-space:pre">			</span>//返回值
      LineNumberTable:
        line 10: 0
        line 11: 2
        line 12: 7
        line 13: 14

slot表示4个字节的 长度,可以理解为字长的意思。

从上面的解释中,就可以很清晰的看到,执行结果应当为0 ,因为自家操作不需要经过操作栈,因此他在本地变量自加后,被栈顶抛出原来的值给覆盖了。

i=i++  --------> tmp=i ; i=i+1 ; i=tmp;

下面看一下将i=i+1的字节码:

public static void main(java.lang.String[]);
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=1
         0: iconst_0<span style="white-space:pre">		</span>//与i++相同
1: istore_1<span style="white-space:pre">		</span><span style="font-family: Arial, Helvetica, sans-serif;">//与i++相同</span>
         2: iload_1<span style="white-space:pre">		</span>//将第一个slot所在的int类型的本地变量压入栈顶
         3: iconst_1<span style="white-space:pre">		</span>//将int类型的常量值1压入栈顶
         4: iadd<span style="white-space:pre">		</span>//将加栈中的数据进行相加
         5: istore_1<span style="white-space:pre">		</span>//将结果赋值给第一个slot所在的int类型的本地变量
         6: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         9: iload_1
        10: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
        13: return
      LineNumberTable:
        line 10: 0
        line 11: 2
        line 12: 6
        line 13: 13
}

在操作栈中进行数据增减,因此最后的值赋给变量,这样子就不会出现不增的 情形。


从这次事件中,体会到要谨慎,不能使用不符合语言规范的表达方式,避开陷阱。