缘起
关于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行是否会对调用方法产生影响呢?
答案是不会的。那为什么不会产生影响呢,请耐心往下看。
前言
看完本文你将获得什么?
- 首先肯定是理解Java传递参数的方式啦
- 深刻理解虚拟机栈、栈帧、局部变量表。
首先给出结论
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);
}
}
结果运行一下,我们很容易得出结果如下:
很简单,我们知道传入method
的code
并没有被改变,下面我们一起探究以下为什么它没有被改变吧。
获取字节码
我们使用如下指令编译与反编译获取字节码:
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
- aload_2指令:将2号局部变量加到操作数栈顶。Slot2局部变量就是test对象的地址呀,我们成为objectRef
- iload_1指令:将1号局部变量压入操作数栈。Slot1局部变量就是参数呀~
- invokevirtual指令:从操作数栈中依次弹出参数、调用者地址,去执行#4方法
到method方法
- 查看method方法的descriptor,发现有一个int型参数,出栈参数列表,将局部变量表设置为参数值(这里修改的可是method方法的局部变量表哦~)
- 出栈objectRef
- 继续执行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产生什么样的影响呢?
我们可以看出1
和2
被加入到同一个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);
}
}