原因:今天项目中出了一个问题,问题定位以后,发现是由于自己为了代码的整洁,使用了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
}
在操作栈中进行数据增减,因此最后的值赋给变量,这样子就不会出现不增的 情形。
从这次事件中,体会到要谨慎,不能使用不符合语言规范的表达方式,避开陷阱。