为什么在Java 1.6.0_29和OSX中比较字符串时可以使用“==”运算符?

4
昨天(2012年4月5日),我试图比较环境中的字符串,环境如下:
计算机1
  1. Java(TM) SE Runtime Environment (build 1.6.0_29-b11-402-11D50b)
  2. OS X 10.7.3
计算机2
  1. Java(TM) SE Runtime Environment (build 1.6.0_29-b11-402-11D50b)
  2. Windows 7
计算机3
  1. Java(TM) SE Runtime Environment (build 1.6.0_29-b11-402-11D50b)
  2. Linux Ubuntu 11.10
以下是我尝试的代码:
public class TComp{
    public static void main(String[] args){
        String a = "arif";
        String b = "arif";
        if(a==b){
            System.out.println("match!");
        }
    }
}

据我所知,在Java中比较字符串应该使用.equal()函数,而'=='会在这种情况下进行内部处理。但是由于不同操作系统的计算机存在差异,为什么在计算机1中内部处理可以正常工作,而在计算机2和计算机3中却出现错误?
如果有任何错误,请纠正。谢谢。

5
简短回答:你(不)幸运。 - Mysticial
1
可能是重复的问题:在JDK 1.6中,String equals操作可以用==替代吗? - Adam Mihalcin
2
但这是一个合理的问题,应该有一个合理的解释(我没有)。 - Bohemian
@TravisJ 不确定你在哪里,但这里已经很晚了——可能是打错字的原因! - assylias
@Mystical 怎么会这么(不)幸运呢? - Arif Setyawan
显示剩余5条评论
7个回答

5
在同一个类中,所有字符串常量都会被编译器(在编译时)折叠到.class文件的常量池中。这意味着编译器只会存储一个字符串副本(因为谁需要常量池中两个相同的常量呢?)。
这意味着在一个类中,字符串的==比较通常有效;但是,在你过于兴奋之前,有一个很好的理由你不应该使用字符串的==比较。没有保证你比较的两个字符串都来自于类内常量池。
因此,
"foo" == new String("foo")

很可能会失败,而

"foo" == "foo"

可能是有效的。这取决于实现方式,如果你按照实现方式编码而不是按照规范,那么如果实现方式发生改变,你可能会遇到非常不愉快的惊喜,因为规范实际上并不要求该实现方式。

总之,每次进行对象比较时,请使用.equals(...)。仅将==用于原始类型比较和“这是相同的对象实例”比较。即使你认为这两个字符串可能已经被合并(或者是相同的对象),因为你永远不知道何时会在不同的类装载器下运行,在不同的JVM实现中运行,或者在一台机器上根本不合并任何内容。


根据JLS 3.10.5规范,"foo" == "foo"应该始终返回true - 这是Java规范,而不是实现细节。同时,"foo" == new String("foo")应该始终为false(尽管"foo" = new String("foo").intern()应该为true)。如果JVM没有这样做,我会说这是一个bug。 - yshavit
@yshavit 我并不是在争论你的观点,也不是说 "foo" == "foo" 不应该返回 true;然而,"foo" == "foo" 几乎总是错误的比较方式。如果你要比较对象的内容相等性(而 String 是对象),那么你应该使用 .equals(Object),因为它是内容相等性运算符。如果你决定采用其他方式,那么你最好真正地围绕实例相等性来构建你的代码。偏爱实例相等性并不是一件坏事,除非你把它当作内容相等性来对待。不一致地偏爱实例相等性是过早优化的迹象。 - Edwin Buck
没有争议,特别是因为在 String(以及大多数类中)的第一个检查是if (this == other) return true; - yshavit

1

这是因为在创建编译时常量字符串时,Java会执行String interning

JLS 15.28. 常量表达式

类型为String的编译时常量表达式总是被“interned”,以共享唯一实例,使用方法String.intern。

这就是为什么当使用“==”进行比较时,您会得到一个true,因为它们实际上是同一个对象。String.valueOf()的工作方式与字符串常量相同。

String x = "a";
String y = "a";
System.out.println(x == y); // true


String w = new String("b");
String z = "b";
System.out.println(w == z); // false

谢谢,检查的方法很有帮助。 :) - Arif Setyawan

1

在一台计算机上,它们是同一个对象,在另一台计算机上则不是。语言的规则并没有指定它们是否是同一个对象,因此可能会出现两种情况。


1

字符串驻留取决于编译器。== 不会"驻留"任何东西;它只是比较对象标识。在某些情况下,a 和 b 可以指向相同的对象。在其他情况下,它们并不是如此。两者都是合法的,因此你确实应该使用 .equals()。

另请参阅 http://docs.oracle.com/javase/specs/jls/se7/html/jls-3.html#jls-3.10.5


你所引用的链接反驳了你声称字符串池是由编译器控制的说法。此外,字符串字面量总是引用 String 类的同一实例。这是因为字符串字面量 - 或者更一般地说,作为常量表达式值(§15.28)的字符串 - 被“interned”以共享唯一实例,使用 String.intern 方法。 - yshavit

0
使用“==”比较对象是不可靠的。除非您确实要查找完全相同的实例,否则不应使用“==”来比较对象的相等性。

@TravisJ Java没有这样的东西。 - David Schwartz
@DavidSchwartz - 你说得对,我实际上认为运算符重载是一种相当常见的功能。这是一个关于此问题的好讨论:http://javarevisited.blogspot.com/2011/08/why-java-does-not-support-operator.html ,感谢您的纠正。 - Travis J

0

双等号运算符(==)用于判断两个对象引用是否指向同一个实例。

而 .equals() 方法则比较对象内的实际字符。

这些对于所在的计算机来说都应该是无关紧要的。


我觉得很有趣的是,那些愿意给你点踩的人却不够能力理解Java中“==”和“equals”的语义。 - James
谢谢詹姆斯...我也是。每个人有自己的选择吧? - Sam

0

最好的方法是始终使用.equals来比较对象。 但是对于字符串,如果由于某种奇怪的原因需要使用==运算符,则需要确保比较.intern method的结果。 它总是返回内部值,并且文档表明它是唯一的。文档说所有的常量也都是内部化和唯一的。


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