一、== 与 equals() 的本质区别(根本避免混用的前提)
很多开发者误以为“== 比 equals() 快,优先用 ==”,或“equals() 是比较内容,== 是比较地址”——这种表述不严谨,核心区别在于“比较目标”的定义不同,具体如下表:
| 比较方式 | 比较目标(核心逻辑) | 适用场景 | 典型错误 |
|---|---|---|---|
== | 1. 对 基本数据类型(int/char/double 等):比较「值」是否相等 2. 对 引用数据类型(Integer/String/自定义对象等):比较「内存地址」是否相同(即是否指向同一个对象) | 1. 基本数据类型的值比较 2. 引用类型的“同一对象”判断(如 obj == null) | 用 == 比较 Integer/String 等引用类型的“内容”(如 Integer a = 127; Integer b = 127; a == b 看似对,实则是缓存池的锅) |
equals() | 1. 未重写 equals() 的类(如默认继承 Object 的类):等价于 ==(比较地址) 2. 重写 equals() 的类(如 Integer/String/HashMap):按类的“业务规则”比较「内容」(如 Integer 比较值,String 比较字符序列) | 引用类型的“内容相等”判断(如比较两个 Integer 的值是否相同,两个 String 的字符是否一致) | 对未重写 equals() 的自定义对象用 equals() 比较内容(如 class Person {int age;},new Person(20).equals(new Person(20)) 会返回 false) |
二、Integer 缓存池:为什么 Integer a=127; a==b 成立,128 却不成立?
Integer 是 int 的包装类(引用类型),其缓存池是 Java 为优化性能设计的“对象复用机制”,也是 == 与 equals() 混用最容易踩坑的场景。
1. 缓存池的核心原理
Java 规定:在自动装箱(int 转 Integer)时,对 -128 ~ 127 范围内的 int 值,直接复用缓存池中的 Integer 对象;超出该范围则创建新的 Integer 对象。
具体逻辑在 Integer 源码的 valueOf(int i) 方法中(JDK 8 为例):
java
复制
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high) // 范围判断:默认 -128~127
return IntegerCache.cache[i + (-IntegerCache.low)]; // 复用缓存对象
return new Integer(i); // 超出范围,新建对象
}
// 缓存池的内部实现(静态内部类)
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];
static {
// high 值可通过 JVM 参数 -XX:AutoBoxCacheMax=<size> 调整(但不能小于 127)
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127); // 确保不小于 127
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// 如果参数无效,忽略
}
}
high = h;
// 初始化缓存数组,存入 -128~high 的 Integer 对象
cache = new Integer[(high - low) + 1];
int j = low;
for(int k=0; k<cache.length; k++)
cache[k] = new Integer(j++);
}
private IntegerCache() {} // 私有构造,禁止实例化
}
2. 缓存池的典型场景对比(为什么会踩坑?)
通过具体案例,直观感受 == 与 equals() 在缓存池场景下的差异:
| 案例代码 | 执行结果 | 原因分析 |
|---|---|---|
① Integer a = 127; Integer b = 127; System.out.println(a == b); | true | 127 在缓存池范围内,a 和 b 指向同一个缓存对象,地址相同(== 比较地址为真) |
② Integer a = 128; Integer b = 128; System.out.println(a == b); | false | 128 超出缓存池范围,a 和 b 是两个新创建的 Integer 对象,地址不同(== 比较地址为假) |
③ Integer a = 127; int b = 127; System.out.println(a == b); | true | a(Integer)与 b(int)比较时,a 会自动拆箱为 int,== 直接比较值(127 == 127) |
④ Integer a = new Integer(127); Integer b = 127; System.out.println(a == b); | false | new Integer(127) 强制创建新对象,不复用缓存;b 复用缓存对象,两者地址不同 |
⑤ Integer a = 128; Integer b = 128; System.out.println(a.equals(b)); | true | Integer 重写了 equals(),逻辑是“比较两个 Integer 的 int 值是否相等”(128 == 128),与缓存池无关 |
三、规避陷阱的核心原则(必记!)
无论是否涉及缓存池,只要记住以下 3 条规则,就能彻底避免 == 与 equals() 混用的错误:
1. 基本数据类型:只用 ==,不用 equals()
基本数据类型(int/long/char 等)没有 equals() 方法(调用 equals() 会自动装箱为包装类),直接用 == 比较值即可。
错误示例:int a = 10; int b = 10; System.out.println(a.equals(b));(编译报错,int 不是对象,没有 equals())。
2. 引用数据类型:分场景选择
-
判断“是否为同一个对象” (如
obj == null、单例模式判断):用==; -
判断“内容是否相等” (如比较两个
Integer的值、两个String的字符):必须用equals(),且确保类重写了equals()(Java 内置类如Integer/String/List已重写,自定义类需手动重写)。
关键提醒:对包装类(Integer/Long/Boolean 等),永远用 equals() 比较“值”,不要依赖 ==(避免缓存池范围外的场景出错)。
正确示例:Integer a = 128; Integer b = 128; if (a.equals(b)) { ... }(无论是否在缓存池,都能正确比较值)。
3. 包装类与基本数据类型比较:== 会自动拆箱(慎用)
当包装类(如 Integer)与基本数据类型(如 int)用 == 比较时,包装类会自动拆箱为基本数据类型,此时 == 比较的是“值”,而非地址。
示例:Integer a = 200; int b = 200; System.out.println(a == b);(结果为 true,a 拆箱为 int 后比较值)。
注意:若包装类为 null,拆箱会抛出 NullPointerException(如 Integer a = null; int b = a;),需先判空。
四、总结
-
==的核心:基本类型比“值”,引用类型比“地址”; -
equals()的核心:未重写时等价于==,重写后按“内容规则”比较(如Integer比“值”); -
Integer缓存池:自动装箱时-128~127复用对象,超出则新建,仅影响==比较,不影响equals(); -
避坑口诀:基本类型用
==,引用类型比内容用equals(),包装类比较值优先用equals()。
掌握这些逻辑,就能轻松应对 Java 中 ==、equals() 与缓存池的各类场景,避免低级错误。