五分钟学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月前
|
SQL JSON Java
告别字符串拼接:用Java文本块优雅处理多行字符串
告别字符串拼接:用Java文本块优雅处理多行字符串
295 108
|
4月前
|
SQL JSON Java
告别拼接噩梦:Java文本块让多行字符串更优雅
告别拼接噩梦:Java文本块让多行字符串更优雅
466 82
|
4月前
|
自然语言处理 Java Apache
在Java中将String字符串转换为算术表达式并计算
具体的实现逻辑需要填写在 `Tokenizer`和 `ExpressionParser`类中,这里只提供了大概的框架。在实际实现时 `Tokenizer`应该提供分词逻辑,把输入的字符串转换成Token序列。而 `ExpressionParser`应当通过递归下降的方式依次解析
279 14
|
8月前
|
存储 缓存 安全
Java 字符串详解
本文介绍了 Java 中的三种字符串类型:String、StringBuffer 和 StringBuilder,详细讲解了它们的区别与使用场景。String 是不可变的字符串常量,线程安全但操作效率较低;StringBuffer 是可变的字符串缓冲区,线程安全但性能稍逊;StringBuilder 同样是可变的字符串缓冲区,但非线程安全,性能更高。文章还列举了三者的常用方法,并总结了它们在不同环境下的适用情况及执行速度对比。
188 17
|
8月前
|
存储 缓存 安全
Java字符串缓冲区
字符串缓冲区是用于处理可变字符串的容器,Java中提供了`StringBuffer`和`StringBuilder`两种实现。由于`String`类不可变,当需要频繁修改字符串时,使用缓冲区更高效。`StringBuffer`是一个线程安全的容器,支持动态扩展、任意类型数据转为字符串存储,并提供多种操作方法(如`append`、`insert`、`delete`等)。通过这些方法,可以方便地对字符串进行添加、插入、删除等操作,最终将结果转换为字符串。示例代码展示了如何创建缓冲区对象并调用相关方法完成字符串操作。
163 13
|
12月前
|
SQL Java 索引
java小工具util系列2:字符串工具
java小工具util系列2:字符串工具
265 83
|
12月前
|
Java 数据库
java小工具util系列1:日期和字符串转换工具
java小工具util系列1:日期和字符串转换工具
209 26
|
12月前
|
存储 缓存 安全
java 中操作字符串都有哪些类,它们之间有什么区别
Java中操作字符串的类主要有String、StringBuilder和StringBuffer。String是不可变的,每次操作都会生成新对象;StringBuilder和StringBuffer都是可变的,但StringBuilder是非线程安全的,而StringBuffer是线程安全的,因此性能略低。
313 8
|
12月前
|
缓存 算法 Java
本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制
在现代软件开发中,性能优化至关重要。本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制。通过调整垃圾回收器参数、优化堆大小与布局、使用对象池和缓存技术,开发者可显著提升应用性能和稳定性。
188 6