通过javap指令解释a=a++问题

首先,补充javap指令详解

编译阶段Java就可以决定方法里本地变量个数,在方法调用的时候,就可以直接分配一个本地变量的区域,这个区域基于slot来分配,每个slot为4字节,任何一个数据至少占一个slot。


1、代码部分

public class test{
	public static void main(String[] args) {
		int a = 1, b = 1;
		a = a++;
		b = ++b;
		System.out.println(a);
		System.out.println(b);
	}
}

本地javac编译成class文件后,通过javap -verbose test //输出堆栈大小、各方法的 locals 及 args 数,以及class文件的编译版本


2、解析输出内容

C:\Users\fedomn>javap -verbose test
Compiled from "test.java"
public class test extends java.lang.Object
  SourceFile: "test.java"
  minor version: 0
  major version: 50
  Constant pool:
const #1 = Method       #5.#14; //  java/lang/Object."<init>":()V
const #2 = Field        #15.#16;        //  java/lang/System.out:Ljava/io/PrintS
tream;
const #3 = Method       #17.#18;        //  java/io/PrintStream.println:(I)V
const #4 = class        #19;    //  test
const #5 = class        #20;    //  java/lang/Object
const #6 = Asciz        <init>;
const #7 = Asciz        ()V;
const #8 = Asciz        Code;
const #9 = Asciz        LineNumberTable;
const #10 = Asciz       main;
const #11 = Asciz       ([Ljava/lang/String;)V;
const #12 = Asciz       SourceFile;
const #13 = Asciz       test.java;
const #14 = NameAndType #6:#7;//  "<init>":()V
const #15 = class       #21;    //  java/lang/System
const #16 = NameAndType #22:#23;//  out:Ljava/io/PrintStream;
const #17 = class       #24;    //  java/io/PrintStream
const #18 = NameAndType #25:#26;//  println:(I)V
const #19 = Asciz       test;
const #20 = Asciz       java/lang/Object;
const #21 = Asciz       java/lang/System;
const #22 = Asciz       out;
const #23 = Asciz       Ljava/io/PrintStream;;
const #24 = Asciz       java/io/PrintStream;
const #25 = Asciz       println;
const #26 = Asciz       (I)V;

