==与equals()混用陷阱,以及Integer缓存池原理和使用场景

4 阅读5分钟

一、== 与 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);true127 在缓存池范围内,a 和 b 指向同一个缓存对象,地址相同(== 比较地址为真)
② Integer a = 128; Integer b = 128; System.out.println(a == b);false128 超出缓存池范围,a 和 b 是两个新创建的 Integer 对象,地址不同(== 比较地址为假)
③ Integer a = 127; int b = 127; System.out.println(a == b);trueaInteger)与 bint)比较时,a 会自动拆箱为 int== 直接比较值(127 == 127)
④ Integer a = new Integer(127); Integer b = 127; System.out.println(a == b);falsenew Integer(127) 强制创建新对象,不复用缓存;b 复用缓存对象,两者地址不同
⑤ Integer a = 128; Integer b = 128; System.out.println(a.equals(b));trueInteger 重写了 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);(结果为 truea 拆箱为 int 后比较值)。
注意:若包装类为 null,拆箱会抛出 NullPointerException(如 Integer a = null; int b = a;),需先判空。

四、总结

  1. == 的核心:基本类型比“值”,引用类型比“地址”;

  2. equals() 的核心:未重写时等价于 ==,重写后按“内容规则”比较(如 Integer 比“值”);

  3. Integer 缓存池:自动装箱时 -128~127 复用对象,超出则新建,仅影响 == 比较,不影响 equals()

  4. 避坑口诀:基本类型用 ==,引用类型比内容用 equals(),包装类比较值优先用 equals()

掌握这些逻辑,就能轻松应对 Java 中 ==equals() 与缓存池的各类场景,避免低级错误。