Java 永远是值传递(Pass by Value):调用方法时,参数变量中的值会被复制。对象参数复制的不是整个对象, 而是“指向对象的引用值”。这句话是理解 Java 参数行为的钥匙。
Table of contents
Open Table of contents
运行三组对照实验
把下面内容保存为 值传递实验.java,然后运行 javac --release 21 值传递实验.java && java 值传递实验。
public class 值传递实验 {
record 用户(String name) {}
static void 修改数字(int number) { number = 99; }
static void 重新赋值(用户 user) { user = new 用户("新用户"); }
static void 修改列表(java.util.List<String> names) { names.add("新增项"); }
public static void main(String[] args) {
int number = 1;
修改数字(number);
System.out.println(number); // 1
用户 user = new 用户("原用户");
重新赋值(user);
System.out.println(user.name()); // 原用户
var names = new java.util.ArrayList<>(java.util.List.of("原项"));
修改列表(names);
System.out.println(names); // [原项, 新增项]
}
}
前三个结果并不矛盾:数字值被复制;对象引用值也被复制;两个列表引用起初指向同一个可变对象,所以通过任一引用都能 修改那个对象。方法内把参数重新指向新对象,不会改变调用者变量。
基本类型、引用类型与包装类型
int、long、double、boolean 等是基本类型,变量直接保存数值。类、数组、接口和枚举是引用类型,
变量保存可用于定位对象的引用;引用也可以是 null。
Integer 是 int 的包装类型。自动装箱会在需要对象时把 int 转成 Integer,拆箱则反向转换:
Integer boxed = 42; // 自动装箱
int value = boxed; // 自动拆箱
Integer missing = null;
// int broken = missing; // 运行时抛出 NullPointerException
不要用 == 比较包装对象的数值。某些小整数会命中缓存,让错误代码偶尔“看起来正确”。使用
Objects.equals(a, b),或在确认非空后拆箱比较。
类型系统解决什么问题
静态类型系统在编译期检查“这个值能否用于这里”。它能阻止把字符串直接交给整数参数,却不能自动保证字符串 不是空白、金额大于零或订单状态合法。这些属于业务不变量,需要构造器校验、值对象和测试共同保证。
record 金额(java.math.BigDecimal value) {
金额 {
if (value == null || value.signum() < 0) {
throw new IllegalArgumentException("金额不能为负数");
}
}
}
实践规则
- 方法参数优先接收不可变对象,减少“谁修改了它”的追踪成本。
- 返回空集合而不是
null;确实可能缺失的单值可考虑Optional。 - 不要为了避免空指针到处使用
Optional字段,它不替代清晰的数据约束。 - 跨层传递对象时,明确它是快照、可变实体还是只读视图。
下一步
阅读面向对象不是堆类,把类型用于表达职责和业务规则。