【Java】字符串相等判断:从源码到实战,避坑指南

大家好,我是云扬~ 实际开发中,字符串相等判断是高频场景,但不少同学会在 ==equals() 之间踩坑,甚至忽略更优雅的进阶方案。今天就结合源码和实战场景,彻底搞懂 Java 字符串相等的判断逻辑!

一、核心疑问:==equals() 到底差在哪?

1.1 本质区别

  • ==比较对象内存地址(判断是否是同一个对象)
  • equals()比较对象内容(需子类重写,否则等价于 ==

1.2 根源:Object 类的默认实现

Java 中所有类都继承自 Object,其 equals() 源码如下:

// Object 类的 equals() 源码
public boolean equals(Object obj) {
    return (this == obj); // 直接用 == 比较地址
}

如果子类(比如 String)不重写 equals(),调用时本质还是比较地址 —— 这也是很多新手踩坑的核心原因!

二、String 类的 equals():源码深度解析

String 重写了 equals() 方法,专门用于比较字符串内容,JDK8 和 JDK17 实现略有差异,但核心逻辑一致。

2.1 JDK8 版本源码

public boolean equals(Object anObject) {
    // 1. 先判断是否是同一个对象(地址相等),是则直接返回 true
    if (this == anObject) {
        return true;
    }
    // 2. 判断参数是否是 String 类型,不是则返回 false
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        int n = value.length;
        // 3. 长度不同直接返回 false(优化性能)
        if (n == anotherString.value.length) {
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = 0;
            // 4. 逐个字符比较
            while (n-- != 0) {
                if (v1[i] != v2[i])
                    return false;
                i++;
            }
            return true;
        }
    }
    return false;
}

2.2 JDK17 版本源码(简化版)

public boolean equals(Object anObject) {
    // 1. 地址相等直接返回 true(同 JDK8)
    if (this == anObject) {
        return true;
    }
    // 2. 优化类型判断:直接用 getClass() 而非 instanceof(避免子类冒充)
    if (anObject instanceof String s) {
        // 3. 按字符编码比较(支持 UTF16/Latin1,逻辑更灵活)
        return compareTo(s) == 0;
    }
    return false;
}

核心优化:JDK17 用 compareTo() 统一字符比较逻辑,同时通过 instanceof String s 简化类型转换,性能和可读性更优。

三、字符串常量池:为什么不推荐 new String()

要彻底理解判断逻辑,必须先搞懂 字符串常量池(String Constant Pool):

  • 直接赋值:String s = "云扬" → 优先从常量池获取对象,若不存在则创建并缓存
  • new 关键字:String s = new String("云扬") → 必然在堆内存创建新对象,同时常量池会同步创建该字符串(若未存在)

示意图:

常量池:["云扬"]
堆内存:new String("云扬") → 新地址

这就是为什么 new String("云扬") == "云扬" 会返回 false—— 两者内存地址完全不同!

四、实战例题:6 种场景全覆盖

直接上代码,结合注释理解:

public class StringEqualsTest {
    public static void main(String[] args) {
        // 1. new String 与常量池字符串比较(内容相同,地址不同)
        System.out.println(new String("云扬").equals("云扬")); // true(equals 比内容)
        
        // 2. new String 与常量池字符串用 == 比较
        System.out.println(new String("云扬") == "云扬"); // false(地址不同)
        
        // 3. 两个 new String 比较(堆中两个不同对象)
        System.out.println(new String("云扬") == new String("云扬")); // false(地址不同)
        
        // 4. 常量池字符串直接比较(同一对象)
        System.out.println("云扬" == "云扬"); // true(常量池缓存复用)
        
        // 5. 常量字符串拼接(编译器优化为完整字符串)
        System.out.println("云扬" == "云" + "扬"); // true(编译后为 "云扬")
        
        // 6. intern() 方法:将堆中字符串入池并返回常量池引用
        System.out.println(new String("云扬").intern() == "云扬"); // true(intern() 返回常量池对象)
    }
}

运行结果:true false false true true true,完全符合预期!

五、进阶方案:更优雅的字符串比较

除了 String.equals(),还有两个场景化更强的方案,避免踩坑同时提升代码健壮性。

5.1 Objects.equals ():避免空指针异常

直接用 a.equals(b) 时,若 anull 会抛出 NullPointerException,而 Objects.equals() 完美解决这个问题:

import java.util.Objects;

public class ObjectsEqualsTest {
    public static void main(String[] args) {
        String a = null;
        String b = "云扬";
        
        // 风险:a 为 null 时抛出空指针
        // System.out.println(a.equals(b)); 
        
        // 安全:无需判空,直接比较
        System.out.println(Objects.equals(a, b)); // false
        System.out.println(Objects.equals("云扬", b)); // true
    }
}

其源码逻辑(简单高效):

public static boolean equals(Object a, Object b) {
    return (a == b) || (a != null && a.equals(b));
}

5.2 String.contentEquals ():兼容所有字符序列

如果需要比较 StringStringBufferStringBuilderCharSequence 实现类,contentEquals() 是最优选择(自动处理同步和类型适配):

public class ContentEqualsTest {
    public static void main(String[] args) {
        String s = "云扬";
        StringBuffer sb = new StringBuffer("云扬");
        StringBuilder sbd = new StringBuilder("云扬");
        
        // 支持 StringBuffer(自动同步)
        System.out.println(s.contentEquals(sb)); // true
        // 支持 StringBuilder
        System.out.println(s.contentEquals(sbd)); // true
        // 支持 String(等价于 equals())
        System.out.println(s.contentEquals("云扬")); // true
    }
}

JDK17 源码优化:对 StringBuffer 自动加锁同步,避免线程安全问题,无需手动处理。

六、核心总结:如何选择比较方式?

场景推荐方案注意事项
两个 String 比较内容Objects.equals(a, b)避免空指针,最常用
比较 String 与 CharSequencecontentEquals()兼容 StringBuffer/StringBuilder
判断是否是同一个字符串对象==仅用于常量池复用场景(谨慎使用)

最后记住:字符串比较,优先用 Objects.equals(),除非明确需要判断对象身份(地址),否则坚决不用 ==

以上就是字符串相等判断的完整知识点啦~ 实际开发中你还遇到过哪些相关坑?或者有其他场景想补充?欢迎在评论区留言讨论,我会第一时间回复!如果需要获取文中完整代码示例,也可以私信我哦~

Tags:

发表回复

Your email address will not be published. Required fields are marked *.

*
*