比较一个字符串与空字符串是否相等(Java)

41

我想询问有关在Java中将字符串与空字符串进行比较的问题。如果我使用==equals来比较一个字符串和空字符串,是否会有区别?例如:

String s1 = "hi";

if (s1 == "")
或者
if (s1.equals("")) 

我知道在比较字符串(或者一般的对象)时应该使用equals而不是==,但我想知道这对空字符串有没有影响。


比较长度不是更好的方法吗?我觉得与字符串比较相比,长度比较更加优化。 - Ravisha
@Ravisha 在内部,String.javaequals方法有几个优化:1:引用==比较,2:在强制转换之前进行instanceof String检查,以及3:在最终逐个比较字符串字符之前进行长度值比较。所以回答你的问题,是的,长度比较是有效的,但你的代码应该依赖于String.equals而不是重新发明任何轮子。 - Patrick M
@Patrick M. 如果这里的意图是检查空字符串,我更喜欢比较长度,而不是进行比较。 - Ravisha
@Ravisha 我理解你的观点。问题及其答案最终集中在字符串比较的规范形式上,当其中一个字符串可能为空时,有一种特定的“正确”方式。但是从检测字符串是否为空的更广泛的角度来看,有一个明确定义的 .isEmpty() 方法,它“仅在length()为0时返回true”。但是你仍然需要进行空值检查,这就是为什么我真的更喜欢C#的 public static bool IsNullOrEmpty - Patrick M
9个回答

81
s1 == ""

它不可靠,因为它测试的是引用相等性而非对象相等性(并且String并非严格规范化)。

s1.equals("")

虽然更好,但也可能出现空指针异常。更好的选择是:

"".equals(s1)

没有空指针异常。

编辑:好的,关于规范形式的问题已经被问到。本文将其定义为:

假设我们有一些对象的集合S, 其中包含等价关系。规范形式是通过指定 S的某些对象作为“规范形式”,使得每个考虑中的对象都等价于恰好 一个规范形式对象。

给你一个实际的例子:取有理数集(或通常称为“分数”). 一个有理数由一个分子和一个分母(除数)组成,两者都是整数。这些有理数是等价的:

3/2, 6/4, 24/16

有理数通常写成最大公约数(gcd)为1的形式。所以它们都会简化为3/2。3/2可以看作是这组有理数的规范形式

那么在编程中使用“规范形式”这个术语意味着什么?它可能有几种含义。例如,考虑以下虚构类:

public class MyInt {
  private final int number;

  public MyInt(int number) { this.number = number; }
  public int hashCode() { return number; }
}

类MyInt的哈希码是该类的规范形式,因为对于MyInt的所有实例集合,您可以取任意两个元素m1和m2,它们将遵守以下关系:

m1.equals(m2) == (m1.hashCode() == m2.hashCode())

关系是规范形式的本质。这种情况更常见的出现方式是当您在类上使用工厂方法时,例如:

public class MyClass {
  private MyClass() { }

  public MyClass getInstance(...) { ... }
}

实例不能直接实例化,因为构造函数是私有的。这只是一个工厂方法。工厂方法允许您执行以下操作:
  • 始终返回相同的实例(抽象单例);
  • 每次调用都创建一个新实例;
  • 规范形式返回对象(稍后会详细介绍);或者
  • 任何其他您想做的事情。
基本上,工厂方法抽象了对象创建,我个人认为强制所有构造函数都是私有的以强制使用此模式将是一种有趣的语言特性,但我离题了。
您可以使用此工厂方法缓存创建的实例,以便对于任何两个实例s1和s2,它们遵守以下测试:
(s1 == s2) == s1.equals(s2)

当我说字符串不是严格规范的时候,这意味着:

String s1 = "blah";
String s2 = "blah";
System.out.println(s1 == s2); // true

但是正如其他人指出的那样,您可以通过使用以下方法进行更改:

String s3 = new String("blah");

可能还包括:

String s4 = String.intern("blah");

所以你不能完全依赖引用相等性,因此你不应该完全依赖它。

作为上述模式的警告,我应该指出,使用私有构造函数和工厂方法控制对象创建并不能保证引用相等性意味着对象相等,因为序列化会绕过正常的对象创建机制。Josh Bloch在Effective Java中涵盖了这个主题(最初是在第一版中讨论类型安全的枚举模式,后来成为Java 5中的语言特性),你可以通过重载(私有)readResolve()方法来解决它,但这很棘手,类加载器也会影响这个问题。

总之,这就是规范形式。


1
非常好的解释,点赞!但是您可能需要解释一下在这个上下文中“canonical”的含义——JVM保存了一个已创建字符串的表格,因此不需要重复创建相同的字符串。这是Java在少数几个方面明显优于C和C ++的地方之一。 - rtperson
1
+1 for "".equals(str) ... 真不敢相信我从来没有想到过这个。 - Tom
请展示一下您的“明显性能优势”。http://raid6.com.au/~onlyjob/posts/arena/ - ActiveTrayPrntrTagDataStrDrvr
1
仅为了说明:对 "".equals(s1) 进行测试也会在 s1NULL 时返回 TRUE。如果要将 s1 用作字符串对象,则必须单独进行测试。 - SimonSimCity
@SimonSimCity 实际上,"".equals(null) 返回 false。你是不是想说 !"".equals(null) 返回 true - LoPoBo
显示剩余2条评论

29

这将取决于字符串是否为文字。如果您使用单引号或双引号创建字符串,则可以执行以下操作:

  • 使用单引号创建字符串:var str = '这是一个字符串';
  • 使用双引号创建字符串:var str = "这是一个字符串";

