Java中的字符串:equals vs ==
可能重复:
如何比较Java中的字符串?
String s1 = "andrei"; String s2 = "andrei"; String s3 = s2.toString(); System.out.println((s1==s2) + " " + (s2==s3));
给出以下代码为什么第二个比较s2 == s3为真? 什么是s2.toString()返回? 实际位于何处(s2.toString())
?
首先, String.toString
是一个无操作:
/** * This object (which is already a string!) is itself returned. * * @return the string itself. */ public String toString() { return this; }
其次,字符串常量被实现,因此s1和s2在后台更改为相同的String实例。
String.intern()方法可用于确保相等的字符串具有相同的引用。 字符串常量是intern
,因此s1
和s2
将引用相同的字符串。 String.toString()
只返回自身,也就是说,当a是String时, a.toString()
返回a。 所以,s2也== s3。
通常,字符串不应该通过引用相等来比较,而是通过值相等来使用equals()
。 原因是很容易得到两个相同但不同引用的字符串。 例如,在创建子字符串时。 此规则的一个例外是,如果您知道两个字符串都已预先intern
(或者您将它们作为比较的一部分实习)。
要回答有关堆或堆栈的隐含问题,请在堆上分配字符串。 即使它们被分配在堆栈上,例如即将进行的转义分析和堆栈分配,程序的语义也不会改变,并且您将获得堆和堆栈分配相同的结果。
基本上当你使用new String("something")
你就迫使Java创建一个全新的对象。
当您分配给String literal ="something"
,该字符串存储在常量池中,由JVM完成优化。 因此,当您为同一个常量分配另一个引用时,存储在常量池中的Object将被重用,基本上,它是同一个对象。
在比较java字符串时,您应该使用.equals而不是==。
==比较引用,因此s2.ToString()返回s2。
从关于堆/堆栈的java词汇表:
In Sun's JVM, the interned Strings (which includes String literals) are
存储在称为perm gen的特殊RAM池中,其中JVM还加载类并存储本机编译的代码。 但是,intered Strings的行为与它们存储在普通对象堆中的行为没有区别。
从java虚拟机规范:
字符串文字,更一般地说,作为常量表达式值的字符串被“实例化”,以便使用String.intern方法共享唯一实例。
"andrei"
是一个字符串文字,因此“实习”。 因此,String s1
和s2
引用相同的String对象,一个内容为"andrei"
的实际String。
因此(s1 == s2) && (s1.equals(s2))
为true
。 String#toString()
不会创建新的String
(就像String
许多其他方法一样)但是简单的返回this
。 因此, s2 == s3
为true
。
为什么第一个结果是假的?
==比较引用,您创建两个不同的对象。
我明白,对于没有原始类型,当我们做’==’时,
String不是原始的。 当引用相同的对象时,引用将是==
。
鉴于==
比较引用,你可以看到s2 == s3
为真, s2.toString()
返回s2。
由于String是不可变的,所以toString方法的一个有用的实现是 – 在String类中 – 返回它。
例如,我的rt.jar包含以下实现:
public String toString() { return this; }
因此,与s3
相关联的引用与与s3
相关联的引用相同。
考虑到s1==s2
语句,这是由于对所有常量字符串的自动调用intern()
。 这意味着在编译时,s2初始化代码将被s2=s1
替换,这使得断言非常明显,不是吗?
只有interned( String.intern()
)字符串可以安全地与==
进行比较,对于所有其他情况,你应该使用equals
。
在您的情况下,您定义自己的类型MyString
,它与java String
有很小的共同点,因此比较s
和s1
,比较对两个不同对象的引用,这就是为什么你得到false
。
MyString
每个实例都位于不同的内存位置,因此,忘记实例的内容,对于每两个不同的实例, ==
test将导致false
。
在String
类的情况下,当你做一个String
变量的赋值并且右边的操作符是文字(即String s = "foo";
)时,有一个小但重要的区别,一个新的内存位置将去只有在以前没有遇到“foo”作为文字时才被“foo”占用。 如果是这种情况(即String s = "foo"; String otherS = "foo";
),则其他otherS
只会引用已经存在的“foo”。
此行为称为字符串池 。
比较s == s1
您正在比较两个不同的MyString
对象。 想想你的情况
s
和s1
是不同的对象,但它们的属性指向同一个toto
实例。
由于您创建它们的方式,它们是不同的对象:
MyString s = new MyString("toto"); MyString s1 = new MyString("toto");
所以s == s1
将返回false
。 要使它返回true
,它们必须是同一个对象。 你可以这样做:
MyString s = new MyString("toto"); MyString s1 = s;
那将是结果
第一次比较失败的原因是,您通过调用new
创建两个对象。 现在, ==
运算符会比较两个内存地址 ,这会产生你得到的返回值,因为这两个对象不在同一个内存单元格中。
它与常量字符串一起工作的原因是,java编译器javac
确实优化了代码。 通过该优化,类似的字符串常量被放置在同一个存储器单元中 。 如果您已完成以下操作,那么您的String
对象的结果将是相同的。
String s2 = new String("toto"); String s3 = new String("toto"); System.out.println(s2==s3); //yields false!!
你要走的路是.equals(其他)。 为此,你必须在类Mystring中实现equals方法:
class MyString{ private String s; public MyString (String s){ this.s = s; } public String getContent(){ return s; } @Override public boolean equals(Object other){ if(other instanceof MyString){ MyString compareTo = (MyString) other; return this.s.equals(compareTo.getContent()); } return false; } }
“==”是比较参考。 但是Object类中的Equals()方法只是比较引用。 并且子类中方法equals()的行为基于重写此方法的实现。
在字符串上使用==
时,仅比较引用。 因此, ==
仅保证在以下情况下返回true
:
String s1 = "..."; String s2 = s1; // reference assignment!
这里, s1 == s2
。 在所有其他情况下,即使两个字符串包含相同的字符序列, ==
也可能返回true
。
要比较两个字符串的内容,请使用equals()
:
if (s1.equals(s2)) { ... }
s2.toString()返回一个String表示。 因为它已经是一个String,它会自行返回(这就是比较为真的原因)。
所有字符串都在Heap上分配,coparison运算符只是比较它们是否是同一个对象(这就是为什么s1!= s2)。