{
public test();
  Code:
   Stack=1, Locals=1, Args_size=1
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   return
  LineNumberTable:
   line 1: 0


public static void main(java.lang.String[]);
  Code:
   Stack=2, Locals=3, Args_size=1
   0:   iconst_1
   1:   istore_1
   2:   iconst_1
   3:   istore_2
   4:   iload_1
   5:   iinc    1, 1
   8:   istore_1
   9:   iinc    2, 1
   12:  iload_2
   13:  istore_2
   14:  getstatic       #2; //Field java/lang/System.out:Ljava/io/PrintStream;
   17:  iload_1
   18:  invokevirtual   #3; //Method java/io/PrintStream.println:(I)V
   21:  getstatic       #2; //Field java/lang/System.out:Ljava/io/PrintStream;
   24:  iload_2
   25:  invokevirtual   #3; //Method java/io/PrintStream.println:(I)V
   28:  return
  LineNumberTable:
   line 3: 0
   line 4: 4
   line 5: 9
   line 6: 14
   line 7: 21
   line 8: 28

其中:Stack:表示栈顶大小(单位为一个slot)。当使用一个数据时,它首先会被放入栈顶,使用完会写回到本地变量中。

Locals:本地变量的slot个数。Args_size:人口参数个数。

minor version: 0 major version: 50表示jdk版本1.6

Constant pool:常量池。程序中需要用到常量时,会写入const后对应入口地址。


这里主要解释main函数里执行过程

public static void main(java.lang.String[]);
  Code:
   Stack=2, Locals=3, Args_size=1
   0:   iconst_1 (把int类型常量1放到栈顶)
   1:   istore_1 (把栈顶的值放到第一个slot所在的本地变量a中)
   2:   iconst_1 (把int类型常量1放到栈顶)
   3:   istore_2 (把栈顶的值放到第二个slot所在的本地变量b中)
   4:   iload_1  (将第一个slot所在的本地变量a放入栈顶)
   5:   iinc    1, 1 (将第一个slot所在的本地变量a自加1)
   8:   istore_1     (将栈顶数据抛出写入到第一个slot所在的本地变量a中,注意:抛出的a是没有自加的,iinc只是对slot区域的a加1)
   9:   iinc    2, 1 (将第二个slot所在的本地变量b自加1) 
   12:  iload_2      (将第二个slot所在的本地变量b放入栈顶) 
   13:  istore_2     (将栈顶数据抛出写入到第二个slot所在的本地变量b中,注意:此时抛出的b是自加后的)
   14:  getstatic       #2; (将System类的out静态属性取出来放入栈顶) //Field java/lang/System.out:Ljava/io/PrintStream;   
   17:  iload_1		  		(将第一个slot所在的本地变量a放入栈顶)
   18:  invokevirtual   #3; (调用当前类里常量池索引为3的方法 即println) //Method java/io/PrintStream.println:(I)V
   21:  getstatic       #2; (将System类的out静态属性取出来放入栈顶) //Field java/lang/System.out:Ljava/io/PrintStream;
   24:  iload_2				(将第二个slot所在的本地变量b放入栈顶)
   25:  invokevirtual   #3; (调用当前类里常量池索引为3的方法 即println) //Method java/io/PrintStream.println:(I)V
   28:  return
  LineNumberTable:
   line 3: 0
   line 4: 4
   line 5: 9
   line 6: 14
   line 7: 21
   line 8: 28

通过上面注释看出,Java在执行”=“操作的时候,先会把右边的值推到栈顶,然后把栈顶的值抛出赋给右边所在slot区域的本地变量中。

例1:a = 1;

第一步:将1推入栈顶

第二步:将栈顶数据抛出赋给slot区中本地变量a


例2:a = a++;

第一步:将slot区域本地变量a推入栈顶

第二步:执行iinc将slot区本地变量a+1

第三步:将栈顶数据抛出赋给slot区中本地变量a


例3:a = ++a

第一步:执行iinc将slot区域本地变量a+1

第二步:将slot区域本地变量a推入栈顶

第三步:将栈顶数据抛出赋给slot区中本地变量a


3、练习

int a = 1;

a = (a++)+(++a)+(a++);

我们也通过javap -verbose 来看执行顺序。

public static void main(java.lang.String[]);
  Code:
   Stack=2, Locals=2, Args_size=1
   0:   iconst_1
   1:   istore_1
   2:   iload_1
   3:   iinc    1, 1
   6:   iinc    1, 1
   9:   iload_1
   10:  iadd
   11:  iload_1
   12:  iinc    1, 1
   15:  iadd
   16:  istore_1
   17:  getstatic       #2; //Field java/lang/System.out:Ljava/io/PrintStream;
   20:  iload_1
   21:  invokevirtual   #3; //Method java/io/PrintStream.println:(I)V
   24:  return
  LineNumberTable:
   line 3: 0
   line 4: 2
   line 5: 17
   line 6: 24

其中iadd解释:add top two elements of stack, leave result on stack

翻译过来:

第一步:将slot区域本地变量a推入栈顶

第二步:将本地slot区域本地变量a加1

第三步:将本地slot区域本地变量a加1

第四步:将slot区域本地变量a推入栈顶

第五步:执行iadd将栈中2个int类型相加,结果放入栈中。

第六步:将slot区域本地变量a推入栈顶

第七步:将本地slot区域本地变量a加1

第八步:执行iadd将栈中2个int类型相加,结果放入栈中。

第九步:将栈顶数据抛出赋给slot区域本地变量a

最终结果为:7

过程有点繁琐,可以通过画出栈区和本地变量区解释。


4、总结

Java里一些语句执行问题,集合效率问题。我们可以通过查看其字节码,查看底层源代码来学习。




  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值