方法参数是值传递还是引用传递?一篇彻底搞懂!
在Java面试和日常开发中,“Java是值传递还是引用传递?”是一个经典问题。很多开发者对此感到困惑,但答案是唯一的:Java永远是值传递(Pass by Value)。
为了彻底厘清这个概念,让我们首先将核心结论放在最前面:
核心结论
当传入的参数是 原始数据类型 (
int,double等) 或 不可变的引用类型 (如String,Integer) 时:- 无论方法内部对这个参数做什么操作,都不会影响到方法外部的原始变量。
当传入的参数是 可变的引用类型 (如
List,Map等) 时:如果修改的是对象自身的状态 (例如调用
list.add(item)),这个改变对方法外部是 可见的。如果将参数引用指向一个新对象 (例如
list = new ArrayList<>()),这个改变对方法外部是 不可见的。
为什么会这样?接下来,我们将通过代码示例和原理解析,深入探讨背后的一切。
什么是值传递 (Pass by Value)?
在程序设计语言中,方法调用时传递参数的方式主要有两种:值传递和引用传递。
- 值传递 (Pass by Value):在调用方法时,实际上传递的是实参的一个副本 (copy)。方法内对形参的任何操作,都只是在操作这个副本,不会影响到方法外部的实参。
- 引用传递 (Pass by Reference):在调用方法时,实际上传递的是实参的内存地址本身。方法内对形参的操作,会直接影响到该内存地址所指向的原始数据。
Java采用的是前者——值传递。理解的关键在于,我们需要搞清楚到底是什么“值”被复制并传递了。
- 对于原始数据类型,传递的是值的副本。
- 对于引用类型,传递的是引用的副本(也就是内存地址的副本)。
正是这个“引用的副本”让很多人误以为是“引用传递”。接下来我们用实例来验证我们的结论。
案例一:原始数据类型
这是最简单的情况。当我们将一个int类型的变量传入方法时,方法得到的是这个int值的一个完整拷贝。
1 | public class PassByValueExample { |
结果分析:
main方法中的num值为10。- 调用
modify(num)时,Java创建了num的一个副本(也就是10这个值),并将这个副本交给了modify方法的参数value。 - 方法内部,
value = 20这行代码修改的仅仅是value这个副本,与main方法中的num毫无关系。 - 方法执行完毕后,
main方法中的num依然是10。这完美印证了我们的第一个结论。
案例二:不可变的引用类型 (String)
String是一个特殊的引用类型,因为它是不可变的 (Immutable)。这意味着一旦一个String对象被创建,它的值就不能被改变。
1 | public class PassByValueExample { |
结果分析:
main方法中的message是一个引用,它指向堆内存中值为”Hello”的字符串对象。- 调用
modify(message)时,Java复制了这个引用(内存地址),并将引用的副本交给了modify方法的参数text。此时,message和text指向同一个”Hello”对象。 - 在方法内部,
text = text + ", World"这行代码执行时,由于String的不可变性,它并不会修改”Hello”对象。相反,它创建了一个全新的字符串对象”Hello, World”,并将text这个引用指向了这个新对象。 - 重要的是,只有
text(引用的副本)的指向改变了,main方法中的message(原始引用)仍然指向最初的”Hello”对象。 - 因此,方法调用后,
message的值没有改变。
案例三:可变的引用类型 (List)
这是最能体现Java值传递精髓的地方,也是最容易产生困惑的地方。
1 | import java.util.ArrayList; |
结果分析:
modifyState方法(修改对象状态)- 调用
modifyState(myList)时,list参数得到了myList引用的一个副本。它们都指向同一个ArrayList对象。 - 在方法内部执行
list.add("C")时,它通过这个引用的副本找到了堆内存中的ArrayList对象,并修改了这个对象自身的状态(添加了一个元素”C”)。 - 由于
main方法中的myList和方法中的list指向的是同一个对象,所以这个修改对于main方法是可见的。myList的内容变成了[A, B, C]。
- 调用
reassignReference方法(引用重新赋值)- 调用
reassignReference(myList)时,list参数同样得到了myList引用的一个副本。 - 关键在于
list = new ArrayList<>()这行代码。它创建了一个全新的、空的ArrayList对象,并将方法内部的list这个引用副本指向了这个新对象。 - 此时,
main方法中的myList引用仍然指向旧的、包含[A, B, C]的那个对象,而方法内的list引用已经和它分道扬镳了。 - 之后对
list的任何操作(如list.add("D"))都只影响这个新对象,与main方法中的myList无关。 - 因此,方法结束后,
myList的值没有改变,依然是[A, B, C]。
- 调用
总结
希望通过以上的分析和示例,你能够清晰地理解:
Java 只有值传递。
- 当参数是原始类型时,传递的是值的拷贝,方法内的修改不影响外部。
- 当参数是引用类型时,传递的是引用的拷贝。因为拷贝的引用和原始的引用指向同一个对象,所以通过拷贝的引用去修改对象的状态(比如
list.add()),会影响到原始引用。但是,如果让拷贝的引用去指向一个新对象(比如list = new ArrayList<>()),这并不会影响原始的引用。
只要牢牢记住开篇的两个核心结论,并理解其背后的“引用副本”原理,你就再也不会被这个问题所困扰了。


