核心结论先行:Java 只有值传递,没有引用传递。
为了彻底搞懂这个问题,需要先明确两个定义,然后通过代码示例一步步验证。
1. 先澄清两个关键概念
值传递:方法调用时,实参将自己的值的副本传递给形参,在方法内部修改形参,不会影响实参本身。
引用传递:方法调用时,实参将自己的内存地址直接传递给形参,在方法内部修改形参指向的内容,会直接影响实参指向的内容。
关键区别在于:值传递传递的是值的拷贝;引用传递传递的是内存地址本身。
2. Java 中的两种数据类型
注意下面的措辞 —— 是引用类型,不要看成引用传递。引用类型可以理解成一种基本属性或者标签,引用传递可以理解成一种行为
基本类型:变量直接存储值本身(如 int a = 10; a 里面就是 10)。
引用类型:变量存储的是对象的引用(可以理解为内存地址),真正的对象在堆内存中(如 Person p = new Person(); p 里面存储的是指向 Person 对象的地址)。
3. 通过代码验证“只有值传递”
3.1. 场景一:传递基本类型
基本类型有:byte、short、int、long、float、double、char、boolean
public class Test { public static void main(String[] args) { int num = 10; System.out.println("调用前:num = " + num); // 输出 10 changeValue(num); System.out.println("调用后:num = " + num); // ? } public static void changeValue(int a) { a = 20; // 修改的是形参 a 的副本 System.out.println("方法内部:a = " + a); // 输出 20 } }这里问号输出的结果是 :10
解释:
- int 是基本类型,所以 main 方法中的 num 变量存储的是
10这个数值本身。 - 调用
changeValue(num)时,JVM 将10这个值拷贝一份,传给形参a。(值传递) - 现在内存中有两个独立的空间:main 的 num 和 changeValue 的 a,都存着
10。 - 在方法内修改
a = 20,只是修改 a 的那个副本,main 的 num 丝毫未受影响。
结论:基本类型肯定是值传递。
3.2. 场景二:传递引用类型
引用类型有:数组、对象
这里直接看案例,可以思考问号会是什么值?
class Person { String name; Person(String name) { this.name = name; } } public class Test { public static void main(String[] args) { Person p1 = new Person("张三"); System.out.println("调用前:p1.name = " + p1.name); // 张三 changeName(p1); System.out.println("调用后:p1.name = " + p1.name); // ? } public static void changeName(Person p) { p.name = "李四"; // 通过引用修改了对象的属性 System.out.println("方法内部:p.name = " + p.name); // 李四 } }答案是:李四。
这是最容易误解的地方,看到方法内修改了对象的属性,外面也变了,就认为这是引用传递。
别急,再看下面这个例子,想想这里的问号是什么值?
public class Test { public static void main(String[] args) { Person p1 = new Person("张三"); System.out.println("调用前:p1.name = " + p1.name); // 张三 changeReference(p1); System.out.println("调用后:p1.name = " + p1.name); // ? } public static void changeReference(Person p) { p = new Person("王五"); // 让 p 指向一个新对象 System.out.println("方法内部:p.name = " + p.name); // 王五 } }答案是:张三。看下面的运行截图:
所以如果 Java 支持“引用传递”,那么这个案例中 main 中的 p1 应该也指向新对象“王五”。但结果它还是“张三”。
形象解释:
- main 方法中:
Person p1 = new Person("张三"),p1是一个变量,想象它存储的是一个“地址”(引用),地址指向堆内存中Person(“张三”)这个对象。假设是p1变量的内容 0x123。 - 调用
changeReference(p1)时:将p1中存储的地址0x123这个值,拷贝一份,赋值给形参p。现在p也存着 0x123,也指向同一个对象。传递的是 0x123 这个数值,而不是 p1 变量本身。这就是值传递! - 所以这就是为什么第一个例子中修改
p.name成功了。拟人表达就是:因为p和p1都拿着同一个地址0x123。p 顺着地址去找到这个对象,并且把对象的名字给改了,改完之后当 p1 也顺着这个地址找到这个对象时,发现名字就变了。 - 第二个案例中,在方法内执行
p = new Person(“王五”)。拟人表达就是:p 把手上的地址坐标给改掉了,然后拿着新地址去找那个地址对应的对象,假设从0x123改为0x456(新对象) - 但 main 中的
p1仍然拿着0x123,纹丝不动。所以第二个案例最后输出的还是“张三”。
结论:引用类型也是值传递。
4. 图示理解
4.1. 修改对象属性p.name = "李四"
初始状态:main中p1 = new Person("张三")
调用changeName(p1):传递地址值的拷贝
方法内执行p.name = "李四":通过任意引用修改对象
结果:p1.name变成"李四",因为两个变量指向同一个对象。
4.2. 改变引用指向p = new Person("王五")
初始状态(同上)
方法内执行p = new Person("王五"):改变形参的引用
结果:p1仍然指向对象A(name = "张三"),形参p指向了新对象B。
5. 最终结论
- Java 只有值传递,没有引用传递。
- 当参数是基本类型时,传递的是数值字面量的拷贝。
- 当参数是引用类型时,传递的是引用的值(地址编号)的拷贝。