Java 是值传递还是引用传递?为什么方法里能修改对象,却换不掉外面的引用?
下面是一段教学用的模拟面试。
🧑💻 面试官:Java 参数是值传递还是引用传递?
🙋♂️ 我:基本类型是值传递,对象是引用传递。
🧑💻 面试官:那方法里把参数改成一个新 User,为什么外面的变量还指向旧对象?
🙋♂️ 我:因为传进来的引用也是一份副本。
🧑💻 面试官:既然是副本,为什么修改参数对象的 name,外面又能看见?到底复制了什么?
Java 始终传值。对象参数复制的是「引用值」,不是对象本身,也不是调用方的变量。
面试速答(60 秒版)
Java 是值传递。调用方法时,实参的值被复制给形参;基本类型复制数值等值,对象参数复制的是引用值。
如果外部变量 user 和方法参数 p 的引用值相同,它们就指向同一个对象。通过 p 修改这个对象的字段,外部通过 user 读取同一对象,自然能看到修改。
但是给 p 重新赋值,只会让方法里的 p 指向另一个对象,不会把调用方的 user 一起改掉。因为它们是两个变量,不是同一个变量的别名。
所以我会把“修改对象”和“重新绑定形参”分开解释。需要替换外部结果时,可以明确返回新对象,让调用方赋值,而不是指望方法里换掉参数就能替换调用方引用。

知识点详解:复制了一份引用,为什么还有共享修改?
先分清变量、引用值和对象
咱们假设外面有一个变量 user,它的值是指向用户对象 A 的引用。
变量是当前保存值的位置;引用值用于访问某个对象;对象则有自己的字段,比如 name。它们不是同一件事。
调用方法时,方法创建自己的形参 p,并把 user 的引用值复制过去。此时 user 和 p 是两个变量,只是它们都指向 A。
因此,这次复制没有创建一个新 User,也没有把 user 本身交给方法控制。为了讲解方便,可以把引用画成箭头,但不要把示意编号当成 Java 暴露的真实内存地址。
改字段,改变的是同一个对象
假设方法通过 p 把 A.name 从“小明”改成“小林”。
这一步改变的不是 p 的引用值,而是箭头指向的对象内容。user 仍然指向 A,只是 A 的内容已经变了。因此方法返回后,通过 user.name 就能读到“小林”。
这不需要引用传递才能实现。只要复制后的引用仍指向同一个可变对象,就有这种共享修改。
类似地,数组也是对象。方法可以通过数组参数修改元素,调用方能看到这些元素的变化;但是把形参改成另一个新数组,仍然只是在修改形参。
换引用,改变的只是方法里的变量
现在方法又创建用户对象 B,然后让 p 指向 B。
这一步改变的是 p 保存的引用值。user 没有被重新赋值,所以仍然指向 A。B 不会凭空替换外面的 A。
如果前面已经修改了 A.name,这个修改仍然存在。不能因为后来 p 换成 B,就认为对 A 的修改被撤销了。
完整过程是:开始时两份引用都指向 A;修改 A 的字段;最后 p 指向 B,user 仍指向已经被修改的 A。把三步画出来,“能改字段却换不掉外部引用”就不矛盾了。

swap 为什么经常交换失败?
假设外面有两个变量 a、b,方法接收参数 x、y,然后交换 x、y。
交换的只是方法自己的两份值,调用方的 a、b 没有被赋值。基本类型如此,对象引用也是如此。
如果真的要交换调用方持有的结果,可以返回一组交换后的值,让调用方重新赋值;或者操作一个明确共享的容器内部元素。后者改的是容器对象内容,不是突然让 Java 变成了引用传递。
同样,String 是不可变对象。方法中看起来“修改字符串”的操作通常产生新字符串,并给局部变量重新赋值,不会直接改变原字符串对象。
这里依据 Oracle 方法参数说明 核验 Java 语义。把两个变量和它们指向的对象分开画,就更容易看清这次究竟复制了什么。
面试官继续追问
final 参数会不会让对象也不能改?
final 限制该变量被重新赋值,不自动冻结它指向的对象。对象是否可变,要看对象本身的设计。
引用值复制,算不算复制对象?
不算。对象仍然是同一个。需要独立对象时,要根据类型和嵌套结构明确执行复制,而不是把参数传递当成对象复制。
为什么有人把对象传递叫引用传递?
通常是在用口语描述“传了对象引用”,但这会误导变量绑定的行为。面试中最好明确:传的是引用值的副本,Java 的传递方式仍然是值传递。
面试速记卡
- 总结:Java 始终是值传递。
- 基本类型:复制值;对象参数:复制引用值。
- 修改字段:通过两份引用访问同一个对象,调用方可见。
- 形参赋值:只改变方法自己的变量,不替换调用方变量。
- final:限制引用变量重赋值,不自动保证对象不可变。
公司面试真题
这道题暂未收录可核验的公司真题来源。你可以先阅读本文解析,或浏览已收录的公司面试真题。
浏览公司面试真题 →