初学JAVA时,在学习如何比较两个字符串是否相等,大量资料告诉我,不能用等于号( = )去比较,需要使用equals方法,理由是String是一个对象,等号此时比较的是两个字符串在java内存堆中的地址。
看下面代码:
String a = "abc";
String b = "abc";
System.out.println(a==b);
此时控制台打印的结果是:true
a和b是两个地址,==是地址的比较,a和b肯定是两个地址的,a==b为true呢?
这又是为什么呢?
求知欲驱使我一番搜索,以下摘抄自《深入理解JAVA虚拟机》:
运行时常量池(Runtime Constant Pool)是方法区的一部分。用于存放编译期生成的各种字面量与符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。
(众所周知,JDK1.8版本中,String常量池已经从方法区中的运行时常量池分离到堆中了,这本书是19年出版的,作者还是沿用旧版的描述。那么在堆中的String常量池里存的是String对象还是引用呢?)
运行时常量池具备动态性,运行期间也可以将新的常量放入池中,使用较多的便是String类的 intern()方法。
《深入理解JAVA虚拟机》这本书的描述让人困惑,干脆直接看JDK源码里的intern方法。
/**
* Returns a canonical representation for the string object.
* <p>
* A pool of strings, initially empty, is maintained privately by the
* class {@code String}.
* <p>
* When the intern method is invoked, if the pool already contains a
* string equal to this {@code String} object as determined by
* the {@link #equals(Object)} method, then the string from the pool is
* returned. Otherwise, this {@code String} object is added to the
* pool and a reference to this {@code String} object is returned.
* <p>
* It follows that for any two strings {@code s} and {@code t},
* {@code s.intern() == t.intern()} is {@code true}
* if and only if {@code s.equals(t)} is {@code true}.
* <p>
* All literal strings and string-valued constant expressions are
* interned. String literals are defined in section 3.10.5 of the
* <cite>The Java™ Language Specification</cite>.
*
* @return a string that has the same contents as this string, but is
* guaranteed to be from a pool of unique strings.
*/
public native String intern();
翻译成中文就是说,当调用 intern方法时,如果池已经包含一个等于此String对象的字符串(用equals(oject)方法确定),则返回池中的字符串。否则,将此String对象添加到池中,并返回此String对象的引用。
刚好有一篇文章:
他的问题完全一样,摘抄一段:
附:Java中两种创建字符串对象的方式的分析。
String s1 = "abc"; String s2 = "abc"; System.out.println(s1==s2);
结果是 true;
采用字面值的方式创建一个字符串时,JVM首先会去字符串池中查找是否存在"abc"这个对象,如果不存在,则在字符串池中创建"abc"这个对象,然后将池中"abc"这个对象的引用地址返回给"abc"对象的引用s1,这样s1会指向池中"abc"这个字符串对象;如果存在,则不创建任何对象,直接将池中"abc"这个对象的地址返回,赋给引用s2。因为s1、s2都是指向同一个字符串池中的"abc"对象,所以结果为true。
而:
String s3 = new String("xyz"); String s4 = new String("xyz"); System.out.println(s3==s4);
结果是 false
采用new关键字新建一个字符串对象时,JVM首先在字符串池中查找有没有"xyz"这个字符串对象,如果有,则不在池中再去创建"xyz"这个对象了,直接在堆中创建一个"xyz"字符串对象,然后将堆中的这个"xyz"对象的地址返回赋给引用s3,这样,s3就指向了堆中创建的这个"xyz"字符串对象;如果没有,则首先在字符串池中创建一个"xyz"字符串对象,然后再在堆中创建一个"xyz"字符串对象,然后将堆中这个"xyz"字符串对象的地址返回赋给s3引用,这样,s3指向了堆中创建的这个"xyz"字符串对象。s4则指向了堆中创建的另一个"xyz"字符串对象。s3 、s4是两个指向不同对象的引用,结果当然是false。
自己试了一下:
回到开头的结论是:java代码编译成class文件时,JVM自己优化了,此时b的地址指向a,所以a==b打印结果是true