java 创建string对象机制 字符串缓冲池 字符串拼接机制

时间:2021-01-29 16:19:06

  对于创建String对象的机制,在这一过程中涉及的东西还是值得探究一番的。

  首先看通过new String对象和直接赋值的方式有什么区别,看如下代码:

public static void main(String[] args) {
String str1 = new String("abc");
String str2 = "abc";
String str3 = new String("abc");
    String str4 = "abc";
System.out.println(str1 == str2);
System.out.println(str1 == str3);
System.out.println(str2 == str3);
    System.out.println(str2 == str4);
}

  结果是:false false false true

  我们知道 == 比较的是对象的引用,从代码以及结果可以看出来这段程序中只有三个对象,str1指向一个对象,str3指向一个对象,str2和str4共同指向一个对象。可是到这里有的同学就会迷惑了,我们知道在java中String类是被final修饰的是不可改变的应该每次都会重新生成一个对象啊。可是在这里对象内容都是"abc",所以程序本身也没有发生改变。如果发生改变也不一定会重新生成对象。这都和string机制中的字符串缓冲池有关系。

  当用new的方法创建一个string对象时会先在字符串缓冲池中找有没有和新创建的字符串内容相等的对象,如果没有的话就会在缓冲池新创建一个字符串对象然后再在堆中创建字符串对象,如果缓冲池中已经有了和新创建的字符串内容相等的对象就会直接在堆上新创建对象。如果不用new的方式也是先会看缓冲池中有没有创建过这个对象,如果没有创建就在里边创建一个,如果已经创建了 那新声明的引用就会直接指向这个对象。

  所以main方法的第一行是执行的时候发现缓冲池中并没有一个内容是"abc"的对象所以先在缓冲池中为创建了一个对象内容是"abc",然后在堆中又创建了一个对象。执行到第二行的时候依然会想去缓冲池中找有没有内容是"abc"的字符串对象,发现已经有了。因为他不用在堆上创建对象所以直接把str2指向缓冲池中的对象。第三行一样的道理缓冲池中已经有了所以直接在堆上新建一个就好了。第四行和第二行一样。所以就出现了false false false true的结果。

  再用一段代码验证一下:

public static void main(String[] args) {
String str1 = new String("abc");
String str2 = "abc";
String str3 = new String("abc");
System.out.println(str1 == str2.intern());
System.out.println(str1 == str3.intern());
System.out.println(str2 == str3.intern());
System.out.println(str2 == str1.intern());
}

  结果:false false true true

  首先intern()方法:

  public String intern()返回字符串对象的规范化表示形式。(这句话到底啥意思我也不太清楚)
  当调用 intern 方法时,如果池已经包含一个等于此 String 对象的字符串(该对象由 equals(Object) 方法确定),则返回池中的字符串。否则,将此 String 对象添加到池中,并且返回此 String 对象的引用。 它遵循对于任何两个字符串 s 和 t,当且仅当 s.equals(t) 为 true 时,s.intern() == t.intern() 才为 true。

  也就是说intern()方法返回的字符串对象肯定是池中的对象而且字符串内容和调用该方法的对象的内容一样。那么结果是false false true true也就不难理解了。str3.intern()和str1.intern()返回的对象就是str2所指向的对象呀。所以我们的结论也得以验证。

  在补充一点关于字符串拼接时的情况:

public static void main(String[] args) {
String str1 = "abcd";
String str2 = "ab";
String str3 = "cd";
String str4 = str2 + str3;
String str5 = "ab" + "cd";
System.out.println(str1 == str4);
System.out.println(str1 == str5);
}

  结果:false true

  这时可能就又会迷惑了,哈哈。这就是程序有意思的地方。

  首先说第一个结果是false。str4所指向的对象不应该是缓冲池中的对象吗?讲道理应该是返回true的呀。这就又涉及到字符串拼接的机制了。原来两个字符串str1, str2的拼接首先会调用 String.valueOf(obj),这个Obj为str1,而String.valueOf(Obj)中的实现是return obj == null ? "null" : obj.toString(), 然后产生StringBuilder, 调用的StringBuilder(str1)构造方法, 把StringBuilder初始化,长度为str1.length()+16。此时的StringBuilder对象是在堆上创建的!, 接下来调用StringBuilder.append(str2), 把第二个字符串拼接进去, 然后调用StringBuilder.toString返回结果。所以会返回false。

  而对于第二个结果来说这种拼接方式,jvm会直接把"ab" + "cd" 看成"abcd"。实际上jvm对于这时候的 + (加号)的处理是在编译期就已经完成了。这时候并没有涉及到stringbuilder。