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

Posted Eternal frustration

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了浅析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)打印结果如下:

可以看到值没有改变,接下来我来浅析一下这是为什么。
(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源码看一下:

熟悉的朋友都会知道,Integer有一个-128-127的一个缓存,在这个区间内会直接从IntegerCache中获取缓存返回,超出这个区间则返回一个新的对象。

原理分析

本次我从虚拟机栈和堆的来进行探讨:
首先JVM模型如下:

(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()。

(3)由图所示,main对应一个栈帧,栈帧中的局部变量表元素i 分别指向堆中的i,s指向堆中的s。

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

因为 i 此时是局部变量,仅仅存在add的栈帧中,当执行代码

 i = i - 1;(等同于执行了  i = Integer.valueOf(i - 1) );

如图所示,在add()方法的栈帧中的局部变量表中 i 内存指向 i1。

综上所诉,我们可以看出,原先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);
    }
}

这样就能成功交换变量的值了:结果如下

以上是关于浅析Integer类型传参值不变来理解Java值传参的主要内容,如果未能解决你的问题,请参考以下文章

js函数传参

JS中函数参数值传递和引用传递

java泛型学习

C语言中的值传参和引用传参是指啥?

函数传值传参

微信小程序-wxml标签绑定data值传参给js方法(事件传参)