关于String中的new String(String original)的深度认识

关于String中的new String(String original)的深度认识

1 参数是否在类加载的时候已经存在于字符串常量池中了?

答案是肯定的。
在编译的时候,字符串类型的实参arg就已经被放入常量表中了。
public class StringTest {
    public static void main(String[] args) {
        String str = new String("he");
    }
}

反编译后的字节码文件内容如下:
Last modified 2021-2-2; size 516 bytes
MD5 checksum efcc3e1edfd34c926f0d4571f8089e94
Compiled from "StringTest.java"
public class com.tj.string.StringTest
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #6.#22
   #2 = Class              #23
   #3 = String             #24
   #4 = Methodref          #2.#25
   #5 = Class              #26
   #6 = Class              #27
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               LocalVariableTable
  #12 = Utf8               this
  #13 = Utf8               Lcom/tj/string/StringTest;
  #14 = Utf8               main
  #15 = Utf8               ([Ljava/lang/String;)V
  #16 = Utf8               args
  #17 = Utf8               [Ljava/lang/String;
  #18 = Utf8               str
  #19 = Utf8               Ljava/lang/String;
  #20 = Utf8               SourceFile
  #21 = Utf8               StringTest.java
  #22 = NameAndType        #7:#8
  #23 = Utf8               java/lang/String
  #24 = Utf8               he
  #25 = NameAndType        #7:#28
  #26 = Utf8               com/tj/string/StringTest
  #27 = Utf8               java/lang/Object
  #28 = Utf8               (Ljava/lang/String;)V
{
  public com.tj.string.StringTest();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 9: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/tj/string/StringTest;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=3, locals=2, args_size=1
         0: new           #2                  // class java/lang/String
         3: dup
         4: ldc           #3                  // String he
         6: invokespecial #4                  // Method java/lang/String."<init>":(Ljava/lang/String;)V
         9: astore_1
        10: return
      LineNumberTable:
        line 11: 0
        line 12: 10
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      11     0  args   [Ljava/lang/String;
           10       1     1   str   Ljava/lang/String;
}
SourceFile: "StringTest.java"
在Constant pool:中的#24对应的就是he。说明早在编译的时候已经存在了。
它存在于方法区内存空间的字符串常量池中

2 堆中的String对象与常量池中“he”对象之间的关系

new String("he")的执行过程:
第一步:先执行new操作符
	在堆中为String类型的对象分配内存空间
	给char[] value分配空间
	给int hash分配空间
	给char[] value数组分配空间,要注意,这里char[] value只是一个"引用"
	存放的是内存地址。
	存放的是谁的内存地址?当然是常量池中的"he"对象的char[] value的内存地址。
	因此JVM会按照方法区常量池中的"he"对象的char[] value数组所占地址空间大小
	给堆中String对象的char[] value引用赋值。
	
重要的补充:
	"he"是常量,它是String类型的常量,
	是以对象的形式存放的,即String有的成员属性它都有。
	也就是说它有char[] value,被private final修饰
	它也有int hash,被private修饰

第二步:执行赋值的操作
	char[] value = 0x1234;
	这个char[] value是堆中String对象的属性,是一个引用,它存放了方法区内存
	字符串常量池中"he"对象的char[] value的地址0x1234(假设是这个值)。
	int hash,默认为0。
	hash要有值,一定要调用hashCode()方法。这个方法调用以后才会给hash属性赋值。
	这部分操作,我们稍后讨论。
	还有其他的赋值操作,比如this赋值等。

3 System.out.println(str)的结果“he”是怎么打印出来的

这也是一个困扰点:
	翻看源码不难发现,println(str)它调用PrintStream中的
	public void println(String x)
	该方法调用了PrintStream中的
	public void print(String s)
	该方法调用了PrintStream中的
	private void write(String s)
	该方法调用了Writer中的
	public void write(String str)
	该方法调用了Writer中的
	public void write(String str, int off, int len)
	该方法调用String中的
	public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin)
	以及BufferedWriter中重写的
	write(cbuf, 0, len)
	最后将字符串拆成字符一个个打印出来
	实际上拿到的还是常量池中的"he"的char[] value数组。将其打印出来。

4 关于hash值

翻看String中hashCode的源码:
private int hash; // Default to 0
public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {
            char val[] = value;

            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
    }

这就很好理解了,hash值一开始就是0,没有调用hasCode()方法就不会有值,只是0而已。
当调用方法后,hash值其实很好算,就是通过char[] value数组中的元素为基础
进行计算的结果,在String中并不是以内存地址进行计算的。
同样的String对象的引用,调用String类中的hashCode方法,
得到的结果应该在哪里都一样。
"he"的hash值,在谁的JVM中运行都应该是一样的。
 
现在我们基本上能明白,new String(String original)到底在干嘛了。
  • 总结:
1.new String(String original)构造方法,从本质上讲是多此一举的
  因为original所指向的实参在类加载的时候就已经放入字符串常量池中了
2.这种做法只是在堆中做了一份方法区内存常量池中存放的original字符串对象的拷贝
  在堆中多创建了一个对象。这也是SUN公司不推荐在一般情况下使用的原因。
内存图如下:

在这里插入图片描述

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值