五分钟学Java:如何比较 Java 的字符串?

简介: 五分钟学Java:如何比较 Java 的字符串?

在逛 Stack Overflow 的时候,发现了一些访问量像喜马拉雅山一样高的问题,比如说这个:如何比较 Java 的字符串?访问量足足有 370万+,这不得了啊!说明有很多很多的程序员被这个问题困扰过。


我们来回顾一下提问者的问题:


截止到目前为止,我一直使用“”操作符来比较字符串,直到程序出现了一个 bug,需要使用 .equals() 方法来解决。这是为什么呢?“”操作符和 .equals() 方法之间有什么区别呢?

和提问者相反,在我刚开始学习 Java 的时候,比较字符串一直使用的是 .equals() 方法,因为不管是书本还是老师,都告诫我不要直接使用“==”操作符来比较,会出 bug。至于为什么,书本和老师都没有帮我搞清楚。


那借此机会,我就来梳理一下 Stack Overflow 上的高赞答案,我们来一起学习进步,打怪升级。


“==”操作符用于比较两个引用(内存中的存放地址)是否相等,它们是否是同一个对象。


.equals() 用于比较两个对象的内容是否相等。


怎么理解这两句话呢?我来举个不恰当又很恰当的例子。


有一对双胞胎,姐姐叫阿丽塔,妹妹叫洛丽塔。我们普通人的眼睛完全无法分辨谁是姐姐谁是妹妹,可她们的妈妈却可以轻而易举地辨认出。



.equals() 就好像我们普通人,看见阿丽塔以为是洛丽塔,看见洛丽塔以为是阿丽塔,看起来一样就觉得她们是同一个人;“==”操作符就好像她们的妈妈,要求更严格,观察更细致,一眼就能分辨出谁是姐姐谁是妹妹。


String alita = new String("小萝莉");

String luolita = new String("小萝莉");


System.out.println(alita.equals(luolita)); // true

System.out.println(alita == luolita); // false



就上面这段代码来说,.equals() 输出的结果为 true,而“==”操作符输出的结果为 false——前者没后者要求那么严格。


大家都知道,Java 的所有类都默认地继承着 Object 这个超类,该类有一个名为 .equals() 的方法,源码如下所示。


public boolean equals(Object obj) {

   return (this == obj);

}



可以看得出,Object 类的 .equals() 方法默认采用的是“”操作符进行比较。假如子类没有重写该方法的话,那么“”操作符和 .equals() 方法的功效就完全一样——比较两个对象的内存地址或者对象的引用是否相等。


但实际情况中,有不少类重写了 .equals() 方法,因为比较内存地址太重了,不太符合现实的场景需求。String 类就重写了 .equals() 方法,源码如下所示。


public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        int n = value.length;
        if (n == anotherString.value.length) {
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = 0;
            while (n-- != 0) {
                if (v1[i] != v2[i])
                    return false;
                i++;
            }
            return true;
        }
    }
    return false;
}

可以看得出,如果两个字符串对象“==”,那么 .equals() 的结果就为 true;否则的话,就比较两个字符串的内容是否相等。


大家应该都知道了,创建字符串对象有两种写法,如下所示。


String luolita = "小萝莉";

String alita = new String("小萝莉");



第一种是在字符串常量池(存储在方法区)中创建对象,并将对象的引用赋值给 luolita。第二种是通过 new 关键字在堆中创建对象,并将对象引用赋值给 alita。


PS:字符串作为最基础的数据类型,使用非常频繁,如果每次都通过 new 关键字进行创建,会耗费高昂的时间和空间代价。Java 虚拟机为了提高性能和减少内存开销,就设计了字符串常量池:相同字面量的对象只有一个。


PPS:Java 虚拟机在执行程序的过程中会把内存区域划分为若干个不同的数据区域,如下图所示。


image.png


下面我们通过实际代码来看看字符串的比较。


第一种:


new String("小萝莉").equals("小萝莉") // --> true

1

.equals() 比较的是两个字符串对象的内容是否相等,所以结果为 true。


第二种:


new String("小萝莉") == "小萝莉" // --> false

1

“==”操作符左侧的对象存储在堆中,右侧的对象存储在方法区,所以返回 false。


第三种:


new String("小萝莉") == new String("小萝莉") // --> false

1

new 出来的两个对象肯定是不相等的,所以返回 false。


第四种:


"小萝莉" == "小萝莉" // --> true

1

字符串常量池中只会有一个对象,所以返回 true。


"小萝莉" == "小" + "萝莉" // --> true

1

由于“小”和“萝莉”都在字符创常量池,所以编译器会将其自动优化为“小萝莉”,所以返回 true。


