预备知识
equals、==
首先明确一点,==比较的是引用,equals比较的是内容,在类库没有定义equals方法重写的情况下,自然继承的是Object类的equals方法,上源码:
1 | public boolean equals(Object obj) { |
那么对于String类来说,官方已封装了复写之后的equals方法,比较的是具体内容,判断:如果是引用相同,也就是堆内的地址相同,那么就是同一个对象,直接返回true,否则就遍历字符串中的每一个字符进行比较:
1 | public boolean equals(Object anObject) { |
其次,字符串还有个常量池,也就是说如果先定义了一个”ABC”字符串,再定义一个相同字符串的时候,会首先去常量池里面找之前有没有定义,如果有,则直接指向常量池的同一地址。
常量池介绍
JVM为了提高性能和减少内存开销,在实例化字符串常量的时候进行了一些优化
为字符串开辟一个字符串常量池,类似于缓存区
创建字符串常量时,首先坚持字符串常量池是否存在该字符串
存在该字符串,返回引用实例,不存在,实例化该字符串并放入池中
有了以上的铺垫,后面几个问题就好解释了:
案例1
1 | public class StringTests { |
str1与str2都指向的是常量池中同一个字符串的地址,所以==为true,而str3是new了一个新的对象,会在堆中新建一个内存地址,所以比较str3和str3的地址时则为flase,equals就简单了,比较的均为字符串内容,故均为true。
案例2
1 | public class StringTests { |
如果我们新定义一个str3_1变量,让他取str3的intern()方法,此方法是这样定义的:
这是一个native的方法,书上是这样描述它的作用的:如果字符串常量池中已经包含一个等于此String对象的字符串,则返回常量池池中这个字符串的String对象;否则,将此String对象包含的字符添加到常量池中,并返回此String对象的引用。
所以str3_1拿到的是常量池中的”ABC“地址,和str1、str2的地址肯定是相同的,
案例3
1 | public class StringTests { |
这里如果给str1拼接了一个D字符串,这里通过javap命令查看字节码文件,会发现+=操作在String源码中调用的其实是apped方法,生成的是一个新的对象堆地址,str5指向的常量池的地址与str1指向的堆对象地址不一,如果我在str1拼接之后加上str1 = str1.intern(); 这时候由于str1指向的是常量池中的ABCD,==就为true了。
1 | public class StringTests { |
这里intern调用之后,如果常量池中没有”ABCD“,则会在常量池生成,如果有则直接指向它。所以此处的str1指向的是常量池中的”ABCD“,后面定义的str5在构造时则会直接指向常量池中的”ABCD“
如果这里把str1 += ”D“修改一下成常量与常量的拼接:
1 | public class StringTests { |
如果两个常量进行拼接,编译器会自动优化成”ABCD”,也就是存到了常量池中去了,str5指向的也是常量池中的值。
总结
原则:
(1)常量+常量:结果是常量池
(2)常量与变量 或 变量与变量:结果是堆
(3)拼接后调用intern方法:结果在常量池
new 出来的是在堆空间




