在Java中substrin*生的结果不共享问题探讨

这个问题其实涉及到 JVM 方法区中的 “常量池”概念。什么叫“常量池”呢?如果 Java 程序遇到字符串就 new 一
个对象出来,其实是很浪费奢侈的行为。因此,JVM 针对字符串的获取做了优化,在方法区中新增一块“字符串常量池”
用来存放字符串常量,每次创建字符串常量时,首先判断字符串常量池是否存在该字符串,如果存在则返回该字符串的
引用实例;如果不存在,就实例化一个再放入池中。为啥能实现这个“常量池”的功能呢,其实一方面是字符串常量
是 final 修饰的无法被后续修改因此编译期便可以确定;二是常量池对每个对象在整个程序生命周期中都维护一个引用
来保证常量不会被GC回收。

有了上述概念我们先看第一个:
String a = "1";
String b = "1";
a == b 结果为 true
返回true的原因是因为上述 case 中的 a 和 b 对应的值 “1”是字符串常量,二者均是直接对常量池中“1”这个字

符串常量的引用,因此返回true。

substring 源码实现如下,如果 substring 的前后标截取的就是源字符串那就返回引用,如果不是就调用 new String(value, beginIndex, subLen)

public String substring(int beginIndex, int endIndex) {
        if (beginIndex < 0) {
            throw new StringIndexOutOfBoundsException(beginIndex);
        }
        if (endIndex > value.length) {
            throw new StringIndexOutOfBoundsException(endIndex);
        }
        int subLen = endIndex - beginIndex;
        if (subLen < 0) {
            throw new StringIndexOutOfBoundsException(subLen);
        }
        return ((beginIndex == 0) && (endIndex == value.length)) ? this
                : new String(value, beginIndex, subLen);

    }

因此对于如下代码返回结果是 true:

public class Main 
{
public static void main(String[] args) 
{
        String a = "1234";
        String b = a.substring(0,4);
        System.out.println(a == b);
}

}

在Java中substrin*生的结果不共享问题探讨

图1

但对于这种需要调用 new 的代码返回结果就是 false:

public class Main
{
public static void main(String[] args) 
{
String a = "1234";
String b = "123";
String c = a.substring(0,3);
System.out.println(b == c);
}

}

在Java中substrin*生的结果不共享问题探讨

图2

因为当使用 new 方法创建对象时不会去常量池寻找该值是否已存在,只要你用了 new 那么结果都会实例化一个新的
对象出来(这个对象在堆上,其值可能是引用常量池中已存在的常量),所以 substring 相当于创建了两个新的对象,
返回的引用自然是不同的。
如果不想担心这些问题,对字符串直接使用 equals 比较是最方便的。因为 equals 的实现中是先比较引用、再是类
型(instance of)、再是长度、再是挨个比较字符。