问题描述

前两天面试的时候面试官问了我一个问题:

在方法f外部创建一个HashMap的引用mf的形参列表是HashMap m,返回值是void,f内部的代码是让m指向另一个HashMap,问调用f方法后,m指向哪个HashMap

例子:

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
import java.util.HashMap;

public class Test {
public static void main(String[] args) {
// 在方法外面定义了一个HashMap的引用,引用名为m
HashMap<String, Integer> m = new HashMap<>();
// 给m赋值
m.put("a", 1);
m.put("b", 2);
// 打印m
System.out.println("Before calling change(): " + m);
// 调用change()方法
change(m);
// 打印m
System.out.println("After calling change(): " + m);
}

public static void change(HashMap<String, Integer> m) {
// 在方法内部让m指向另一个HashMap
m = new HashMap<>();
// 给新的m赋值
m.put("c", 3);
m.put("d", 4);
}
}
//输出结果
//Before calling change(): {a=1, b=2}
//After calling change(): {a=1, b=2}

可以看到,在调用change()方法后,原来的m并没有改变,而是仍然指向之前创建的那个HashMap对象。而在change()方法内部创建的新的HashMap对象并没有被返回或者传递给其他变量,所以它会被垃圾回收器回收。

这是因为在Java中,方法的参数是按值传递的,也就是说,当你把m作为参数传递给change()方法时,实际上是把m存储的地址值复制了一份给change()方法内部的m变量。所以在change()方法内部,m变量和main()方法中的m变量指向的是同一个对象。但是当你在change()方法内部让m指向另一个对象时,你只是改变了change()方法内部的m变量的值,并没有影响到main()方法中的m变量的值。所以在调用change()方法后,main()方法中的m变量仍然指向原来的对象。

值传递和引用传递

在JVM的层面,值传递和引用传递的区别主要体现在栈内存堆内存的分配和使用上。

栈内存是线程私有的,用于存储基本类型的变量和对象类型的引用。每个方法调用时都会创建一个栈帧,用于存储该方法的局部变量、参数、返回值等信息。当方法返回时,栈帧就会被销毁。

堆内存是线程共享的,用于存储对象类型的实例数据。每个对象都有一个唯一的地址标识,对象类型的引用就是指向这个地址标识的值。当没有任何引用指向一个对象时,该对象就会被垃圾回收器回收。

当我们在Java中传递参数时,无论是基本类型还是对象类型,都是按值传递的。也就是说,都是把参数在栈内存中的值复制一份给方法内部使用。对于基本类型来说,这个值就是它本身;对于对象类型来说,这个值就是它在堆内存中的地址标识。

所以,在方法内部修改基本类型参数或者让对象类型参数指向新创建的对象,并不会影响到原始参数。但是,在方法内部修改对象类型参数所指向对象的属性或状态,则会影响到原始参数所指向的同一个对象。