我是靠谱客的博主 殷勤世界,这篇文章主要介绍Java方法中用Integer类型作为入参 无法改变原值的根本原因,现在分享给大家,希望可以做个参考。

文章目录

    • 1 复现过程
    • 2 原理分析
    • 3 总结


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

1 复现过程

(1)先上代码

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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)打印结果如下:
在这里插入图片描述
可以看到值没有改变,接下来我来浅析一下这是为什么。
(3)反编译如下:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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源码看一下:
在这里插入图片描述
熟悉的朋友都会知道,Integer有一个-128-127的一个缓存,在这个区间内会直接从IntegerCache中获取缓存返回,超出这个区间则返回一个新的对象。

2 原理分析

本次我从虚拟机栈和堆的来进行探讨:
首先JVM模型如下:
在这里插入图片描述
(1)显示具体字节码


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

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
```java ​ 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()。
在这里插入图片描述
(3)由图所示,main对应一个栈帧,栈帧中的局部变量表元素i 分别指向堆中的i,s指向堆中的s。
在这里插入图片描述
当在main()方法中执行到字节码的第 18行: invokespecial #8 // 调用实例化方法test,add()方法
时候因为Java是引用类型的值传递(值传递),所以会把局部变量表中的 i
的引用地址,传递给实例化对象test的add()方法中的i参数,同时add()方法被调用,压入add()方法的栈帧进入java虚拟机栈中,同时该栈帧拥有自己的局部变量表
i 指向堆中内存 i 。
在这里插入图片描述
因为 i 此时是局部变量,仅仅存在add的栈帧中,当执行代码

复制代码
1
2
i = i - 1;(等同于执行了 i = Integer.valueOf(i - 1) )

如图所示,在add()方法的栈帧中的局部变量表中 i 内存指向 i1。
在这里插入图片描述
综上所诉,我们可以看出,原先main()方法的栈帧中的局部变量表 i
的内存地址指向并没有发生任何的改变,所以自然在打印的时候也不会发生任何的改变。仅仅只是add()栈帧中的局部变量表里面的 i
的指向堆中的内存地址发生了改变,并不会影响到main()方法局部变量表中的 i 。

3 总结

Java中的传递方式是引用类型的值传递(值传递),在方法中如果要修改变量的值,只能修改原本变量指向堆中内存的值,而不能通过在方法中改变对象的引用地址来进行修改。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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); } }

这样就能成功交换变量的值了:结果如下
在这里插入图片描述
文章转自 https://blog.csdn.net/weixin_44640989/article/details/120590881

最后

以上就是殷勤世界最近收集整理的关于Java方法中用Integer类型作为入参 无法改变原值的根本原因的全部内容,更多相关Java方法中用Integer类型作为入参内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(69)

评论列表共有 0 条评论

立即
投稿
返回
顶部