
在 Java 开发中关于参数传递的讨论经久不衰。很多初学者甚至一些有经验的开发者都会困惑Java 到底是值传递还是引用传递本文将彻底澄清这个问题通过代码示例和内存模型分析证明一个事实Java 只有值传递不存在真正的“引用传递”。一、什么是值传递什么是引用传递在深入 Java 之前我们先明确两个概念值传递方法调用时实参将它的值传递给形参。形参得到的是实参的副本在方法内部对形参的修改不会影响实参本身。引用传递方法调用时实参将它的引用地址传递给形参。形参和实参指向同一个内存地址对形参的修改会直接影响实参。很多编程语言如 C同时支持这两种方式但Java 只支持值传递。二、Java 中的两种数据类型要理解 Java 的传递机制首先要了解 Java 中的两种数据类型基本类型byte、short、int、long、float、double、char、boolean。变量本身存储的就是具体的数值。引用类型类、接口、数组等。变量存储的是对象的内存地址即引用而不是对象本身。三、基本类型的传递值传递的直观体现先看一个基本类型的例子public class PrimitiveTest { public static void main(String[] args) { int a 10; System.out.println(调用前 a a); // 10 change(a); System.out.println(调用后 a a); // 10 } public static void change(int x) { x 20; System.out.println(方法内 x x); // 20 } }输出调用前 a 10 方法内 x 20 调用后 a 10分析a的值是 10。调用change(a)时将a的值10复制一份给形参x。在方法内部修改x为 20仅仅改变了形参副本的值a本身不受影响。这清晰地展示了值传递传递的是值的副本。四、引用类型的传递表面看起来像引用传递实则还是值传递引用类型的例子更容易让人误解。先看代码public class ReferenceTest { public static void main(String[] args) { Person p new Person(张三); System.out.println(调用前姓名 p.getName()); // 张三 changeName(p); System.out.println(调用后姓名 p.getName()); // 李四 } public static void changeName(Person person) { person.setName(李四); System.out.println(方法内姓名 person.getName()); // 李四 } } class Person { private String name; public Person(String name) { this.name name; } public String getName() { return name; } public void setName(String name) { this.name name; } }输出调用前姓名张三 方法内姓名李四 调用后姓名李四看起来方法内部修改了对象外部也受到了影响这难道不是引用传递吗不是这里传递的依然是“值”只不过这个值是对象的引用地址。内存分析Person p new Person(张三);在堆内存中创建一个Person对象地址假设为0x123。栈中的变量p存储了这个地址0x123。changeName(p);将p的值地址0x123复制一份给形参person。此时person也指向了同一个对象0x123。person.setName(李四);通过person这个引用找到堆中的对象修改其属性。因为p和person指向同一个对象所以通过p访问时看到的是修改后的内容。关键点传递的是引用的值地址而不是引用本身。形参和实参是两个不同的变量但它们存储的地址值相同所以操作的是同一个对象。如果尝试修改形参的指向外部不会受影响public static void changeReference(Person person) { person new Person(王五); // 修改形参的引用 System.out.println(方法内姓名 person.getName()); // 王五 }调用Person p new Person(张三); changeReference(p); System.out.println(调用后姓名 p.getName()); // 张三输出方法内姓名王五 调用后姓名张三可见虽然形参person被重新赋值为一个新对象但外部的p仍然指向原来的对象因为形参只是实参的副本改变副本的指向不会影响实参。五、数组和 String 的特殊性5.1 数组数组也是引用类型传递的是数组引用的副本public class ArrayTest { public static void main(String[] args) { int[] arr {1, 2, 3}; changeArray(arr); System.out.println(arr[0]); // 100 } public static void changeArray(int[] a) { a[0] 100; } }因为a和arr指向同一个数组对象所以修改数组内容会反映到外部。5.2 String 的不可变性String 虽然是引用类型但它是不可变的且设计为常量池优化可能会让人误解public class StringTest { public static void main(String[] args) { String s hello; changeString(s); System.out.println(s); // hello } public static void changeString(String str) { str world; } }这里s仍然输出hello。因为str world只是让形参指向了常量池中的另一个字符串并没有改变实参s的引用。如果尝试修改 String 的内容String 没有提供修改方法就更不可能了。六、常见误区与总结误区一“Java 对基本类型是值传递对引用类型是引用传递”这是错误的。Java 对所有类型都是值传递区别仅在于基本类型传递的是数据值。引用类型传递的是引用值内存地址。误区二“传递对象时方法内修改对象属性会影响外部所以是引用传递”这混淆了“传递机制”和“操作结果”。即使形参和实参指向同一对象修改对象属性是“通过引用操作对象”的必然结果但不能因此认为参数传递方式是引用传递。引用传递要求形参和实参本身是同一个变量即形参是实参的别名而 Java 中形参只是实参的副本。误区三“C/C 中可以通过指针实现引用传递Java 没有指针所以只能值传递”Java 的引用本质上就是受限的指针但它不支持直接取地址或指针运算。参数传递时引用的值被复制因此是值传递。七、总结Java 只有值传递没有引用传递。基本类型传递的是数值本身方法内修改形参不影响实参。引用类型传递的是引用的副本地址值方法内通过形参修改对象属性会影响实参指向的对象但若修改形参的指向如person new Person()不影响实参。理解这一机制有助于写出更健壮的代码避免因误以为“引用传递”而犯下的错误。