JVM|浅析Integer类型传参值不变来理解Java值传参

浅析Integer类型传参值不变来理解Java值传参

以前对java值的引用传递有一些疑惑,将Integer和String传入方法中进行修改,但最后值却没有修改,现在经过不断的学习以后,对这里有了一些新的体会,现在总结一下。

代码过程 (1)先上代码
private void add(Integer i) { i = i - 1; }private void reverse(String s) { s = "sey"; }public static void main(String[] args) { Integer i = 1; String s = "yes"; Test test = new Test(); test.add(i); test.reverse(s); // 打印值 System.out.println(String.format("i的值:%d", i)); System.out.println(String.format("s的值:%s", s)); }

(2)打印结果如下:
JVM|浅析Integer类型传参值不变来理解Java值传参
文章图片

可以看到值没有改变,接下来我来浅析一下这是为什么。
(3)反编译如下:
public class Test { public Test() { } private void add(Integer i) { i = Integer.valueOf(i.intValue() - 1); } private void reverse(String s) { s = "sey"; } public static void main(String args[]) { Integer i = Integer.valueOf(1); String s = "yes"; Test test = new Test(); test.add(i); test.reverse(s); System.out.println(String.format("i的值:%d", new Object[] { i })); System.out.println(String.format("s的值:%s", new Object[] { s })); } }

我们可以明显的看出由于java语法糖的缘故,Integer i = 1; 实质上是 Integer.valueOf(1)。接着深入Integer源码看一下:
JVM|浅析Integer类型传参值不变来理解Java值传参
文章图片

熟悉的朋友都会知道,Integer有一个-128-127的一个缓存,在这个区间内会直接从IntegerCache中获取缓存返回,超出这个区间则返回一个新的对象。
原理分析 本次我从虚拟机栈和堆的来进行探讨:
首先JVM模型如下:
JVM|浅析Integer类型传参值不变来理解Java值传参
文章图片

(1)显示具体字节码
执行 javap -c Test.class 命令分解方法代码,显示每个方法具体的字节码

public class Test { public Test(); Code: 0: aload_0// 装载局部变量表[0]位置的变量(一般是this对象) 1: invokespecial #1// 初始化方法 4: return// 方法结束public static void main(java.lang.String[]); Code: 0: iconst_1// 将常量1压入操作数栈 1: invokestatic#3// 装箱操作(Integer.valueOf()),返回一个对象并且压入栈顶 4: astore_1// 栈顶元素出栈,并将引用存入局部变量表[1]的位置 5: ldc#5// String yes 把常量池中的项压入栈 7: astore_2// 栈顶元素出栈,并将引用存入局部变量表[2]的位置 8: new#6// 创建Test对象(堆上分配内存,返回引用),并将引用压入栈顶 11: dup// 栈顶元素出栈,并将栈顶元素复制 12: invokespecial #7// 栈顶元素出栈,并且调用实例化init()方法 15: astore_3// 栈顶元素出栈,并将引用存在局部变量表[3]的位置 16: aload_3// 装载局部变量表[3]的引用 -----> 对应test 17: aload_1// 装载局部变量表[1]的引用 -----> 对应i 18: invokespecial #8// 调用实例化方法test,add()方法 21: aload_3// 装载局部变量表[3]的引用 22: aload_2// 装载局部变量表[2]的引用 23: invokespecial #9// 调用实例化方法test,reverse()方法 26: getstatic#10// Field java/lang/System.out:Ljava/io/PrintStream; 29: ldc#11// String i的值:%d 31: iconst_1 32: anewarray#12// class java/lang/Object 35: dup 36: iconst_0 37: aload_1 38: aastore 39: invokestatic#13// Method java/lang/String.format:(Ljava/lang/String; [Ljava/lang/Object; )Ljava/lang/String; 42: invokevirtual #14// Method java/io/PrintStream.println:(Ljava/lang/String; )V 45: getstatic#10// Field java/lang/System.out:Ljava/io/PrintStream; 48: ldc#15// String s的值:%s 50: iconst_1 51: anewarray#12// class java/lang/Object 54: dup 55: iconst_0 56: aload_2 57: aastore 58: invokestatic#13// Method java/lang/String.format:(Ljava/lang/String; [Ljava/lang/Object; )Ljava/lang/String; 61: invokevirtual #14// Method java/io/PrintStream.println:(Ljava/lang/String; )V 64: return }

(2)我们知道Java虚拟机是线程私有的,每个方法被执行的时候,Java虚拟机都会同步创建一个栈帧用于存储局部变量表,操作数栈,动态链接,方法出口等信息,每一个方法被调用直至执行完毕的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程,这次我们讨论虚拟机栈的这两个方法的栈帧:main(),add()。
JVM|浅析Integer类型传参值不变来理解Java值传参
文章图片

(3)由图所示,main对应一个栈帧,栈帧中的局部变量表元素i 分别指向堆中的i,s指向堆中的s。
JVM|浅析Integer类型传参值不变来理解Java值传参
文章图片

当在main()方法中执行到字节码的第 18行: invokespecial #8 // 调用实例化方法test,add()方法
时候因为Java是引用传递,所以会把局部变量表中的 i 的引用地址,传递给实例化对象test的add()方法中的i参数,同时add()方法被调用,压入add()方法的栈帧进入java虚拟机栈中,同时该栈帧拥有自己的局部变量表 i 指向堆中内存 i 。
JVM|浅析Integer类型传参值不变来理解Java值传参
文章图片

因为 i 此时是局部变量,仅仅存在add的栈帧中,当执行代码
i = i - 1; (等同于执行了i = Integer.valueOf(i - 1) );

如图所示,在add()方法的栈帧中的局部变量表中 i 内存指向 i1。
JVM|浅析Integer类型传参值不变来理解Java值传参
文章图片

综上所诉,我们可以看出,原先main()方法的栈帧中的局部变量表 i 的内存地址指向并没有发生任何的改变,所以自然在打印的时候也不会发生任何的改变。仅仅只是add()栈帧中的局部变量表里面的 i 的指向堆中的内存地址发生了改变,并不会影响到main()方法局部变量表中的 i 。
总结 Java中的传递方式是引用传递,在方法中如果要修改变量的值,只能修改原本变量指向堆中内存的值,而不能通过在方法中改变对象的引用地址来进行修改。
public class Test {Integer x = 1; Integer y = 2; /** * 交换变量 */ private void swap(Integer i1, Integer i2) { i1 = i1 ^ i2; i2 = i1 ^ i2; i1 = i1 ^ i2; }private void swap(Test test) { Integer x = test.x; Integer y = test.y; x = x ^ y; y = x ^ y; x = x ^ y; test.x = x; test.y = y; } public static void main(String[] args) { Integer i = 1; Integer j = 2; Test test = new Test(); test.swap(i, j); System.out.println(i); System.out.println(j); test.swap(test); System.out.println(test.x); System.out.println(test.y); } }

【JVM|浅析Integer类型传参值不变来理解Java值传参】这样就能成功交换变量的值了:结果如下
JVM|浅析Integer类型传参值不变来理解Java值传参
文章图片

    推荐阅读