如果您使用模板字符串,则需要使用反引号(`):

var str = `这是一个模板字符串`;

new String("")

然后,使用等于运算符时它将永远不会与“”匹配,如下所示:
    String one = "";
    String two = new String("");
    System.out.println("one == \"\": " + (one == ""));
    System.out.println("one.equals(\"\"): " + one.equals(""));
    System.out.println("two == \"\": " + (two == ""));
    System.out.println("two.equals(\"\"): " + two.equals(""));

--

one == "": true
one.equals(""): true
two == "": false
two.equals(""): true

基本上,您希望始终使用equals()函数。

14

简短回答

s1 == ""         // No!
s1.equals("")    // Ok
s1.isEmpty()     // Ok: fast (from Java 1.6) 
"".equals(s1)    // Ok: null safe

我会确保s1不为空并使用isEmpty()。

注意:空字符串""不是特殊的字符串,但会被视为其他任何“值”。

稍微长一些的答案

对于String对象的引用取决于它们的创建方式:

使用运算符new创建的String对象总是引用不同的对象,即使它们存储相同的字符序列,因此:

String s1 = new String("");
String s2 = new String("");
s1 == s2 // false

使用等号=后跟双引号"value"括起来的值创建的字符串对象被存储在一个字符串对象池中: 在池中创建新对象之前,会搜索具有相同值的对象,如果找到则引用该对象。

String s1 = ""; // "" added to the pool
String s2 = ""; // found "" in the pool, s2 will reference the same object of s1
s1 == s2        // true

对于用双引号 ("value") 包含一个值创建的字符串同样适用:

String s1 = "";  
s1 == "";        //true

String的equals方法会检查两个参数是否相等,因此写成以下这样是安全的:

s1.equals("");

如果s1 == null,这个表达式可能会抛出一个NullPointerException异常,因此,如果您在之前没有检查null值,更安全的写法是:

"".equals(s1);

请也阅读如何在Java中比较字符串?

希望这可以帮助那些可能觉得其他答案有点复杂的不太有经验的用户。 :)


10

这有点偏离你原来的问题,但总有办法。

if(s1.length() == 0)

我认为这与1.6版本中的isEmpty()方法相同。


1
如果字符串为null,这个程序就会遭遇NullPointerException。你可以使用if(s1 != null && s1.length() == 0)……这样一来,如果s1为null,你的条件语句将会短路直接退出计算length。 - rtperson
5
好的,但是s1.isEmpty()或者s1.equals("")也是如此。 - eaolson

9
"".equals(s)

看起来这是最好的选择,但是Apache commons lang库中也包含Stringutils.isEmpty(s)


6
有趣,一个库调用比替换的代码还要冗长和晦涩。 ;) - Peter Lawrey
我不会因为这个问题而添加commons-lang,如果我之前没有使用它的话,但很有可能我已经在使用它了。避免空指针的语句版本一直让我很烦恼,我也不喜欢检查null的方法,所以我更喜欢StringUtils的版本。 - Michael Rutherfurd
1
虽然使用库调用(如StringUtil.isNullOrEmpty(someString))可能会更长,但并不会更加晦涩难懂。原因在于,有一个名称可以使代码更易读。性能损失可以忽略不计,所以这是一种胜利。 - Chii
我想这归于可读性的主观性 :) 对我而言,StringUtils.isEmpty(foo)比"".equals(foo)更易读,但我能理解Peter Lawrey的观点。 - Raibaz
1
@PeterLawrey "" 是硬编码。即使您什么也不输入,那些引号也是硬编码包装器。我们讨厌硬编码的事实迫使我们使用StringUtils。 - Alpaslan
@Alpaslan 我讨厌那些只遵循经验法则而不运用自己的智慧的人。为什么有人会认为 "" 可能是除了空字符串之外的任何东西,但却认为 isEmpty 不能被打破呢?一个害怕编写任何代码的开发者害怕自己的影子,并且有一天会发现当他们必须编写代码时不知道该怎么做。 - Peter Lawrey

6

无论是空字符串还是非空字符串,字符串就是字符串。使用 equals() 方法。


4

我希望Java也有一个类似C#中的String.Empty,可以将字符串设置为空。这将使isEmpty()方法更加合理,并使变量等于空字符串更加容易和简洁。 - Knoxie

0

通过使用isEquals()方法与空字符串和String进行比较。

通过使用另一个方法CompareTo()方法。


你的回答可以通过提供更多支持信息来改进。请编辑以添加进一步的细节,例如引用或文档,以便他人可以确认你的答案是正确的。您可以在帮助中心中找到有关如何编写良好答案的更多信息。 - Community

0

给定两个字符串:

String s1 = "abc";
String s2 = "abc";

-或者-

String s1 = new String("abc");
String s2 = new String("abc");

在两个对象上执行的 == 运算符检查对象标识(如果两个运算符返回到同一对象实例,则返回 true)。应用于 java.lang.Strings 的 == 的实际行为似乎并不总是一致的,因为存在字符串池。

在 Java 中,字符串被 interned(至少部分由 JVM 自行决定)。在任何时候,s1 和 s2 可能已经被 interned 为相同的对象引用(假设它们具有相同的值)。因此,s1 == s2 可能会返回 true 或 false,仅取决于 s1 和 s2 是否都已被 interned。

使 s1 和 s2 等于空字符串对此没有影响 - 它们仍然可能已经被 interned 或未被 interned。

简而言之,如果 s1 和 s2 具有相同的内容,则 == 可能会返回 true 或 false。如果 s1 和 s2 具有相同的内容,则 s1.equals(s2) 保证返回 true。


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