Java只有值传递,因为方法调用时基本类型复制数值、引用类型复制地址值,均写入新栈帧局部变量表,JVM字节码指令(iload/aload+store)证实仅为值拷贝。

Java 中“只有值传递”不是一句口号,而是由 JVM 内存模型和字节码执行逻辑共同决定的底层事实。要真正吃透它,不能只记结论,得看到栈帧怎么压、地址怎么拷贝、对象怎么寻址。
栈与堆的分工是理解起点
Java 方法调用时,每个线程都有自己的虚拟机栈,每次调用生成一个栈帧。栈帧里有局部变量表、操作数栈等——而参数就存在局部变量表中。
- 基本类型(如 int、boolean):变量名和值(比如 42、true)直接存进栈帧的局部变量表,传参就是把那个数值原样复制一份放进新栈帧的对应槽位。
- 引用类型(如 ArrayList、String、自定义对象):栈帧局部变量表里存的不是对象本身,而是一个 32 或 64 位的内存地址(比如 0x7f8a1c3d),这个地址指向堆中真正的对象;传参时,复制的是这个地址值,不是堆里的对象,更不是“引用本身”。
字节码层面看“复制”动作
用 javap -c 反编译一个含参数的方法,你会看到 iload、aload 等指令——它们干的事,就是从当前栈帧的局部变量表里“读出一个值”,再把它“压入新栈帧的局部变量表”。
- 对 int 参数:iload_0 → 把第 0 个槽位的整数值取出来 → astore_1(在目标方法中)→ 存进新栈帧第 1 个槽位。这是纯数值拷贝。
- 对 Object 参数:aload_0 → 把第 0 个槽位的引用地址取出来 → astore_1 → 存进新栈帧第 1 个槽位。这是地址值拷贝,不是对象克隆,也不是指针传递。
JVM 规范明确要求:方法调用必须创建新的局部变量表,所有参数都通过 store 指令写入其中——这本身就是“值传递”的字节码证据。
为什么改对象内容会“生效”,但换引用却无效?
关键不在“传了什么”,而在“后续操作作用于谁”。
- 当你写 list.add("x"):形参 list 拿到的是地址副本,但它仍指向堆中同一个 ArrayList 对象;add() 修改的是堆中那个对象的内部数组,所以实参看到变化——这不是引用传递,是“两个引用指向同一块堆内存”带来的副作用。
- 当你写 list = new ArrayList():只是把形参所在栈帧里的地址槽(比如 slot 1)覆盖成新地址,实参栈帧里的原地址(slot 0)完全没动,自然不影响外部变量。
String 和数组是典型验证场
它们都是引用类型,但行为极具教学意义:
- String 是不可变的:str = "hello"; 调用 foo(str) 后在方法里 str = "world"; —— 实际是新建字符串对象,把新地址赋给形参变量,原 str 变量仍指向 "hello"。GC 会回收无人引用的旧对象。
- 数组是可变容器:int[] arr = {1}; foo(arr); 方法内 arr[0] = 99; —— 直接修改堆中数组第一个元素,main 里 arr[0] 就变成 99;但若写 arr = new int[]{2};,则只改形参引用,原 arr 不变。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











