Java的传参方式其实只有一种,从根上理解Java传参方式

在这里插入图片描述

缘起

关于Java的传参问题说起来并不是很难,但是有时候却很容易引起大问题。

问:我想完成两个Collection集合的求交集,如下代码当list1==null时是否会修改参数?

public List<String> overlop(List<String> list1, List<String> list2) {
    if (list1 == null) {
        list1 = new ArrayList<>();
    }
    if (list2 == null) {
        list2 = new ArrayList<>();
    }
    // 求交集操作
    return 交集集合;
}

请问如上代码的3、6行是否会对调用方法产生影响呢?

答案是不会的。那为什么不会产生影响呢,请耐心往下看。

前言

看完本文你将获得什么?

  1. 首先肯定是理解Java传递参数的方式啦
  2. 深刻理解虚拟机栈、栈帧、局部变量表。

首先给出结论

Java只存在值传递(所有的赋值都是copy到了一个完全不同的变量中)。

有的同学说了,不是都说Java到处都是指针吗,我还听说对于对象来说是引用传递,你这讲错了吧。客官请耐心听完。

实例说明1:立即数

我们使用以下的例子来说明:

public class Test {
    public static void main(String[] args) {
        int code = 10;
        Test test = new Test();
        test.method(code);
        System.out.println(code);
    }

    public void method(int code2) {
        System.out.println(code2);
		code2 = 11;
		System.out.println(code2);
    } 
}

结果运行一下,我们很容易得出结果如下:

在这里插入图片描述

很简单,我们知道传入methodcode并没有被改变,下面我们一起探究以下为什么它没有被改变吧。

获取字节码

我们使用如下指令编译与反编译获取字节码:

javac -g:vars Test.java // 编译Test.java文件,并且编译局部变量表,由此可见局部变量表是在编译阶段就可以确定的。
javap -v Test.class     // 反编译Test.class文件。

提示:注释中有一个知识点哦~(局部变量表是在编译阶段确定的)

我们得到的字节码如下(仅截取方法指令):

 flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=3, args_size=1
         0: bipush        10
         2: istore_1
         3: new           #2                  // class Test
         6: dup
         7: invokespecial #3                  // Method "<init>":()V
        10: astore_2
        11: aload_2
        12: iload_1
        13: invokevirtual #4                  // Method method:(I)V
        16: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
        19: iload_1
        20: invokevirtual #6                  // Method java/io/PrintStream.println:(I)V
        23: return
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      24     0  args   [Ljava/lang/String;
            3      21     1  code   I
           11      13     2  test   LTest;

  public void method(int);
    descriptor: (I)V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=2, args_size=2
         0: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: iload_1
         4: invokevirtual #6                  // Method java/io/PrintStream.println:(I)V
         7: bipush        11
         9: istore_1
        10: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
        13: iload_1
        14: invokevirtual #6                  // Method java/io/PrintStream.println:(I)V
        17: return
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      18     0  this   LTest;
            0      18     1 code2   I

从main方法讲起

invokespacial指令是java执行方法的指令,这个指令在执行方法之前需要依次将调用者的this指针和参数压入操作数栈。
我们使用method方法的调用理解一下invoke方法吧,请看main方法指令中的这几条,就是在执行method方法。

        11: aload_2
        12: iload_1
        13: invokevirtual #4                  // Method method:(I)V
  1. aload_2指令:将2号局部变量加到操作数栈顶。Slot2局部变量就是test对象的地址呀,我们成为objectRef
  2. iload_1指令:将1号局部变量压入操作数栈。Slot1局部变量就是参数呀~
  3. invokevirtual指令:从操作数栈中依次弹出参数、调用者地址,去执行#4方法

到method方法

  1. 查看method方法的descriptor,发现有一个int型参数,出栈参数列表,将局部变量表设置为参数值(这里修改的可是method方法的局部变量表哦~)
  2. 出栈objectRef
  3. 继续执行method方法。

现在的运行时数据区是这样子的(简图)

在这里插入图片描述

所以在method中任意修改code2,main中的code也不会变。

实例说明2:String字符串

public class Test {
    public static void main(String[] args) {
        String code = "这是一个很长很长的文本这是一个很长很长的文本这是一个很长很长的文本这是一个很长很长的文本这是一个很长很长的文本这是一个很长很长的文本这是一个很长很长的文本";
        Test test = new Test();
        test.method(code);
        System.out.println(code);
    }

    public void method(String code2) {
        System.out.println(code2);
		code2 = "俺也一样";
		System.out.println(code2);
    } 
}

经过编译和反编译,我们可以看到和实例说明1是完全一样的。

实例说明3:Collection对象

import java.util.*;
public class Test {
    public static void main(String[] args) {
        List<String> list1 = new ArrayList<>();
		list1.add("1");
        Test test = new Test();
        test.method(code);
    }

    public void method(List<String> list2) {
        System.out.println(code2);
		list2.add("2")
		list2 = new ArrayList<>();
		list2.add("3")
		System.out.println(code2);
    } 
}

上面的三次add会对两个ArrayList产生什么样的影响呢?
我们可以看出12被加入到同一个ArrayList中,3自己被加入到了一个ArrayList中.

总结

java中所有的传递都是值传递,因为调用方法使得当前栈帧切换,使用的的是新栈帧的局部变量表。那在什么情况下会引起改变呢?
引用的实际空间(堆)产生了数据上的变化。(如使用List的add方法,使用数组的赋值方法)

练习题

import java.util.*;
public class Test {
	public static void main(String[] args){
        List<Integer> list1 = new ArrayList<>();
        List<Integer> list2 = new ArrayList<>();
        list1.add(0);
        list2.add(0);
        func(list1,list2);
        System.out.println("-------------------------------");
        list1.forEach(System.out::println);
        System.out.println("-------------------------------");
        list2.forEach(System.out::println);
    }
    public static void func(List<Integer> list1,List<Integer> list2){
        list1 = new ArrayList<>();
        list1.add(1);
        list2.add(1);
        list2 = new ArrayList<>();
        list1.add(2);
        list2.add(2);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

tinpo_123

感谢给小张填杯java~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值