Java自增方法的解析

2018/10/10

关于Java4种自增方法的不同

4种自增的方法

  • i++
  • ++i
  • i+=1
  • i=i+1

示例代码

public class HelloWorld{
	
	public static void main(String[] args){
		method1();// i=i+1
		method2();// i+=1
		method3();// i++
		method4();// ++i
	}
	
	public static void method1(){
		int i = 0;
		System.out.println(i = i + 1);
	}
	
	public static void method2(){
		int i = 0;
		System.out.println(i += 1);
	}
	
	public static void method3(){
		int i = 0;
		System.out.println(i++);
	}
	
	public static void method4(){
		int i = 0;
		System.out.println(++i);
	}
}

运行结果

1
1
0
1

解释说明

  • 第一种i=i+1:首先=的优先级小于+,所以该表达式会首先进行加法操作,然后再进行赋值操作
  • 第二种i+=1:第一种方法的优化版本?;
  • 第三种i++:先参与操作,后自增
  • 第四种++i:先自增,后参与操作

以上都是大家都熟知的想法

从字节码的角度

Compiled from "HelloWorld.java"
public class HelloWorld {
  public HelloWorld();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: invokestatic  #2                  // Method method1:()V
       3: invokestatic  #3                  // Method method2:()V
       6: invokestatic  #4                  // Method method3:()V
       9: invokestatic  #5                  // Method method4:()V
      12: return

  public static void method1();
    Code:
       0: iconst_0
       1: istore_0
       2: iload_0
       3: iconst_1
       4: iadd
       5: istore_0
       6: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
       9: iload_0
      10: invokevirtual #7                  // Method java/io/PrintStream.println:(I)V
      13: return

  public static void method2();
    Code:
       0: iconst_0
       1: istore_0
       2: iinc          0, 1
       5: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
       8: iload_0
       9: invokevirtual #7                  // Method java/io/PrintStream.println:(I)V
      12: return

  public static void method3();
    Code:
       0: iconst_0
       1: istore_0
       2: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
       5: iload_0
       6: iinc          0, 1
       9: invokevirtual #7                  // Method java/io/PrintStream.println:(I)V
      12: return

  public static void method4();
    Code:
       0: iconst_0
       1: istore_0
       2: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
       5: iinc          0, 1
       8: iload_0
       9: invokevirtual #7                  // Method java/io/PrintStream.println:(I)V
      12: return
}

| 指令码 | 操作码(助记符)| 操作数 | 描述(栈指操作数栈)| | —- | —- | —- | —- | | 0x03 | iconst_0 | 无 | 0(int)值入栈 | | 0x04 | iconst_1 | 无 | 1(int)值入栈 | | 0x1a | iload_0 | 无 | 从局部变量0中装载int类型值入栈 | | 0x3b | istore_0 | 无 | 将栈顶int类型值保存到局部变量0中 | | 0x60 | iadd | 无 | 将栈顶两int类型数相加,结果入栈 | | 0xb2 | getstatic | indexbyte1 indexbyte2 | 获取静态字段的值 | | 0x84 | iinc | indexbyte constbyte | 将整数值constbyte加到indexbyte指定的int类型的局部变量中 | | 0xb8 | invokestatic | indexbyte1 indexbyte2 | 调用静态方法 | | 0xb6 | invokevirtual | indexbyte1 indexbyte2 | 运行时方法绑定调用方法 |

method1

解释一下操作符:

  • 将常量0压入操作数栈
  • 将栈顶的int类型的数保存到局部变量表的第0个索引的位置
  • 从局部变量表的第0个索引的位置,将整数压入栈中
  • 将常量1压入操作数栈
  • 将栈顶的两个int类型数相加,将结果1压入操作数栈
  • 将结果1存储到局部变量的第0个索引的位置
  • 获取静态字段PrintStream的值
  • 将局部变量表第0个索引位置的值压入栈
  • 调用实例方法print输出结果
  • 方法结束返回

    method2 method3 method4

    我们可以看到剩下的三个方法的操作码是一致的,唯一不同的是操作码的执行顺序。而且多了一个新指令,该指令之后带有2个数值,分别是0和1,第一个参数是局部变量表的索引值,也就是说局部变量表第0索引处存放着一个int类型的值,第二个参数就是自增的大小,将索引0处的int值加1,得到结果。注意,这个操作是在局部变量表上操作的,而不是在操作数栈上。

    这时候我们看一下i++++i的操作码,i++是先将局部变量表0处的值载入到操作数栈,然后才将局部变量表0处的值加1,接下来调用方法print的时候输出的是操作数栈的值,此时值为0,所以最终输出的结果就如我们所知一致,输出0。

    ++i的话就正好与i++相反先加1,然后将值载入操作数栈,再调用print方法输出,得到的结果当然是1。

总结

其实,但从操作码来看,无论是i++还是++i还是i+=1的操作码的个数都是比i=i+1少的,这样是不是可以说,前者的运行效率会高于后者呢?

Search

    Table of Contents