【问题标题】:When "" == s is false but "".equals( s ) is true当 "" == s 为假但 "".equals(s) 为真时
【发布时间】:2010-11-09 19:46:57
【问题描述】:

编辑 感谢您的及时回复。请看看真正的问题是什么。这次我把它加粗了。

我确实了解 == 和 .equals 之间的区别。所以,这不是我的问题(我实际上为此添加了一些上下文)


我正在对空字符串执行以下验证:

if( "" == value ) { 
    // is empty string 
} 

过去在从数据库中获取值或从另一个节点反序列化对象时,此测试失败,因为两个字符串实例确实是不同的对象引用,尽管它们包含相同的数据。

所以解决这些情况的方法是

if( "".equals( value ) ) {
   // which returns true for all the empty strings
}

我很好。这很清楚。

今天又出现了这种情况,但是让我很困惑,因为这次的应用是一个非常小的独立应用,根本不使用网络,所以没有新的字符串是从数据库中获取的,也不是从另一个节点反序列化的。

所以问题是:


在哪种其他情况下:

"" == value // yields false 

"".equals( value ) // yields true

对于本地独立应用程序?

我很确定 new String() 没有在代码中使用。

字符串引用可能是“”的唯一方法是因为它被直接在代码中分配了“”(或者这就是我的想法),例如:

String a = "";
String b = a;

assert "" == b ; // this is true 

不知何故(在阅读代码后我有一个线索)创建了两个不同的空字符串对象引用,我想知道如何

jjnguys 回答中的更多内容:

字节!

编辑:结论

我找到了原因。

根据 jjnguy 的建议,我能够以不同的眼光看待代码。

有罪的方法:StringBuilder.toString()

分配并初始化一个新的 String 对象以包含该对象当前表示的字符序列。

哇!...

    StringBuilder b = new StringBuilder("h");
    b.deleteCharAt( 0 );
    System.out.println( "" == b.toString() ); // prints false

谜团解开了。

代码使用 StringBuilder 来处理不断增长的字符串。事实证明,在某些时候有人这样做了:

 public void someAction( String string ) { 
      if( "" == string ) {
           return;
       }

       deleteBankAccount( string );
 }

并使用

 someAction( myBuilder.toString() ); // bug introduced. 

附言我最近读太多 CodingHorror 了吗?或者为什么我觉得有必要在这里添加一些有趣的动物图片?

【问题讨论】:

  • if (value != null && value.length == 0) { ... },你应该使用 equals 因为 == 对象比较引用,即它们是同一个对象。虽然在编译时 Java 会找到相同的字符串并让它们共享相同的引用(字符串是不可变的),但在运行时很容易创建具有不同引用的空字符串。
  • 正如我的回答所暗示的......忘记你的“想法”。如果您要检查某些内容是否与空字符串具有相同的值,请使用 equals。您正试图依赖编译器来实习您的字符串,Josh Bloch 指出这不是程序员的工具:-)。见下文。
  • 现在有几个答案显示了简单的字符串操作,这些操作会产生不同的零长度字符串对象。网络和善良与这些答案无关。
  • (我可以打断一下String.isEmpty,从 1.6 开始。谢谢。)
  • (哦,是的,FindBugs 会警告这类事情。)

标签: java comparison equals equality


【解决方案1】:
String s = "";
String s2 = someUserInputVariale.toLowercase(); // where the user entered in ""

这样的事情会导致s == s2 评估为假。

大量代码仍然会创建新的Strings,而不会将调用暴露给new String()

【讨论】:

  • 任何时候在运行时生成/创建/操作字符串,它都有可能发生。我猜虚拟机可以在它认为合适的时候为字符串池创建一个新的字符串,即使为了优化,使用现有的字符串会更好。
  • 不,我喜欢。 “我们 Java 程序员甚至不知道如何拼写 optimyz...optimo...看,JIT 为我们做的好吗?”
  • 哈哈。可悲的是,Firefox 纠正了我,但我懒得修复它。我需要自动完成常规文本。
  • 虽然不是唯一正确的答案,但这让我找到了原因。谢谢。 ..
【解决方案2】:
"" == value // yields false

"".equals( value ) // yields true

任何时候变量value 的值还没有被实习。如果值是在运行时计算的,就会出现这种情况。请参阅JLS section 3.10.5 String Literals 示例代码来说明这一点:

因此,由编译单元(§7.3)组成的测试程序:

package testPackage;
class Test {
    public static void main(String[] args) {
        String hello = "Hello", lo = "lo";
        System.out.print((hello == "Hello") + " ");
        System.out.print((Other.hello == hello) + " ");
        System.out.print((other.Other.hello == hello) + " ");
        System.out.print((hello == ("Hel"+"lo")) + " ");
        System.out.print((hello == ("Hel"+lo)) + " ");
        System.out.println(hello == ("Hel"+lo).intern());
    }
}
class Other { static String hello = "Hello"; }

和编译单元:

package other;
public class Other { static String hello = "Hello"; }

产生输出:

true true true true false true

