String s = new String("123");创建了几个String对象?

提要

最近复习看到一道题目很有意思啊:

String s = new String("123");

问这样一行代码创建了几个String对象?

乍一看我其实以为和JDK的版本有关系,因为在深入理解java虚拟机第三版读书笔记021.6.2 intern方法有意思的点曾经提到,JDK7之前,字符串常量池在方法区(永久代)中,而JDK7开始,就将字符串常量池移到了堆中。那么这个题目和这个知识点有关系吗?

然而仔细一看这行代码,便发现了蹊跷:我们平时定义字符串会使用这样的语法赋值吗?平时常用的给字符串赋值的方法应该是下面这样才对:

String s = "123";

那么这样两行代码有什么区别呢?先从搞清楚"123"是什么开始:

字符串字面量

字符串的字面量和基本类型的字面量有点不同,基本类型可以写1,true,2l这样的字面量。然而在Java中字符串不属于基本类型,String本身是一个类,那么 "123"这样一个字面量肯定也是一个String的实例对象

但是我们之前又提到了“字符串常量池”这个东西,到底什么时候字符串会从常量池中取值呢?下面通过几个例子验证一下:

public static void main(String[] args) {
    String a = "111";
    System.out.println("111" == a);
}

首先,"111"是一个String的实例对象,将它赋值给a,我们通常说,在Java中只有值传递,没有引用传递,就是指这里其实是把"111"的引用地址通过值传递给了a这个引用类型,使得它们指向了同一个对象。

而后面的输出语句中,再一次出现了"111"这个字面量,通过它和a做==比较,看它们指向的是不是同一个对象,结果输出true,说明重复出现的字面量其实指向的是同一个对象,这里可以猜测第一次出现的字符串字面量就创建了一个String实例,并放入了常量池中,后面出现的相同的字面量都是从常量池中取值

那么既然第一次出现的字面量就会将创建的String实例放入字符串常量池,intern这个方法有什么存在的必要呢?其实这是因为字符串不仅仅会以字面量的形式赋值

比如下面这个方法:

public static void main(String[] args) {
    String a = new StringBuilder().append("11").append("1").toString();
    String b = "111";
    System.out.println(a == b);
}

a是通过StringBuilder来构造的,因此没有出现"111"这个字符串字面量,相反而是出现了"11","1"这两个字面量,因此在与b进行比较的时候,输出的是false。

但假设我们仍想在第一行代码中就让"111"进入字符串常量池,就可以使用intern方法:

public static void main(String[] args) {
    String a = new StringBuilder().append("11").append("1").toString();
    a.intern();
    String b = "111";
    System.out.println(a == b);
}

这样就会输出true

通过这里的研究可以知道,下面这样的代码就是多此一举:

String a = "111";
a.intern();

因为第一行其实已经实现了intern的效果。

如何解答这个问题

了解完了字符串字面量这个概念,相信回答这个问题也不难了,

String s = new String("123");

这行代码中,"123"本身是一个String的实例对象,它的来源可能有两种情况:
1. 若之前字符串常量池中没有"123",这里就会创建一个字符串,并放入常量池
2. 若之前字符串常量池中有"123",这里就会直接指向常量池中的对象。

s是怎么来的?看一下这个构造方法(JDK8):

public String(String original) {
    this.value = original.value;
    this.hash = original.hash;
}

可以看到这个构造方法实际上是一个浅复制,只是会将字符串字面量中的value字符数组复制过来。

所以这个问题应该这样回答:

如果在这段代码运行之前字符串常量池中已经有"123"了,就只会创建一个String对象,相反则会创建两次,因为字符串字面量第一次出现会创建一个String实例对象。

与之对应的是,

String s = "123";

这样一行代码倒是有可能一个字符串对象也不创建,因为只是将常量池中对象的引用地址传递了一下。

原创文章,作者:彭晨涛,如若转载,请注明出处:https://www.codetool.top/article/string-s-new-string123%e5%88%9b%e5%bb%ba%e4%ba%86%e5%87%a0%e4%b8%aastring%e5%af%b9%e8%b1%a1%ef%bc%9f/