Java为什么说只有值传递没有引用传递详解
前言
做Java开发久了,总会被一个问题绕晕:Java到底是值传递还是引用传递?尤其是刚接触对象和引用的时候,写个简单的交换方法,结果和预期不一样,就更懵了。
其实答案很明确:Java里只有值传递,没有引用传递。很多人之所以纠结,核心是把“引用”和“引用传递”搞混了——以为传递的是对象引用,就是引用传递,这其实是个误区。
先明确什么是值传递、什么是引用传递?
要搞懂Java的传递方式,先把最基础的两个概念拎清楚,不用记教科书上的晦涩定义,记住核心区别就好:
无论是值传递还是引用传递,本质都是“方法参数的传递机制”,关键看传递的是“值的副本”还是“变量本身”:
• 值传递(Pass by Value):传递的是参数的“副本”——方法接收的,不是原变量本身,只是原变量的值的一份拷贝。无论在方法内部怎么修改这个副本,都不会影响原变量的值。
• 引用传递(Pass by Reference):传递的是参数“本身”——方法接收的是原变量的内存地址,不是副本。在方法内部修改这个参数,会直接影响原变量的值。
简单说:值传递是“复制一份给你,你改你的,我不变”;引用传递是“把我的东西给你,你改了,我也会变”。
而Java的核心原则就是:无论传递的是基本类型,还是对象引用,本质都是值传递——区别只在于,传递的“值”到底是“基本类型的值”,还是“对象引用的值(内存地址)”。
分两种场景拆解
我们日常开发中,传递参数无非两种情况:基本类型(int、char、boolean等)和引用类型(对象、数组、集合等),分别拆解,就能彻底明白。
场景1:基本类型参数传递
基本类型的传递,完全符合值传递的定义,也是最容易理解的场景。
举个日常开发中最常见的例子——写一个方法,试图修改一个int变量的值:
public class ValuePassTest {
public static void main(String[] args) {
int num = 10;
System.out.println("调用方法前,num的值:" + num); // 输出10
changeNum(num);
System.out.println("调用方法后,num的值:" + num); // 还是输出10
}
// 试图修改基本类型参数的值
public static void changeNum(int num) {
num = 20; // 这里修改的是“副本”的值
System.out.println("方法内部,num的值:" + num); // 输出20
}
}运行结果很明确:方法内部修改了num的值,但main方法里的原变量num,根本没变化。
原因很简单:
当调用changeNum(num)时,Java会把main方法里num的值(10),复制一份,传递给changeNum方法的参数num——这两个num,虽然名字一样,但却是两个完全不同的变量,占用不同的内存空间。
方法内部修改的,只是“副本”的num,和原变量num没有任何关系,所以原变量的值不会变。这就是典型的值传递。
场景2:引用类型参数传递
这是大家最容易纠结的场景:传递对象的时候,明明在方法内部修改了对象的属性,原对象的属性也跟着变了,这不就是引用传递吗?
其实不是。我们还是用实际开发中的例子来说明,先看一段代码:
// 定义一个简单的User类,日常开发中很常见
class User {
String name;
int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
}
public class ValuePassTest {
public static void main(String[] args) {
User user = new User("张三", 20);
System.out.println("调用方法前,user的年龄:" + user.age); // 输出20
changeUserAge(user);
System.out.println("调用方法后,user的年龄:" + user.age); // 输出30
}
// 试图修改User对象的年龄
public static void changeUserAge(User userParam) {
userParam.age = 30; // 修改对象的属性
System.out.println("方法内部,userParam的年龄:" + userParam.age); // 输出30
}
}这里看起来,方法内部修改了userParam的age,原对象user的age也跟着变了,好像是“引用传递”——但其实,这依然是值传递。
关键在于:引用类型传递的“值”,是对象的引用地址,而不是对象本身。
我们拆解一下这个过程,就很清楚了:
1. main方法中,User user = new User("张三", 20); —— 这里的user,是一个引用变量,它存储的不是对象本身,而是对象在内存中的地址(比如:0x123456)。
2. 调用changeUserAge(user)时,Java会把user变量存储的“地址值(0x123456)”,复制一份,传递给changeUserAge方法的参数userParam。
3. 此时,user和userParam两个引用变量,存储的是同一个地址值——也就是说,它们指向内存中同一个User对象。
4. 方法内部,userParam.age = 30; —— 这里修改的不是“引用变量的值(地址)”,而是“地址指向的对象的属性”。因为两个引用指向同一个对象,所以原对象的属性也会跟着变。
重点来了:方法内部并没有修改“引用变量本身的值(地址)”,只是通过地址,修改了地址指向的对象的属性。这依然是值传递——传递的是“地址值的副本”,而不是引用本身。
再做一个测试:修改引用变量的地址,看原对象是否变化
如果真的是引用传递,那么在方法内部修改引用变量的地址(让它指向新的对象),原引用变量也应该跟着指向新对象。我们来测试一下:
class User {
String name;
int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
}
public class ValuePassTest {
public static void main(String[] args) {
User user = new User("张三", 20);
System.out.println("调用方法前,user的名字:" + user.name); // 输出张三
changeUserReference(user);
System.out.println("调用方法后,user的名字:" + user.name); // 还是输出张三
}
// 试图修改引用变量的地址(指向新对象)
public static void changeUserReference(User userParam) {
// 让userParam指向一个新的User对象
userParam = new User("李四", 25);
System.out.println("方法内部,userParam的名字:" + userParam.name); // 输出李四
}
}运行结果很明显:方法内部,userParam指向了新的对象,但main方法里的原user引用,依然指向原来的“张三”对象,没有任何变化。
这就彻底证明了:Java传递引用类型时,传递的依然是“值”(地址的副本)。方法内部修改的是“副本的地址”,和原引用变量的地址没有关系,所以原引用变量不会跟着变。
如果是真正的引用传递,修改方法参数的地址,原引用变量也会跟着指向新对象——但Java里根本不会出现这种情况。
为什么大家总误以为Java有引用传递?
其实核心原因就一个:混淆了“对象引用”和“引用传递”。
我们常说“传递对象”,其实本质是“传递对象的引用地址”——这个地址是一个“值”,传递这个地址的副本,依然是值传递。
举个生活化的例子,更容易理解:
你有一把钥匙,能打开你家的门(钥匙 = 引用地址,家门 = 对象)。你把钥匙复制了一把,给了朋友(传递的是钥匙的副本 = 值传递)。
朋友用复制的钥匙,打开了你家的门,把家里的家具换了(修改对象的属性)——你回家后,看到的就是换了家具的家(原对象属性变化)。
但如果朋友把复制的钥匙,换成了别人家的钥匙(修改副本的地址,指向新对象)——你的钥匙依然能打开你家的门,不会受到任何影响(原引用变量不变)。
这就是Java的传递机制:无论传递基本类型还是引用类型,都是“复制一份给你”,你改你的副本,不会影响原变量本身;如果是引用类型,你只是通过副本地址,操作了同一个对象而已。
常见误区
结合平时开发中容易踩的坑,再拆解两个常见误区,帮大家彻底避开混淆。
误区1:“传递对象就是引用传递”
错。传递的不是对象本身,是对象的引用地址,而这个地址是一个“值”,传递地址的副本,依然是值传递。
记住:Java中,对象永远不会被直接传递,传递的只能是对象的引用地址,而地址的传递,是值传递。
误区2:“方法能修改原对象的属性,就是引用传递”
错。修改对象属性,是因为“副本地址和原地址指向同一个对象”,不是因为传递了引用本身。
关键判断标准:看方法内部能否修改“原变量本身”——基本类型改不了,引用类型也改不了(改的是地址副本,不是原引用),这就是值传递的核心特征。
误区3:“数组、集合的传递是引用传递”
和对象一样,数组、集合本质也是引用类型,传递的依然是“引用地址的副本”,属于值传递。
比如修改数组的元素,原数组会变化(因为指向同一个数组);但修改数组引用的地址(让它指向新数组),原数组引用不会变化。
最后总结
Java里只有值传递,没有引用传递。
传递基本类型时,传递的是“基本类型值的副本”;传递引用类型时,传递的是“对象引用地址的副本”。
方法内部能修改的,要么是“副本的值”(不影响原变量),要么是“副本地址指向的对象属性”(影响原对象,但不影响原引用)——永远改不了原变量本身(无论是基本类型变量,还是引用类型变量)。
搞懂这一点,以后写代码时,就不会再为“参数传递后为什么没变化”而困惑,也能轻松避开相关的坑。
到此这篇关于Java为什么说只有值传递没有引用传递的文章就介绍到这了,更多相关Java只有值传递内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
解析Spring Data JPA的Audit功能之审计数据库变更
Spring Data JPA 提供了Audit审计功能,用来记录创建时间、创建人、修改时间、修改人等,下面来详细讲解下审计数据库变更2021-06-06
详解Spring DeferredResult异步操作使用场景
本文主要介绍了Spring DeferredResult异步操作使用场景,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下2021-10-10
AsyncHttpClient exception异常源码流程解析
这篇文章主要为大家介绍了AsyncHttpClient的exception源码流程解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪2023-12-12
JavaWeb开发之Spring+SpringMVC+MyBatis+SpringSecurity+EhCache+JC
这篇文章主要介绍了JavaWeb开发之Spring+SpringMVC+MyBatis+SpringSecurity+EhCache+JCaptcha 完整Web基础框架的相关资料,需要的朋友可以参考下2016-12-12


最新评论