包装类比较陷阱
生产事故现场,你的订单金额比对出问题了。
java
Integer totalPrice = 200;
Integer discount = 200;
if (totalPrice == discount) {
// 应该走进来,但没走
applyDiscount();
}明明都是 200,为什么 == 比较失败了?
这就是包装类比较的经典陷阱,而且往往在你不注意的时候给你一刀。
为什么 == 有时候不准?
基本类型用 == 比较值,这是天经地义的:
java
int a = 200;
int b = 200;
System.out.println(a == b); // true,值相等包装类用 == 比较的是引用地址,不是值:
java
Integer a = 200; // 自动装箱 → Integer.valueOf(200) → new Integer(200)
Integer b = 200; // 自动装箱 → Integer.valueOf(200) → new Integer(200)
System.out.println(a == b); // false,两个不同的对象问题出在缓存:只有 -128~127 范围的值会被缓存复用。
java
Integer a = 127; // 缓存范围内,返回同一个对象
Integer b = 127;
System.out.println(a == b); // true,碰巧相等
Integer c = 128; // 超出范围,每次 new
Integer d = 128;
System.out.println(c == d); // false,两个不同对象基本类型 vs 包装类:四种组合
java
public class CompareCombinations {
public static void main(String[] args) {
// 组合一:基本类型 vs 基本类型
int primitive1 = 200;
int primitive2 = 200;
System.out.println("int == int: " + (primitive1 == primitive2)); // true ✅
// 组合二:包装类 vs 包装类(在缓存范围)
Integer wrapper1 = 127;
Integer wrapper2 = 127;
System.out.println("Integer == Integer (127): " + (wrapper1 == wrapper2)); // true ✅
// 组合三:包装类 vs 包装类(超出缓存)
Integer wrapper3 = 200;
Integer wrapper4 = 200;
System.out.println("Integer == Integer (200): " + (wrapper3 == wrapper4)); // false ❌
// 组合四:包装类 vs 基本类型
Integer wrapper5 = 200;
int primitive3 = 200;
System.out.println("Integer == int: " + (wrapper5 == primitive3)); // true ✅
// 包装类会自动拆箱,再和基本类型比较
}
}组合四为什么能 work? 因为 wrapper5 == primitive3 实际上被翻译成了 wrapper5.intValue() == primitive3。
自动装箱的坑:与方法参数的暧昧关系
java
public class MethodParamTrap {
public static void main(String[] args) {
// 陷阱一:变量参与装箱
int x = 100;
Integer a = x; // 变量装箱:new Integer(x)
Integer b = 100;
System.out.println("变量装箱 == 字面量: " + (a == b)); // false
// 陷阱二:方法参数
method(100); // 字面量,缓存
method(200); // 超出缓存
}
static void method(Integer param) {
// param == 100 实际上是比较 Integer 和 int
// param.intValue() == 100 → true
// 但如果外部传的是超出缓存的自动装箱...
System.out.println("method: " + (param == 100));
}
}正确的比较方式
方式一:equals() —— 最通用
java
public class EqualsCompare {
public static void main(String[] args) {
Integer a = 200;
Integer b = 200;
// ✅ equals 比较值,永远正确
System.out.println("a.equals(b): " + a.equals(b)); // true
// ✅ equals 不受缓存影响
Integer c = new Integer(200);
Integer d = new Integer(200);
System.out.println("new == new: " + (c == d)); // false
System.out.println("new.equals(new): " + c.equals(d)); // true
}
}方式二:compare() —— 需要排序结果时
java
public class CompareMethod {
public static void main(String[] args) {
// compare 返回 -1、0、1,适合排序场景
System.out.println(Integer.compare(10, 20)); // -1
System.out.println(Integer.compare(20, 20)); // 0
System.out.println(Integer.compare(30, 20)); // 1
// 用于排序
Integer[] nums = {3, 1, 2};
Arrays.sort(nums, Integer::compare);
System.out.println("排序后: " + Arrays.toString(nums));
// 与 Comparator 的结合
List<Integer> list = Arrays.asList(5, 2, 8);
list.sort(Integer::compareTo); // Integer 实现了 Comparable
}
}方式三:Objects.equals() —— null 安全
java
public class NullSafeCompare {
public static void main(String[] args) {
Integer a = null;
Integer b = 100;
// 直接 equals 会 NPE
// System.out.println(a.equals(b)); // NPE
// ✅ Objects.equals 是 null 安全的
System.out.println(Objects.equals(a, b)); // false
System.out.println(Objects.equals(null, null)); // true
// ✅ 还可以自定义 null 时的返回值
String result = Objects.equals(a, b) ? "相等" : "不等";
System.out.println(result);
}
}Float 和 Double 的坑:精度问题
java
public class FloatPrecision {
public static void main(String[] args) {
// 浮点数的 == 永远不可靠
Double d1 = 0.1;
Double d2 = 0.1;
System.out.println("Double 0.1 == 0.1: " + (d1 == d2)); // false(没有缓存)
// 精度问题
double a = 0.1;
double b = 0.2;
System.out.println("0.1 + 0.2 == 0.3: " + (a + b == 0.3)); // false!
// NaN 比较
Double nan1 = Double.NaN;
Double nan2 = Double.NaN;
System.out.println("NaN == NaN: " + (nan1 == nan2)); // false ❌
System.out.println("NaN.equals(NaN): " + nan1.equals(nan2)); // true ✅
// 正负零
Double posZero = 0.0;
Double negZero = -0.0;
System.out.println("0.0 == -0.0: " + (posZero == negZero)); // true
System.out.println("compare(0.0, -0.0): " + Double.compare(posZero, negZero)); // 1
}
}浮点数比较的正确方式:
java
public class CorrectFloatCompare {
// 方式一:使用 BigDecimal(金额计算必须用它)
public static void main(String[] args) {
BigDecimal bd1 = new BigDecimal("0.1");
BigDecimal bd2 = new BigDecimal("0.2");
BigDecimal result = bd1.add(bd2);
System.out.println("BigDecimal 0.1 + 0.2: " + result); // 0.3
System.out.println("equals 0.3: " + result.equals(new BigDecimal("0.3"))); // true
}
// 方式二:容差比较(适用于工程计算)
static boolean doubleEquals(double a, double b, double epsilon) {
return Math.abs(a - b) < epsilon;
}
// 方式三:BigDecimal 字符串构造
static boolean preciseEquals(double a, double b) {
return BigDecimal.valueOf(a).equals(BigDecimal.valueOf(b));
}
}实战避坑指南
场景一:Map 的 key 比较
java
public class MapKeyTrap {
public static void main(String[] args) {
Map<Integer, String> map = new HashMap<>();
map.put(200, "value");
// ❌ 可能找不到
Integer key = 200;
System.out.println("== 查找: " + map.get(key)); // 可能 null
// ✅ 用 equals 查找
System.out.println("equals 查找: " + map.get(key)); // "value"
// HashMap 用 hashCode 定位桶,用 equals 精确匹配
// 所以只要 hashCode 对了,equals 就能找到
}
}场景二:集合排序
java
public class CollectionSort {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(3, 1, 2);
// ✅ 直接排序(Integer 实现了 Comparable)
Collections.sort(list);
System.out.println("排序: " + list);
// ✅ 自定义比较器
list.sort((a, b) -> Integer.compare(b, a)); // 降序
System.out.println("降序: " + list);
}
}场景三:数据库映射
java
public class DbMapping {
public static void main(String[] args) {
// 数据库 ID 通常用 Long(避免 int 溢出)
Long userId = 100L;
Long orderId = 100L;
// ⚠️ Long 的缓存范围也是 -128~127
Long a = 100L;
Long b = 100L;
System.out.println("Long ==: " + (a == b)); // true(在缓存范围)
a = 1000L;
b = 1000L;
System.out.println("Long == (1000): " + (a == b)); // false(超出)
// ✅ 数据库 ID 比较永远用 equals
System.out.println("Long equals: " + a.equals(b)); // true
}
}比较方法总结
| 场景 | 推荐方法 | 原因 |
|---|---|---|
| 基本类型比较 | == | 值比较 |
| 包装类比较 | equals() | 值比较 |
| 需要排序结果 | compare() | 返回 -1/0/1 |
| 可能为 null | Objects.equals() | null 安全 |
| 浮点数比较 | BigDecimal 或容差 | 精度问题 |
| 金额计算 | BigDecimal | 必须精确 |
要点回顾
- 包装类
==比较引用地址,不是值 - 只有 -128~127 的 Integer 有缓存,
==可能碰巧为 true - 永远用
equals()比较包装类的值 - 浮点数
==永远不可靠,用BigDecimal或容差 - NaN 和自身比较也是 false,必须用
isNaN() - 数据库 ID 用
Long,比较用equals()
血的教训:代码里出现
Integer == Integer,review 时直接打回。等线上出事就晚了。