这个例子说明了六点:

  • 同一包 (§7) 中的同一类 (§8) 中的文字字符串表示对同一 String 对象的引用 (§4.3.1)。
  • 同一包中不同类中的文字字符串表示对同一 String 对象的引用。
  • 不同包中不同类中的文字字符串同样表示对同一 String 对象的引用。
  • 由常量表达式(第 15.28 节)计算的字符串在编译时计算,然后将其视为文字。
  • 在运行时计算的字符串是新创建的,因此是不同的。
  • 显式实习计算字符串的结果是与任何具有相同内容的预先存在的文字字符串相同的字符串。

【讨论】:

    【解决方案3】:

    如果您能抓住 Joshua Bloch 和 Neal Gafter 所著的Java Puzzlers一书,并查看第 13 题“动物农场”...他在这个问题上有很好的建议。我要复制一些相关的文字:

    “您可能知道String 类型的编译时常量是interned [JLS 15.28]。换句话说,@987654322 类型的任意两个常量表达式 @ 指定相同的字符序列由相同的对象引用表示... 您的代码应该很少(如果有的话)依赖于字符串常量的实习。 实习只是为了减少虚拟机,而不是作为程序员的工具...当比较对象引用时,您应该使用equals 方法而不是== 运算符,除非您需要比较对象标识而不是值。”

    这来自我提到的上述参考资料......我书中的第 30 - 31 页。

    【讨论】:

    • 换句话说......使用equals,因为这就是你的意思......这就是你的意思。
    【解决方案4】:

    您是否希望 "abcde".substring(1,2)"zbcdefgh".substring(1,2) 产生相同的 String 对象?

    它们都产生从两个不同字符串中提取的“相等”子字符串,但它们是不同的对象似乎很合理,因此 == 将它们视为不同的。

    现在考虑当子字符串的长度为 0 时,substring(1, 1)。它产生一个长度为零的字符串,但"abcde".substring(1,1)"zbcdefgh".substring(1,2) 是不同的对象也就不足为奇了,因此其中至少有一个与“”不同。

    【讨论】:

    • 我认为这个类比并不能说明任何事情
    • 我愿意。它说即使没有显式调用“new String”,很多代码仍然会创建新的字符串...子字符串、toUpperCase 等。
    • 我认为这是一个很好的答案。显然人们会期望 == 产生错误,但 .equals 产生正确。这就是提问者实际问的问题,而不是大多数其他人回答他的“== 和 equals 之间的区别是什么”
    • 我试着再详细说明一下。在教学时,我更喜欢使用问题而不是答案。猜猜这在文本中并不那么容易。
    • 我不明白第三段。
    【解决方案5】:

    据我了解,在将 Java 代码编译为字节码或运行程序时,在大多数情况下,相同的字符串将被引用到相同的对象以节省内存。所以有时你会用 == 比较字符串来逃避。但这是您不能依赖的编译器优化。

    但有时会发生编译器决定不进行此优化,或者程序无法看到字符串相同并且突然检查失败,因为您依赖于一些底层优化巫术这取决于您正在使用的 jvm 的实现等等。

    所以使用 equals 总是一件好事。对于空字符串,还有其他可能性,例如与长度 == 0 进行比较,或者如果您不关心向后兼容性,则可以使用 string.empty()。

    【讨论】:

      【解决方案6】:

      您应该尝试考虑String.length() == 0

      【讨论】:

      • 我也建议这样做,除非字符串可以为空。 ;)
      【解决方案7】:
      【解决方案8】:

      String.intern()javadoc==.equals() 有一些很好的评论。

      文档还阐明了每个字符串文字都是intern'd。

      公共字符串实习生()

      返回字符串对象的规范表示。

      一个字符串池,最初是空的,由 String 类私下维护。

      当调用intern方法时,如果 池已经包含一个字符串 等于此 String 对象为 由等于(对象)确定 方法,然后是池中的字符串 被退回。否则,这个字符串 对象被添加到池中,并且 对此 String 对象的引用是 返回。

      因此对于任意两个字符串 s 和 t, s.intern() == t.intern() 是 当且仅当 s.equals(t) 为真 真的。

      所有文字字符串和字符串值 常量表达式被实习。 字符串文字在 §3.10.5 中定义 Java语言规范

      返回:具有相同的字符串 内容作为这个字符串,但是是 保证来自一个独特的池 字符串。

      【讨论】:

        【解决方案9】:

        如果你使用 google 代码搜索,你会发现很多地方都犯了同样的错误:google for file:.java \=\=\ \"\" 当然,在精心控制的情况下,这可能是一个正确的习惯用法,但通常这只是一个错误。

        【讨论】:

        • 是的。好吧,对于使用网络的应用程序,失败很常见,但对于自包含代码来说,这很奇怪。谷歌查询很好! :)
        • 很遗憾,链接失效了
        【解决方案10】:

        为什么不使用:

        if (value != null && value.length == 0) {
            // do stuff (above could be "== null ||"
        }
        

        您应该使用equals(),因为== 用于对象比较引用,即它们是否是同一个对象。虽然在编译时 Java 会找到相同的字符串并让它们共享相同的引用(字符串是不可变的),但在运行时很容易创建具有不同引用的空字符串,其中 == 因您的典型意图 equals() 而失败。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2023-04-02
          • 2020-05-19
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多