String s=new String(“abc“)创建了几个对象?

图片

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

1、引言

本文将围绕字符串常量池与字符串展开介绍。

字符串常量池和运行时常量池是两个不同的概念。运行时常量池存储的是类的字面量,是每个类独有的,而字符串常量池存储的是字符串字面量,是所有类共享的。

JDK1.7字符串常量池在方法区,JDK1.7之后字符串常量池就转移到了堆区。

2、"abc"与newString("abc")的执行原理

2.1、JVM如何执行String s="abc"

String s="abc"会先从字符串常量池(下文简称常量池)中查找,如果常量池中已经存在"abc",而"abc"必定指向堆区的某个String对象,那么直接将s指向这个String对象即可;如果常量池中不存在"abc",则在堆区new一个String对象,然后将"abc"放到常量池中,并将"abc"指向刚new好的String对象。

2.2、JVM如何执行new String("abc")

new String("abc")相当于new String(String s1="abc"),即先要执行String s1="abc"(2.1已经讲过了),然后再在堆区new一个String对象。

因此,现在可以解答本文的标题了,String s=new String("abc")创建了1或2个对象,String s="abc"创建了0或1个对象。

2.3、String类的intern方法

下面介绍一个方法,String类的intern方法,现有如下代码。

String s=new String("abc");
s.intern();

首先会检查常量池中是否存在"abc",如果存在,则s.intern()会返回"abc"指向堆区的对象,如果不存在,则将"abc"放入常量池,并将"abc"指向s指向的对象。

2.4、new String("abc")会将"abc"放到常量池里吗?

答案是会,如何验证呢?

String s1 = new String("abc");
s1.intern();
String s = "abc";
System.out.println(s==s1);// false

下面用反证法证明。

如果不会,那么是.intern()执行之后,常量池中"abc"指向的就是s1指向的对象,s也是指向常量池中"abc"指向的对象,那么s和s1指向的是同一对象,结果就是true,但结果是false,说明会放到常量池。

3、字符串拼接+操作符原理

首先来看String s="hello"+"world"这句代码。

通过javap -c class文件名称可以查看字节码,如下图所示。

图片

可以看到String s="hello"+"world"与String s="helloworld"是一样的。

再看下面一段代码

String s1="hello";
String s2="world";
String s3=s1+s2;

通过javap命令查看字节码,如下图所示。

图片

  • 第一步:new一个StringBuilder;

  • 第二步:append s1;

  • 第三步:append s2;

  • 第四步:调用StringBuilder的toString方法;

这下我们知道了,+操作符的原理是StringBuilder的append方法。

下面再看一道经典面试题,如下代码所示。

String s1 = "a";
String s2 = "b";
String s3 = s1+s2;
String s4 = "ab";
System.out.println(s3==s4);

这道题的疑惑点在于:s1+s2拼接之后的"ab"会不会放进常量池里,如果会,那么s3==s4返回true,如果不会,则返回false。

我们已经知道s1+s2会通过StringBuilder的append方法进行拼接,然后通过StringBuilder的toString方法返回"ab",那我们看一下toString方法的源码,如下图所示。

图片

这下我们知道了,这是new了一个String对象,并且new好的String对象不会放进常量池里,因此s3==s4返回false。

4、为什么String是不可变的?

我们知道,用+操作符拼接字符串,会产生中间对象,如果是线程安全的环境,我们会用StringBuffer拼接字符串,线程不安全的环境则使用StringBuilder。

产生中间对象的原因是String是不可变的,我们看看String类的源码,如下图所示。

图片

String类用声明为final的char数组来存储字符串,在拼接的时候由于char数组不可变,因此会产生中间对象;

那到底产生的是哪些中间对象呢?其实上面已经讲到了,+操作符拼接字符串时,每次会new一个StringBuilder对象,然后将+操作符连接的两个字符串append上,最后toString,而toString又会new一个String对象,因此每使用一次+操作符都会产生2个中间对象。

String类的replace方法只是替换原来的值,因此不会产生中间对象;

那StringBuilder和StringBuffer在拼接字符串时为什么效率高呢?StringBuilder和StringBuffer都继承自AbstractStringBuilder,AbstractStringBuilder的源码如下图所示。

图片

可以看到AbstractStringBuilder中的char数组没有声明为final,因此是可变的。

猜你喜欢

转载自blog.csdn.net/xl_1803/article/details/114390731
今日推荐