细节问题1:String类与字符串常量池?

细节问题1:String类与字符串常量池

1、String对象创建方式

String str1 = "abcd";
String str2 = new String("abcd");//abcd 也会存进常量池里边,同时堆中也创建一个对象,并返回地址引用
System.out.println(str1==str2);//false

这两种不同的创建方法是有差别的,第一种方式是在常量池中拿对象,第二种方式是直接在堆内存空间创建一个新的对象。
只要使用new方法,便需要创建新的对象。

2、String使用—连接表达式 +

重载“+”

在Java中,唯一被重载的运算符就是用于String的“+”与“+=”。除此之外,Java不允许程序员重载其他的运算符。

(1)只有使用引号包含文本的方式创建的String对象之间使用“+”连接产生的新对象才会被加入字符串池中。
(2)对于所有包含new方式新建对象(包括null)的“+”连接表达式,它所产生的新对象都不会被加入字符串池中。

  String str1 = "str";//字符串常量池中开辟对象,返回地址引用
  String str2 = "ing";//字符串常量池中开辟对象,返回地址引用
  
  String str3 = "str" + "ing";//字符串常量池中开辟新对象,返回地址引用
  String str4 = str1 + str2;//堆中创建新对象,不存进字符串常量池,故于str3地址不同
  System.out.println(str3 == str4);//false
  
  String str5 = "string";//因为str3=string。被存进字符串常量池,这里直接拿来用
  System.out.println(str3 == str5);//true

java基础:字符串的拼接

1、例子1

public class StringPoolTest {
    public static final String a ="ab";
    public static final String b ="ab";
    public static final String c ;
    public static final String d ;
    static{
        c="cd";
        d = "cd";
    }

    public static void main(String[] args) {
        String s0 = a+b;//s是由a,b常量"+"链接的常量,存进字符串常量池
        String t0 = "abab";
        System.out.println((s0==t0)+" zero");//true
        
//c,d虽然被定义为常量,但是由于是用静态代码块进行初始化,必须是在类加载才能确认其值,所以就相当于变量,那么s1自然是不会进入常量池
        String s1 = c+d;
        String t1 = "cdcd";
        System.out.println((s1==t1)+" first");//false

在这里插入图片描述

第一种:A和B都是常量,值是固定的,因此s0的值也是固定的,它在类被编译时就已经确定了。也就是说:String s0=A+B; 等同于:String s0=“ab”+“ab”;

第二种:C和D虽然被定义为常量,但是它们都没有马上被赋值。在运算出s的值之前,他们何时被赋值,以及被赋予什么样的值,都是个变数。因此C和D在被赋值之前,性质类似于一个变量。那么s1就不能在编译期被确定,而只能在运行时被创建了。

2、String s1 = new String(“xyz”); 创建了几个对象?

考虑类加载阶段和实际执行时。
(1)类加载对一个类只会进行一次。"xyz"在类加载时就已经创建并驻留了(如果该类被加载之前已经有"xyz"字符串被驻留过则不需要重复创建用于驻留的"xyz"实例)。驻留的字符串是放在全局共享的字符串常量池中的。
(2)在这段代码后续被运行的时候,"xyz"字面量对应的String实例已经固定了,不会再被重复创建。所以这段代码将常量池中的对象复制一份放到heap中,并且把heap中的这个对象的引用交给s1 持有。
这条语句创建了2个对象。

3、String.intern()

运行时常量池相对于CLass文件常量池的另外一个重要特征是具备动态性,Java语言并不要求常量一定只有编译期才能产生,也就是并非预置入CLass文件中常量池的内容才能进入方法区运行时常量池,运行期间也可能将新的常量放入池中,这种特性被开发人员利用比较多的就是String类的intern()方法。
String的intern()方法会查找在常量池中是否存在一份equal相等的字符串,如果有则返回该字符串的引用,如果没有则添加自己的字符串进入常量池。

public static void main(String[] args) {    
      String s1 = new String("计算机");//存进常量池,同时堆中创建一份,返回地址引用
      String s2 = s1.intern();//intern就会去字符串常量池中寻找有没有s1,有就返回地址,没有就创建并返回。
      String s3 = "计算机";
      System.out.println("s1 == s2? " + (s1 == s2));
      System.out.println("s3 == s2? " + (s3 == s2));
  }
 

结果 :

s1 == s2? false
s3 == s2? true

4、字符串比较更丰富的一个例子

class Other1 { static String hello = "Hello"; }
public class Test {
 public static void main(String[] args) {   
      String hello = "Hello", lo = "lo";
      System.out.println((hello == "Hello") + " 1");
      System.out.println((Other1.hello == hello) + " 2");
   	  System.out.println((other.Other.hello == hello) + " 3");//只要是同一份内容,字符常量池就只有一份
      System.out.println((hello == ("Hel"+"lo")) + " 4");//加号得到“hello”本来就在常量池中,故指向地址一致
      System.out.println((hello == ("Hel"+lo)) + " 5");//lo是变量,而且不进入常量池,是在堆开辟空间,地址不一样
      System.out.println((hello == ("Hel"+lo).intern())+" 6");//使用intern(),检查常量池有没有,没有就创建,显然是有的,就是hello
 }   
}

package other;
public class Other { public static String hello = "Hello"; }
 

在这里插入图片描述

在同包同类下,引用自同一String对象.
在同包不同类下,引用自同一String对象.
在不同包不同类下,依然引用自同一String对象.
在编译成.class时能够识别为同一字符串的,自动优化成常量,引用自同一String对象.
在运行时创建的字符串具有独立的内存地址,所以不引用自同一String对象.

参照博客以及深度好文(赞一个!):字符串常量池、class常量池和运行时常量池

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章