深入了解new String()

问题一:这段代码创建了几个对象?

String str1 = new String("aa");

答案是两个
“aa”对象和String对象

其中有一项是常量池
常量池在Class文件被加载的时候,会被加载进内存中的方法区中的运行时常量池,而运行时常量池里就包括字符串常量池,Class文件中的字符串在类加载时就会加载到字符串常量池中去
在这里插入图片描述

不过在周志明老师在深入java虚拟机中有说到,到了JDK1.7时,字符串常量池就被移出了方法区,转移到了堆里了。

String str1 = new String(“aa”);
"aa"就是被加载进去的字符串,我们可以看看Class文件
在这里插入图片描述

  1. 这里的aa在之后类加载的时候,会在字符串常量池里创建一个 "aa"对象,这是第一个对象
  2. 类加载完成了之后,那就要开始正式执行代码了,执行该行代码时new一个"aa"的String对象存放在Java堆中,这是第二个对象
  3. 创建完第二个对象后,虚拟机栈上的str1将会指向第二个对象,也就是堆上的对象

问题二:输出结果是true还是false?

 String str1 = new String("aa");
 String str2 = "aa";
 System.out.println(str1 == str2);

答案很明显是false,因为两个变量指向的地址不同,一个指向字符串常量池,一个指向堆上的对象,而==比较的就是地址。

问题三:输出结果是true?

 String str1 = new String("aa");
 str1.intern();
 String str2 = "aa";
 System.out.println(str1 == str2);

首先我们来了解一下intern方法
intern的处理是 先判断字符串常量是否在字符串常量池中,如果存在直接返回该常量,如果没有找到,说明该字符串常量在堆中,则处理是把堆区该对象的引用加入到字符串常量池中,以后别人拿到的是该字符串常量的引用,实际存在堆中。

也就是说现在字符串常量池中的"aa"实际上是指向堆上的String对象的?所以结果是true?
并不是,结果还是false

回到问题一

String str1 = new String("aa");

这段代码创建了两个对象,而第一个就是在字符串常量池中的,而intern方法在判断时会发现字符串常量池中已经存在"aa"对象了,所以它就不用把字符串常量池中添加一个指向堆上的String对象的地址了
所以最后intern方法只是返回了"aa"对象,并没有做任何修改

所以还是str1指向堆,str2指向字符串常量池,结果为false

问题四:那要怎么样才能true?

String str3 = new String("a") + new String("a");
str3.intern();
String str4 = "aa";
System.out.println(str3 == str4);

这里打印的结果就是true了

这里的str3生成的方式不再是new String(“aa”);
而是new String(“a”) + new String(“a”);拼接起来的方式,因此在编译后,Class文件中的常量池写入的是"a"对象而不是"aa"对象,如下图:
在这里插入图片描述

因此intern方法在判断时会发现字符串常量池中并没有"aa"对象,于是它就把堆中String对象的引用加入到字符串常量池中。
之后创建str4的时候,str4就会先在字符串常量池中先查找有没有"aa",于是它找到了intern放入的引用,并把这个引用赋给str4
所以str3和str4都是同一个引用,str3==str4,为true

问题五:那么这段代码又创建了几个对象?

String str3 = new String("a") + new String("a");

答案是五个

因为使用+号的String字符串拼接,底层其实都是先创建一个StringBuilder对象,然后调用append方法把要+的字符串都append进去,最后toString创建一个新的String对象如下图:

在这里插入图片描述

红色的地方就是new出来对象的语句,而绿色则是两次append
四个红色一共四个对象,再加上字符串常量池上创建的"a"对象,一共五个

这也正是为什么阿里巴巴代码规范中不建议在for循环里使用+号拼接字符串在这里插入图片描述

String str1 = "aaa";
String str2 = "bbb";
String str4 = str1 + str2;

这个的String str4 = str1 + str2;创建了两个对象,StringBuilder和toString时生成的String对象

那下面这段呢?是"aaa"对象加"bbb"对象加StringBuilder和toString时生成的String对象一共四个对象吗?

String str5 = "aaa" + "bbb";

很可惜这段只创建了1个对象
java编译器在编译这段的时候做了优化,实际上"aaa"+"bbb"会先拼接成"aaabbb"之后才开始编译,也就是说这段代码等于是String str5 = “aaabbb”
如下图:(code里面没有任何new操作)
在这里插入图片描述

  • 59
    点赞
  • 125
    收藏
    觉得还不错? 一键收藏
  • 25
    评论
Java中的String a = new String("a")创建了两个对象,一个是存储字符串"a"的String常量池中的对象,另一个则是通过new关键字在堆内存中新创建的String对象。 下面我们来深入探讨一下这个问题,需要了解以下几个概念: 1. String对象的不可变性 2. 字符串常量池 3. Java中的内存分配 1. String对象的不可变性 在Java中,String对象是不可变的,一旦被创建,它的值就不能被更改。这是因为Java中的String类是final的,不能被继承。 例如,当我们对String对象进行修改时,实际上是创建了一个新的String对象,而原来的String对象并没有被修改。 例如: ``` String str = "hello"; str = str + "world"; ``` 在这个例子中,"hello"和"world"都是String对象,但是当我们将它们拼接起来时,实际上是创建了一个新的String对象"helloworld",而原来的"hello"和"world"对象并没有被修改。 2. 字符串常量池 字符串常量池是Java中的一种特殊的内存区域,用于存储String常量。在Java中,如果我们使用双引号定义一个字符串,那么这个字符串就会被存储在字符串常量池中。例如: ``` String str1 = "hello"; String str2 = "hello"; ``` 在这个例子中,str1和str2都是指向字符串常量池中的"hello"对象,因此它们在内存中是同一个对象。 需要注意的是,字符串常量池中的对象是不可变的,一旦被创建,它的值就不能被更改。 3. Java中的内存分配 Java中的内存分为栈内存和堆内存两种。栈内存用于存储局部变量和方法调用栈,而堆内存用于存储对象。 当我们使用new关键字创建一个对象时,它会被分配到堆内存中。例如: ``` String str = new String("hello"); ``` 在这个例子中,"hello"字符串被存储在字符串常量池中,而通过new关键字创建的String对象则被分配到了堆内存中。 结合以上三个概念,我们可以对题目进行深入分析: ``` String a = new String("a"); ``` 根据第3点,new关键字会在堆内存中创建一个新的String对象,因此这里至少会创建一个对象。 根据第1点和第2点,我们知道字符串常量池中已经有了一个值为"a"的String对象,如果这个对象没有被其他变量引用,那么第一次执行上述代码时,会在堆内存中创建一个新的String对象,并将常量池中的"a"字符串复制到堆内存中的新对象中。因此,此时会创建两个对象,一个在字符串常量池中,一个在堆内存中。 但是,如果常量池中已经有了一个值为"a"的String对象,并且它被其他变量引用了,那么第一次执行上述代码时,会在堆内存中创建一个新的String对象,但是这个对象中的字符串值会直接指向常量池中的"a"字符串。因此,此时只会创建一个对象。 总之,在Java中,创建String对象的方式很多,而创建的对象数量也会受到多种因素的影响。对于一个简单的语句,可能需要深入分析才能得出准确的答案。
评论 25
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值