经过大量实例的分析,我们可以得出如下结论(也是对提问者的回答):


当比较两个字符串对象的内容是否相等时,请使用 .equals() 方法。


当比较两个字符串对象是否相等时,请使用“==”操作符。


当然了,如果要进行两个字符串对象的内容比较,除了 .equals() 方法,还有其他可选的方法。


1)Objects.equals()


Objects.equals() 这个静态方法的优势在于不需要在调用之前判空。


public static boolean equals(Object a, Object b) {

   return (a == b) || (a != null && a.equals(b));

}



如果直接使用 a.equals(b),则需要在调用之前对 a 进行判空,否则可能会抛出空指针 java.lang.NullPointerException。


Objects.equals("小萝莉", new String("小" + "萝莉")) // --> true

Objects.equals(null, new String("小" + "萝莉")); // --> false

Objects.equals(null, null) // --> true


String a = null;

a.equals(new String("小" + "萝莉")); // throw exception


2)String 类的 .contentEquals()


.contentEquals() 的优势在于可以将字符串与任何的字符序列(StringBuffer、StringBuilder、String、CharSequence)进行比较。


public boolean contentEquals(CharSequence cs) {
    // Argument is a StringBuffer, StringBuilder
    if (cs instanceof AbstractStringBuilder) {
        if (cs instanceof StringBuffer) {
            synchronized(cs) {
               return nonSyncContentEquals((AbstractStringBuilder)cs);
            }
        } else {
            return nonSyncContentEquals((AbstractStringBuilder)cs);
        }
    }
    // Argument is a String
    if (cs instanceof String) {
        return equals(cs);
    }
    // Argument is a generic CharSequence
    char v1[] = value;
    int n = v1.length;
    if (n != cs.length()) {
        return false;
    }
    for (int i = 0; i < n; i++) {
        if (v1[i] != cs.charAt(i)) {
            return false;
        }
    }
    return true;
}

从源码上可以看得出,如果 cs 是 StringBuffer,该方法还会进行同步,非常的智能化。不过需要注意的是,使用该方法之前,需要确保比较的两个字符串都不为 null,否则将会抛出空指针。


再强调一点,.equals() 方法在比较的时候需要判 null,而“==”操作符则不需要。


System.out.println( null == null); // --> true


相关文章
|
2月前
|
安全 Java API
【Java字符串操作秘籍】StringBuffer与StringBuilder的终极对决!
【8月更文挑战第25天】在Java中处理字符串时,经常需要修改字符串,但由于`String`对象的不可变性,频繁修改会导致内存浪费和性能下降。为此,Java提供了`StringBuffer`和`StringBuilder`两个类来操作可变字符串序列。`StringBuffer`是线程安全的,适用于多线程环境,但性能略低;`StringBuilder`非线程安全,但在单线程环境中性能更优。两者基本用法相似,通过`append`等方法构建和修改字符串。
47 1
|
4天前
|
Java 数据库
java小工具util系列1:日期和字符串转换工具
java小工具util系列1:日期和字符串转换工具
13 3
|
4天前
|
SQL Java 索引
java小工具util系列2:字符串工具
java小工具util系列2:字符串工具
8 2
|
8天前
|
存储 移动开发 Java
java核心之字符串与编码
java核心之字符串与编码
13 2
|
15天前
|
Java
Java实现:将带时区的时间字符串转换为LocalDateTime对象
通过上述方法,你可以将带时区的时间字符串准确地转换为 `LocalDateTime`对象,这对于处理不需要时区信息的日期和时间场景非常有用。
205 4
|
25天前
|
算法 Oracle Java
Java字符串拼接技术演进及阿里巴巴的贡献
本文主要讲述了Java字符串拼接技术的演进历程,以及阿里巴巴贡献的最新实现 PR 20273。
|
1月前
|
算法 Oracle Java
Java字符串拼接技术演进及阿里巴巴的贡献
本文主要讲述了Java字符串拼接技术的演进历程,以及阿里巴巴贡献的最新实现 PR 20273。
|
2月前
|
API C# 开发者
WPF图形绘制大师指南:GDI+与Direct2D完美融合,带你玩转高性能图形处理秘籍!
【8月更文挑战第31天】GDI+与Direct2D的结合为WPF图形绘制提供了强大的工具集。通过合理地使用这两种技术,开发者可以创造出性能优异且视觉效果丰富的WPF应用程序。在实际应用中,开发者应根据项目需求和技术背景,权衡利弊,选择最合适的技术方案。
46 0
|
2月前
|
存储 Java API
下一篇
无影云桌面