为什么java中的String是Final或不可变的

该问题在面试过程中经常被提及,考察对String的设计和原理的理解。主要是因为线程安全、性能和安全原因,通过将String修饰为final,不允许被继承,也就避免了对内部实现进行修改的机会。下面对各方面进行描述:

线程安全

因为字符串是不可变的,所以是线程安全的,同一个字符串可以被多个线程共享,不用考虑线程安全而进行同步加锁操作

性能

安全性

下面通过代码示例来加深对字符串分配的理解:

String a = "aa";
String b = "aa";
/* 1: */ System.out.println(a == b); // true

String aa = new String(a);
String bb = new String(b);
/* 2: */ System.out.println(aa == bb); // false
/* 3: */ System.out.println(aa.intern() == bb.intern()); // true
/* 4: */ System.out.println(a == aa.intern()); // true

String a3 = a + b;
String a4 = a + b;
/* 5: */ System.out.println(a3 == a4); // false

String a5 = "aa" + "aa";
String a6 = "aa" + "aa";
/* 6: */ System.out.println(a5 == a6); // true
String a7 = a + "1";
String a8 = a + "1";
/* 7: */ System.out.println(a7 == a8); // false
  1. 因为是字面量的字符串,在分配时就直接存在常量池中了,所以a和b指向相同的常量池地址

  2. 这里比较好理解,因为是new的两2不同的对象

  3. 在调用intern时,会先看常量池中是否已经有相同的字符串,判断是使用的equals(Object)方法,有则直接返回,没有就加到常量池中,然后返回一个指向到分配的常量池地址的引用。所以这里是true,因为"aa"已经在常量池中了

  4. 这里和第3个操作是一样的,都指向同一个引用地址

  5. 这里看似相等,其实是不相等的,因为jvm在这里使用的StringBuilder构造新的字符串,结合javap查看字节码就很清晰了

Code:
    0: ldc           #2                  // String aa
    2: astore_1
    3: ldc           #2                  // String aa
    5: astore_2
    6: new           #3                  // class java/lang/StringBuilder
    9: dup
  10: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
  13: aload_1
  14: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  17: aload_2
  18: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  21: invokevirtual #6                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
  24: astore_3
  25: new           #3                  // class java/lang/StringBuilder
  28: dup
  29: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
  32: aload_1
  33: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  36: aload_2
  37: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  40: invokevirtual #6                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
  43: astore        4
  45: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
  48: aload_3
  49: aload         4
  51: if_acmpne     58
  54: iconst_1
  55: goto          59
  58: iconst_0
  59: invokevirtual #8                  // Method java/io/PrintStream.println:(Z)V
  1. 和第一步相同,都是字面量的字符串

  2. 这里和第5步是一样的,只是这里常量池中有2个,一个是"aa",另外一个是"1",在构造字符串时,使用的是StringBuilder,如下面的字节码

Code:
    0: ldc           #2                  // String aa
    2: astore_1
    3: ldc           #2                  // String aa
    5: astore_2
    6: new           #3                  // class java/lang/StringBuilder
    9: dup
  10: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
  13: aload_1
  14: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  17: ldc           #6                  // String 1
  19: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  22: invokevirtual #7                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
  25: astore_3
  26: new           #3                  // class java/lang/StringBuilder
  29: dup
  30: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
  33: aload_1
  34: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  37: ldc           #6                  // String 1
  39: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  42: invokevirtual #7                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
  45: astore        4
  47: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
  50: aload_3
  51: aload         4
  53: if_acmpne     60
  56: iconst_1
  57: goto          61
  60: iconst_0
  61: invokevirtual #9                  // Method java/io/PrintStream.println:(Z)V