如何在Java中比较字符串?

723

目前我在程序中一直使用==运算符来比较所有的字符串。然而,我遇到了一个错误,将其中一个用.equals()替换后,问题被解决了。

==是否不好?应该何时使用或不使用?有什么区别?


12
同时需要注意的是,如果你要重写 .equals() 方法,确保你也要重写 .hashcode() 方法,否则你将违反 equals 和 hashcode 之间的等价关系。如需更多信息,请参考Java文档。 - Nageswaran
这里是我对于 == 在对象上运作方式的解释链接:http://stackoverflow.com/a/19966154/2284641 - Johannes H.
"==" 有时会起作用,因为 Java 有一个字符串池,它试图重用常用字符串的内存引用。但是 "==" 比较的是对象是否相等,而不是值... 所以你应该使用 ".equals()" 来比较值。 - James Oravec
除非你喜欢追踪微妙的错误并研究Java字符串内部化过程的复杂性,否则不要使用“==”来测试字符串是否相同。 "12" == "1" + 2 是错误的(可能)。 - Flight Odyssey
23个回答

6062

==测试引用相等性(即它们是否是同一个对象)。

.equals()测试值相等性(即它们是否包含相同的数据)。

Objects.equals()在调用.equals()之前检查null,因此您不必自己检查(JDK7及以上版本可用,也可在Guava中使用)。

因此,如果您想测试两个字符串是否具有相同的值,您可能会想要使用Objects.equals()

// These two have the same value
new String("test").equals("test") // --> true 

// ... but they are not the same object
new String("test") == "test" // --> false 

// ... neither are these
new String("test") == new String("test") // --> false 

// ... but these are because literals are interned by 
// the compiler and thus refer to the same object
"test" == "test" // --> true 

// ... string literals are concatenated by the compiler
// and the results are interned.
"test" == "te" + "st" // --> true

// ... but you should really just call Objects.equals()
Objects.equals("test", new String("test")) // --> true
Objects.equals(null, "test") // --> false
Objects.equals(null, null) // --> true

从Java语言规范JLS 15.21.3. 引用相等运算符==!=中可以得知:

虽然==可以用于比较String类型的引用,但这种相等测试确定两个操作数是否引用同一个String对象。如果操作数是不同的String对象,即使它们包含相同的字符序列(§3.10.5§3.10.6),结果也是false。可以通过方法调用s.equals(t)来测试两个字符串st的内容是否相等。

你几乎总是要使用Objects.equals()。在罕见的情况下,你知道你正在处理interned字符串时,你可以使用==
来自JLS 3.10.5. String Literals

此外,字符串字面值始终引用类String相同实例。这是因为字符串字面值 - 或者更一般地说,作为常量表达式值的字符串(§15.28) - 被“interned”以共享唯一实例,使用方法String.intern

JLS 3.10.5-1中也可以找到类似的例子。

考虑其他方法

String.equalsIgnoreCase()忽略大小写的值相等性。但要注意,在不同的区域设置相关情况下,该方法可能会产生意外的结果,请参考this question

String.contentEquals()String的内容与任何CharSequence的内容进行比较(自Java 1.5起可用)。这样可以避免在进行相等性比较之前将StringBuffer等转换为String,但空值检查留给您自己处理。


7
如果 == 用于检查引用是否相等,那么 n==5 为什么是有意义的呢?5 不是一个变量。 - Not Euler
14
因为==比较的是变量的,所以当你有一个对象时,引用该对象的变量将其引用作为。因此,使用==比较两个变量时,要比较它们的引用。对于诸如int这样的基本数据类型,情况仍然相同。int类型的变量具有整数值。因此,使用==比较两个int的值。无论int是变量的值还是魔数都没有关系。此外,引用只是一个指向内存的数字。 - akuzminykh
我想补充一点,即使你知道你的字符串已经被内部化了,你也应该使用equals,因为这样更明显正确。或者,你应该使用枚举而不是字符串。 - kaya3
2
考虑到类名是复数形式(Objects),且名称来自英语,我认为他们保留了方法名.equals而不是将其更改为.equal,这让我感到不协调。 - Karl Knechtel

783

== 测试对象引用,.equals() 测试字符串值。

有时候看起来好像 == 比较的是值,因为Java在幕后做了一些工作,以确保相同的内联字符串实际上是同一个对象。

