【问题标题】:Comparing a string with the empty string (Java)将字符串与空字符串进行比较(Java)
【发布时间】:2010-10-06 14:48:01
【问题描述】:

我有一个关于将字符串与 Java 中的空字符串进行比较的问题。如果我将字符串与空字符串与==equals 进行比较,是否有区别?例如:

String s1 = "hi";

if (s1 == "")

if (s1.equals("")) 

我知道应该将字符串(和一般的对象)与equals 进行比较,而不是==,但我想知道它对于空字符串是否重要。

【问题讨论】:

  • 比较长度不是更好吗?我觉得长度比较比字符串比较更优化
  • @Ravisha 在内部,String.java'sequals 方法有几个优化:1:参考==比较,2:instanceof String在转换之前检查,并在最后逐字符比较字符串之前比较 3: 长度值。所以回答你的问题,是的,长度比较是有效的,但你的代码应该依赖 String.equals 而不是重新发明它的任何*。
  • @Patrick M 。如果这里的目的是检查空字符串,我更喜欢比较长度。而不是比较。
  • @Ravisha 我同意你的观点。问题及其答案最终集中在字符串比较的规范形式上,当其中一个字符串可能为空时,它具有特定的“正确”方式。但是从检测字符串是否为空的更广泛的角度来看,有专门定义的.isEmpty() 方法“当且仅当length() 为0 时返回true”。但是你仍然需要做一个空检查,这就是为什么我更喜欢 C# 的public static bool IsNullOrEmpty

标签: java string comparison string-comparison


【解决方案1】:

这将取决于字符串是否为文字。如果您使用

创建字符串
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()

【讨论】:

    【解决方案2】:

    字符串,是字符串,是字符串,不管是不是空字符串。使用equals()

    【讨论】:

      【解决方案3】:
      s1 == ""
      

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

      s1.equals("")
      

      更好,但可能会遇到空指针异常。更好的是:

      "".equals(s1)
      

      没有空指针异常。

      编辑:好的,问题是关于canonical form。本文将其定义为:

      假设我们有一些对象集合 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 不是严格规范时,它的意思是:

      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 以获得很好的解释。但是您可能想解释一下“规范”在这种情况下的含义——JVM 拥有一个已创建字符串的表,因此它不需要重复创建相同的字符串。 Java 与 C 和 C++ 相比具有明显性能优势的少数几个地方之一。
      • 对于 C/C++,GCC 至少有一个编译时选项来重用字符串常量。它也很聪明,可以在可能的情况下支持子字符串。但它不会在运行时执行此操作(除非您有一个执行此操作的库,例如使用 Qt 之类的隐式共享)。
      • +1 for "".equals(str) ...不敢相信我从来没有想到过。
      • 拜托,我想看看你的“明显的性能优势”。 raid6.com.au/~onlyjob/posts/arena
      • 只是说:如果s1NULL,则针对"".equals(s1) 进行测试也会返回TRUE。如果你想使用s1 作为字符串对象,你必须单独测试。
      【解决方案4】:
      "".equals(s)
      

      似乎是最好的选择,但Apache commons lang库中也包含Stringutils.isEmpty(s)

      【讨论】:

      • 有趣,一个库调用,它比它替换的代码更长更晦涩。 ;)
      • 虽然我不会为此添加 commons-lang,但如果我还没有将它用于其他用途,但我有可能使用它。语句的空指针避免版本总是让我感到很头疼,我也讨厌检查空值的方式,所以我更喜欢 StringUtils 版本。
      • 虽然它更长,但使用像 StringUtil.isNullOrEmpty(someString); 这样的库调用并不会更晦涩难懂。 ,原因是在那里有一个名字使它更具可读性。对性能的影响可以忽略不计,所以它是一场胜利。
      • 我想这属于可读性的主观性:) 对我来说 StringUtils.isEmpty(foo) 比 "".equals(foo) 更具可读性,但我能理解 Peter Lawrey 的观点。
      • @PeterLawrey "" 是硬编码。即使您不输入任何内容,引号也是编码包装器。我们讨厌硬编码的事实迫使我们使用 StringUtils。
      【解决方案5】:

      这与您最初的问题有点偏离,但总有

      if(s1.length() == 0)
      

      我相信这相当于 1.6 中的 isEmpty() 方法。

      【讨论】:

      • 如果 String 为空,这将遭受 NullPointerException。您始终可以使用 if(s1 != null && s1.length() == 0)... 这样,如果 s1 为空,您的条件将短路评估长度。
      • 是的,但 s1.isEmpty() 或 s1.equals("") 也是如此。
      【解决方案6】:

      给定两个字符串:

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

      -或-

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

      对两个 Object 执行的 == 运算符检查对象身份(如果两个运算符返回同一个对象实例,则返回 true。)应用于 java.lang.Strings 的 == 的实际行为并不总是一致,因为 String interning。

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

      使 s1 和 s2 等于空字符串对此没有影响 - 它们仍然可能已被拘留,也可能未被拘留。

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

      【讨论】:

        【解决方案7】:

        如果需要空检查,请使用 String.isEmpty()StringUtils.isEmpty(String str)

        【讨论】:

        • 我希望 java 有一个 String.Empty,你可以在 C# 中设置一个字符串。这将使 isEmpty() 方法更有意义,并使变量等于空字符串更容易。
        【解决方案8】:

        简答

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

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

        注意:空字符串“”不是特殊的字符串,而是算作任何其他“值”。

        稍长一点的答案

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

        使用运算符 new 创建的字符串对象总是引用单独的对象,即使它们存储相同的字符序列,所以:

        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);
        

        另请阅读How do I compare strings in Java?

        希望它可以帮助没有经验的用户,他们可能会发现其他答案有点过于复杂。 :)

        【讨论】: