Stack Overflow 上 370万浏览量的一个问题:如何比较 Java 的字符串?
在逛 Stack Overflow 的时候,发现了一些访问量像喜马拉雅山一样高的问题,比如说这个:如何比较 Java 的字符串?访问量足足有 370万+,这不得了啊!说明有很多很多的程序员被这个问题困扰过。
PS:系列文章回顾:《Stack Overflow 上250万浏览量的一个问题:你对象丢了》
我们来回顾一下提问者的问题:
截止到目前为止,我一直使用“”操作符来比较字符串,直到程序出现了一个 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 虚拟机在执行程序的过程中会把内存区域划分为若干个不同的数据区域,如下图所示。

下面我们通过实际代码来看看字符串的比较。
第一种:
new String("小萝莉").equals("小萝莉") // --> true
.equals() 比较的是两个字符串对象的内容是否相等,所以结果为 true。
第二种:
new String("小萝莉") == "小萝莉" // --> false
“==”操作符左侧的对象存储在堆中,右侧的对象存储在方法区,所以返回 false。
第三种:
new String("小萝莉") == new String("小萝莉") // --> false
new 出来的两个对象肯定是不相等的,所以返回 false。
第四种:
"小萝莉" == "小萝莉" // --> true
字符串常量池中只会有一个对象,所以返回 true。
"小萝莉" == "小" + "萝莉" // --> true
由于“小”和“萝莉”都在字符创常量池,所以编译器会将其自动优化为“小萝莉”,所以返回 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
好了各位读者朋友们,以上就是本文的全部内容了。能看到这里的都是人才,二哥必须要为你点个赞
Stack Overflow 上 370万浏览量的一个问题:如何比较 Java 的字符串?的更多相关文章
- [转帖]Stack Overflow上188万浏览量的提问:Java 到底是值传递还是引用传递?
Stack Overflow上188万浏览量的提问:Java 到底是值传递还是引用传递? http://www.itpub.net/2019/12/03/4567/ 在逛 Stack Overfl ...
- Stack Overflow上59万浏览量的提问:为什么会发生ArrayIndexOutOfBoundsException?
在逛 Stack Overflow 的时候,发现了一些访问量像昆仑山一样高的问题,比如说这个:为什么会发生 ArrayIndexOutOfBoundsException?这样看似简单到不值得一问的问题 ...
- 为什么开发者热衷在Stack Overflow上查阅API文档?
摘要:一项新研究跟踪了Android开发者的访问历史,发现开发者多达二分之一的文档是从Stack Overflow上获取到的,而Stack Overflow上的示例也多于官方指南,开发者通过搜索更多时 ...
- Stack Overflow 上排名前十的与API相关的问题
Stack Overflow是一个庞大的编程知识仓库,在Stack Overflow 上,数百万的提问被回答,并且这些回答都是高质量的.这就是为什么在Google搜索结果的排行榜上,Stack Ove ...
- Stack Overflow上关于Java Collections的几个常见问题
下面列出Stack Overflow上最常见的几个关于Java Collections的问题并给出答案. 1. 什么时候用LinkedList,什么时候用ArrayList? ArrayList是使用 ...
- JavaScript超越了Java,c,python等等成为Stack Overflow上最热门的
JavaScript超越了Java,c,python等等成为Stack Overflow上最热门的标签 在2015年6月至今,JavaScript超越了Java,c,python等等成为Stack O ...
- Stack Overflow 上 250W 浏览量的一个问题:你对象丢了
在逛 Stack Overflow 的时候,发现最火的问题竟然是:什么是 NullPointerException(java.lang.NullPointerException),它是由什么原因导致的 ...
- Stack Overflow 上人气最旺的 10 个 Java 问题
1. 为什么两个(1927年)时间相减得到一个奇怪的结果? (3623个赞) 如果执行下面的程序,程序解析两个间隔1秒的日期字符串并比较: public static void main(String ...
- 关于Stack Overflow上ASP.NET最大连接数限制提问的一个思考
原文地址:Why request queuing is high even when request executing is below its limit? We are using below ...
随机推荐
- 利用 Ansiable 自动化部署 Veeam Backup & Replication 9.5U4b
利用 Ansiable 自动化部署 Veeam Backup & Replication 9.5U4b 前言 上周出差期间接到一个做CMP(云管平台)Partner的需求,要在无人值守的安装 ...
- SpringCloud之Nacos服务注册(十八)
一 服务提供配置 pom.xml <dependency> <groupId>org.springframework.boot</groupId> <arti ...
- 列表[‘hello’ , ‘python’ ,’!’ ] 用多种方法拼接,并输出’hello python !’ 以及join()在python中的用法简介
列表[‘hello’ , ‘python’ ,’!’ ] 用多种方法拼接,并输出’hello python !’ 使用字符串链接的四种方法都可以创建 字符串拼接一共有四种方法,也可以应用到列表的拼接中 ...
- Caused by: java.lang.IllegalArgumentException: Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required [ IDEA mybatis项目报错 ]
今天笔者用Springboot框架整合Mybatis做一个小小的项目: 代码写完,在运行项目时,IDEA给我报了3处错误: org.springframework.beans.factory.Unsa ...
- Android_Fragment
(一) Faragment有自己的生命周期 Fragment依赖于Activity Fragmen通过getActivity()可以获取所在Activity:Activity通过FragmentMan ...
- 测试面试题集-测试用例设计:登录、购物车、QQ收藏表情、转账、充值、提现
以下内容首发于微信公众号[ITester软件测试小栈]: 测试面试题集-2.测试用例设计 大家好 我是coco小锦鲤 上周五给大家分享了测试基础理论题 这个周五给大家分享测试用例设计题 测试用例的考察 ...
- MapReduce任务提交源码分析
为了测试MapReduce提交的详细流程.需要在提交这一步打上断点: F7进入方法: 进入submit方法: 注意这个connect方法,它在连接谁呢?我们知道,Driver是作为客户端存在的,那么客 ...
- mysql group by分组查询后 查询个数
mysql group by分组查询后 查询个数2个方法随便你选 <pre>select count(distinct colA) from table1;</pre>< ...
- CSS(7)--- 通俗讲解清除浮动
CSS(7)--- 通俗讲解清除浮动 上一篇讲了CSS浮动 博客地址:CSS(6)---通俗讲解浮动(float) 一.理解清除浮动 1.为什么要清除浮动 我们前面说过,浮动本质是用来做一些文字混排效 ...
- ThinkPHP 怎样让URL访问的时候省略 index.php
ThinkPHP 怎样让URL访问的时候省略 index.php Nginx 服务器配置 修改 nginx.conf 文件 location / { // …..省略部分代码 if (!-e $req ...