例如:

String fooString1 = new String("foo");
String fooString2 = new String("foo");

// Evaluates to false
fooString1 == fooString2;

// Evaluates to true
fooString1.equals(fooString2);

// Evaluates to true, because Java uses the same object
"bar" == "bar";

但要小心null值!

==可以很好地处理null字符串,但是从一个null字符串调用.equals()会引发异常:

String nullString1 = null;
String nullString2 = null;

// Evaluates to true
System.out.print(nullString1 == nullString2);

// Throws a NullPointerException
System.out.print(nullString1.equals(nullString2));

所以如果你知道fooString1可能是空的,那么请通过写出来告诉读者。

System.out.print(fooString1 != null && fooString1.equals("bar"));
以下代码更短,但不太明显地检查了空值:
System.out.print("bar".equals(fooString1));  // "bar" is never null
System.out.print(Objects.equals(fooString1, "bar"));  // Java 7 required

94
有时候看起来好像“==”比较的是值,但实际上它总是比较值!只是某些值是引用而已! - aioobe
8
哎呀,Java中没有isNullOrEmpty()的静态方法和自定义操作符重载,这使得Java比C#或Python更加笨拙。并且由于Java没有扩展方法,您无法编写自己的实用程序来扩展java.lang.String。对吧?您认为通过创建String子类并添加静态实用程序方法,然后始终使用MyString可以解决问题吗?在该子类中添加两个参数的静态方法以进行空值安全比较也将非常有用。 - Jon Coombs
8
Groovy通过__安全导航操作符__(http://groovy.codehaus.org/Operators#Operators-SafeNavigationOperator(?.))`?.`,使这个过程更方便。这会将`nullString1?.equals(nullString2);`转换为完全为空的语句。但是,如果您有`validString?.equals(nullString);`,它仍会抛出异常,此方法则无法解决。 - Charles Wood
6
Java中比较可为空字符串的短方法:https://dev59.com/FWgu5IYBdhLWcg3wgnVh - Vadzim
5
Java支持子类化和创建自己的方法。然而,由于某些原因,有一些类被标记为final,String就是其中之一,所以我们无法进行扩展。我们可以创建其他类并在那里创建实用程序类,该类接受两个字符串作为参数,并在其中实现我们的逻辑。此外,对于空值检查,还有一些其他库,如Spring和Apache,拥有很好的方法集合,可以使用它们。 - Panther
显示剩余4条评论

479

==比较对象引用。

.equals()比较字符串值。

有时==会在以下情况下产生比较字符串值的错觉:

String a="Test";
String b="Test";
if(a==b) ===> true

这是因为当你创建任何字符串文字时,JVM首先在字符串池中搜索该文字,并且如果找到匹配项,则会将相同的引用赋给新字符串。因此,我们得到:

(a==b) ===> true

                       String Pool
     b -----------------> "test" <-----------------a

然而,== 在以下情况下会失败:

String a="test";
String b=new String("test");
if (a==b) ===> false
在这种情况下,对于new String("test"),将在堆上创建新的字符串对象,然后该引用将被赋给b,因此b将被赋予堆上的引用,而不是字符串池中的引用。
现在a指向字符串池中的一个字符串,而b指向堆上的一个字符串。因此我们得到: if(a==b) ===> false.
                String Pool
     "test" <-------------------- a

                   Heap
     "test" <-------------------- b

.equals() 方法始终比较 String 值,因此在两种情况下都返回 true:

.equals()方法永远比较字符串的内容,而不是引用或内存地址。

String a="Test";
String b="Test";
if(a.equals(b)) ===> true

String a="test";
String b=new String("test");
if(a.equals(b)) ===> true

因此,始终使用.equals()更好。


3
".equals() 比较两个实例,但 equals 的实现方式是进行比较。这可能或可能不会比较 toString 的输出。" - Jacob
3
Java中Object类的.equals()方法比较的是实例(引用/地址),而String类的.equals()方法被重写为比较内容(字符)。 - kittu
2
很好的指出了字符串池与Java堆之间的差异,因为它们肯定不同。在字符串池中,Java尝试“缓存” String 对象以节省内存占用,因为众所周知 String 是不可变的(我希望我在这里表达得正确)。还可以查看https://dev59.com/6XA75IYBdhLWcg3w4tR7 - Roland

234

==操作符用于检查两个字符串是否完全相同。

.equals()方法将检查两个字符串是否具有相同的值。


5
通常我强烈推荐使用Apache Commons库: https://commons.apache.org/proper/commons-lang/javadocs/api-2.6/org/apache/commons/lang/StringUtils.html#equals(java.lang.String, java.lang.String) - Marcin Erbel

188

Java中的字符串是不可变的。这意味着每当您尝试更改/修改字符串时,都会得到一个新实例。您无法更改原始字符串。这样做是为了可以对这些字符串实例进行缓存。典型的程序包含许多字符串引用,缓存这些实例可以减少内存占用并提高程序性能。

当使用==运算符进行字符串比较时,您并没有比较字符串的内容,而是实际上在比较内存地址。如果它们都相等,则返回true,否则返回false。而在字符串中equals比较字符串内容。

那么问题是,如果所有字符串都被缓存在系统中,为什么 == 返回false,而equals返回true?嗯,这是可能的。如果您创建一个像String str = new String(“ Testing”)的新字符串,即使缓存已经包含具有相同内容的字符串,您也会在缓存中创建一个新字符串。简而言之,"MyString" == new String("MyString")将始终返回false。

Java还谈论了可以在字符串上使用的intern()函数,使其成为缓存的一部分,因此"MyString" == new String("MyString").intern()将返回true。

注意:==运算符比equals运算符快得多,只是因为您正在比较两个内存地址,但您需要确保代码不会在代码中创建新的String实例。否则,您将遇到错误。


153
String a = new String("foo");
String b = new String("foo");
System.out.println(a == b); // prints false
System.out.println(a.equals(b)); // prints true

确保你理解为什么。这是因为==比较只比较引用;而equals()方法则对内容逐个字符进行比较。

当你为ab调用new时,每个变量都会得到一个指向字符串表中的"foo"的新引用。引用不同,但内容相同。


130

是的,这很糟糕...

== 表示你的两个字符串引用是完全相同的对象。你可能听说过这是因为Java保留了一种字面表(确实如此),但并不总是这样。有些字符串以不同的方式加载,从其他字符串构建等,所以你绝不能假定两个相同的字符串存储在同一位置。

Equals 方法为你进行了实际比较。


128

是的,== 不适用于比较字符串(任何对象实际上都不适用,除非您知道它们是规范的)。== 只是比较对象引用。 .equals() 则测试相等性。对于字符串来说,通常它们会相同,但正如你所发现的那样,并不总是保证相同。


124
Java有一个字符串池,Java在其中管理String对象的内存分配。请参阅Java中的字符串池 使用==运算符比较两个对象时,它会将地址相等性与字符串池进行比较。如果两个String对象具有相同的地址引用,则返回true,否则返回false。但是,如果您想比较两个字符串对象的内容,则必须覆盖equals方法。 equals实际上是Object类的方法,但是它被重写为String类,并提供了一个新的定义,用于比较对象的内容。
Example:
    stringObjectOne.equals(stringObjectTwo);

但请注意,它会尊重字符串的大小写。如果您想进行不区分大小写的比较,则必须使用String类的equalsIgnoreCase方法。

让我们看一下:

String one   = "HELLO"; 
String two   = "HELLO"; 
String three = new String("HELLO"); 
String four  = "hello"; 

one == two;   // TRUE
one == three; // FALSE
one == four;  // FALSE

one.equals(two);            // TRUE
one.equals(three);          // TRUE
one.equals(four);           // FALSE
one.equalsIgnoreCase(four); // TRUE

7
我看到这是一个晚回答的重要问题。我可以问一下,它提供了什么不在现有答案中提到的内容吗? - Mysticial
7
@Mysticial添加了 equalsIgnoreCase,这可能对新手比较有用。 - AmitG

107

我同意zacherates的答案。

但是你可以在非文字直接量字符串上调用intern()方法。

以zacherates的示例为例:

// ... but they are not the same object
new String("test") == "test" ==> false 

如果将非文字字符串进行比较,结果为true

new String("test").intern() == "test" ==> true 

10
一般来说,这不是一个好主意。使用内部化通常比较昂贵,且可能(出乎意料地)会增加JVM的内存占用和GC成本。在大多数情况下,这些成本会超过使用==进行字符串比较所带来的性能优势。 - Stephen C

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接