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 ,因此s1s2将引用相同的字符串。 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 s1s2引用相同的String对象,一个内容为"andrei"的实际String。

因此(s1 == s2) && (s1.equals(s2))trueString#toString()不会创建新的String (就像String许多其他方法一样)但是简单的返回this 。 因此, s2 == s3true

为什么第一个结果是假的?

==比较引用,您创建两个不同的对象。

我明白,对于没有原始类型,当我们做’==’时,

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有很小的共同点,因此比较ss1 ,比较对两个不同对象的引用,这就是为什么你得到false

MyString每个实例都位于不同的内存位置,因此,忘记实例的内容,对于每两个不同的实例, == test将导致false

String类的情况下,当你做一个String变量的赋值并且右边的操作符是文字(即String s = "foo"; )时,有一个小但重要的区别,一个新的内存位置将去只有在以前没有遇到“foo”作为文字时才被“foo”占用。 如果是这种情况(即String s = "foo"; String otherS = "foo"; ),则其他otherS只会引用已经存在的“foo”。

此行为称为字符串池 。

比较s == s1您正在比较两个不同的MyString对象。 想想你的情况

在此处输入图像描述

ss1是不同的对象,但它们的属性指向同一个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)。