← 返回题目列表

Java 是值传递还是引用传递?

高频 中等 第 13 / 32 题 更新于 2026/07/25
Java 基础参数传递值传递

简化版

Java 只有值传递(pass by value),没有引用传递。传基本类型时,拷贝的是值本身;传对象时,拷贝的是引用(地址)的副本——所以方法内能通过这个副本改对象的内容,但无法改变外部变量指向哪个对象。很多人误以为对象是「引用传递」,其实是「把引用当值拷了一份」。

详细版

关键是分清「改对象内容」和「改变量指向」:

① 基本类型:改了不影响外部

void change(int x) { x = 100; }
int a = 1;
change(a);        // a 仍是 1,传的是数值副本

② 对象:能改内容,因为副本和原引用指向同一个对象

void changeName(User u) { u.setName("B"); }
User user = new User("A");
changeName(user);   // user.name 变成 "B",改的是同一个对象

③ 但重新赋值不影响外部:证明是值传递的铁证

void reassign(User u) { u = new User("C"); }   // 让副本指向新对象
User user = new User("A");
reassign(user);     // user 仍是 "A"!外部引用没被改变

如果 Java 是真正的引用传递,reassignu = new User("C") 应该能让外部 user 也指向新对象。但它没有——因为传进去的只是引用的一个副本,改副本的指向不影响原变量

完整版教学

一、把「引用」和「引用传递」分开

混淆的根源是把两个概念搅在一起:

  • 引用(reference):Java 对象变量存的是对象的地址,这个地址就叫引用。
  • 引用传递(pass by reference):一种参数传递机制,指「传的是变量本身,方法内能改变外部变量的指向」。

Java 用引用来操作对象(这没错),但传参时传的是引用的拷贝(值传递)。所以准确说法是:Java 传对象时,按值传递了对象的引用。 传的是「引用这个值」,不是「变量本身」。

二、一张图看懂对象传参

外部:  user ──────────┐

                  [ User对象: name="A" ]

方法内:  u(副本)─────┘        ← u 是 user 的拷贝,两个引用指向同一对象
  • u.setName("B"):顺着 u 找到那个对象、改它的字段 → 因为 u 和 user 指向同一对象,外部可见 ✅
  • u = new User("C"):让 u 改指向另一个新对象 → 只动了副本 u,user 还指着老对象 ❌

理解这张图,所有「Java 传参」的题都能推出来。

三、经典陷阱题:交换两个对象

void swap(User a, User b) {
    User tmp = a; a = b; b = tmp;   // 只交换了副本的指向
}
User x = new User("X"), y = new User("Y");
swap(x, y);
// x 还是 "X",y 还是 "Y",交换失败!

swap 里交换的是副本 ab 的指向,对外部 xy 毫无影响。这是「Java 是值传递」的经典证明题。想真正交换,得交换对象的内容(字段),或返回新值,而不是交换引用。

四、String 和包装类为什么「像值传递」

void change(String s) { s = "new"; }
String str = "old";
change(str);   // str 仍是 "old"

有人以为这说明 String 是特殊的值传递。其实规则完全一样:sstr 引用的副本,s = "new" 只是让副本指向新字符串,不影响 str。加上 String 不可变(没有能改内容的方法),所以你连「改对象内容」这条路都没有——看起来就特别像「传了个值改不动」。本质还是值传递。

五、从 API 设计看副作用边界

参数按值传递不代表方法没有副作用。引用副本仍能到达同一个可变对象,所以 sort(list)user.setName() 都会改变调用方可观察的状态。设计 API 时应明确方法是“原地修改”还是“返回新值”,否则调用方即使理解值传递,也可能误判对象是否会变化。

void addTag(List<String> tags) { tags.add("hot"); }       // 改共享对象
void reset(List<String> tags) { tags = new ArrayList<>(); } // 只改局部副本
List<String> withTag(List<String> tags) {                   // 返回独立结果
    List<String> copy = new ArrayList<>(tags);
    copy.add("hot");
    return copy;
}

假设外部列表初始有 2 个元素:调用 addTag 后同一个列表变成 3 个元素;调用 reset 后外部仍是 2 个;调用 withTag 则外部保持 2 个、返回的新列表有 3 个。三种结果完全由“是否沿引用修改对象”和“是否重新绑定局部参数”决定。

方法内操作外部变量指向外部可见对象内容
给基本类型参数重新赋值不变不适用
给引用参数重新赋值不变原对象不变
通过参数调用 setter/add不变会变化
返回新对象并由调用方接收由调用方决定可切换到新结果

六、常见误区与追问

  • 误区:Java 对基本类型值传递、对对象引用传递。 两者都复制参数值,只是对象变量的值恰好是引用。
  • 误区:方法能改对象字段就证明传了变量本身。 两个不同引用值指向同一对象,也能观察到同一处修改。
  • 误区:给参数赋 null 能清空调用方变量。 它只清空局部副本,外部引用仍保持原指向。
  • 追问:数组传参是什么规则? 数组也是对象,传入的是数组引用的副本;能改元素,不能替换外部数组变量。
  • 追问:如何让方法“改变调用方的指向”? Java 没有这种参数机制,可返回新引用并让调用方赋值,或把引用放进可变 holder 后修改其字段。
  • 追问:不可变对象为何看起来像基本类型? 方法无法原地改其内容,只能让局部引用指向新对象,但传递规则没有变化。

判断心法:画两个变量和一个对象;重新画箭头是局部赋值,沿箭头改方框内容才会被双方看到。

七、加强记忆

Java 每次调用都会为形参创建一份值副本:基本类型复制数值,对象类型复制引用值。复制后的两个引用能共同访问一个对象,因此字段或集合元素的修改对外可见;但给形参重新赋值只改变局部箭头,无法重绑调用方变量。把“变量、引用值、对象”分成三层,再结合 API 是否允许修改对象,就能解释交换失败、String 不变、数组可改等所有常见题型。