自动装箱是 Java 在基本类型(int、)和它们的(、)之间的自动转换。虽然很方便,但存在微妙的陷阱——性能开销、令人惊讶的 行为以及 风险。
doubleIntegerDouble==NullPointerExceptionInteger boxed = 42; // autoboxing: int → Integer (Integer.valueOf(42))
int unboxed = boxed; // auto-unboxing: Integer → int (boxed.intValue())
List<Integer> nums = new ArrayList<>();
nums.add(5); // autoboxes int 5 → Integer (collections need objects)
int x = nums.get(0); // auto-unboxes Integer → int
之所以自动发生,是因为集合和泛型需要对象(不能有 List<int>),所以 Java 透明地装箱基本类型。
Integer a = 1000;
Integer b = 1000;
a == b; // ❌ FALSE — different Integer OBJECTS (reference comparison)
a.equals(b); // ✅ true — value comparison
// the WORSE trap — the Integer cache makes small values seem to work:
Integer c = 100, d = 100;
c == d; // TRUE — Java CACHES Integers from -128 to 127 (same object)
Integer e = 200, f = 200;
e == f; // FALSE — outside the cache range → different objects
这很隐蔽:在 Integer 上使用 == 对小值有效(被缓存),但对大值失败 —— 在测试中看似正确的代码在生产环境中会崩溃。对于包装器值的比较,始终使用 .equals(),或者先拆箱为基本类型。
Integer value = null; // a wrapper can be null
int x = value; // 💥 NullPointerException — unboxing null calls null.intValue()
Map<String, Integer> map = new HashMap<>();
int count = map.get("missing"); // 💥 NPE — get() returns null, then unboxing fails
拆箱 null 包装器会抛出 NPE —— 这是一个常见的、令人惊讶的崩溃,特别是在返回 null 的 map 查询中。
// ❌ autoboxing in a hot loop — creates millions of Integer objects (GC pressure, slow)
Long sum = 0L; // WRONG type — wrapper
for (long i = 0; i < 1_000_000; i++) {
sum += i; // unbox, add, re-box → new Long each iteration!
}
// ✅ use primitives in hot paths
long sum = 0L; // primitive — no boxing
反复装箱/拆箱会创建过多对象,在紧密循环中影响性能。
自动装箱很方便且无处不在(集合、泛型都依赖它),但它的陷阱会导致真实的、难以诊断的 bug。
== 陷阱特别危险:用 == 比较 Integer 对于缓存的小值(-128 到 127)有效,但对更大的值失败 —— 这是一个在测试中通过但在生产环境中崩溃的 bug —— 使得 .equals()(或拆箱)对包装器比较至关重要。NullPointerException 来自拆箱 null 包装器(常见于 map 查询)是另一个频繁出现的崩溃。
而且在热循环中装箱会创建不必要的对象,影响性能。
理解何时发生装箱、值与引用比较问题、null 拆箱风险,以及在性能关键代码中使用基本类型,对编写正确、高效的 Java 至关重要 —— Integer 缓存的 == 行为是一个经典面试题,能表现出深层次的理解。