String 是不可变的
String 类中使用 final 关键字修饰字符数组来保存字符串
public final class String implements java.io.Serializable, Comparable<String>, CharSequence {private final char value[];//...
}
final关键字的作用:
- 不可变性:当final用于修饰一个变量时,表示该变量的值在初始化之后不能被修改。这适用于基本数据类型和引用类型。对于基本数据类型,final表示该值在初始化后不能被改变;对于引用类型,final表示该引用在初始化后不能指向其他对象,但是对象本身可以被修改。
- 不可继承性:当final用于修饰一个类时,表示该类不能被其他类继承。这意味着该类的方法不能被覆盖,以及类的行为不能被修改。
- 不可覆盖性:当final用于修饰一个方法时,表示该方法不能被子类覆盖(即不能被子类中的同名方法重写)。
string中的final作用:
1.数组引用在初始化后不能指向其他对象,但是对象本身可以被修改,即数组保存的字符串是可变的
2.虽然对象本身是可变,但是这个数组被 final 修饰且为私有的,并且String 类没有提供/暴露修改这个字符串的方法
3.String 类被 final 修饰导致其不能被继承,进而避免了子类破坏 String 不可变。
Java 9 将 String 的底层实现由 char[] 改成了 byte[],依旧还是不可变。
字符串常量池
字符串常量池 是 JVM 为了提升性能和减少内存消耗针对字符串(String 类)专门开辟的一块区域,主要目的是为了避免字符串的重复创建。
String s1 = new String(“abc”);过程解析
会创建 1 或 2 个字符串对象。
1、如果字符串常量池中不存在字符串对象“abc”的引用,那么它会在堆上创建两个字符串对象,其中一个字符串对象的引用会被保存在字符串常量池中。
public static void main(String[] args) {String S1 = new String("abc");String S2 = "abc";//直接返回字符串常量池中字符串对象”abc“的引用System.out.println(S1 == S2); //false}
2、如果字符串常量池中已存在字符串对象“abc”的引用,则只会在堆中创建 1 个字符串对象“abc”。
// 字符串常量池中已存在字符串对象“abc”的引用
String s1 = "abc";
// 下面这段代码只会在堆中创建 1 个字符串对象“abc”
String s2 = new String("abc");
String#intern 方法
本地方法:
本地方法(Native Method)是指在Java中通过使用本地接口(Native Interface,JNI)调用由其他编程语言编写的函数。这些函数通常是使用C、C++等编程语言编写的,并且在Java程序中通过本地方法接口(Native Interface)进行调用。
String.intern() 是一个 native(本地)方法,其作用是将指定的字符串对象的引用保存在字符串常量池中,可以简单分为两种情况:
- 如果字符串常量池中保存了对应的字符串对象的引用,就直接返回该引用。
- 如果字符串常量池中没有保存了对应的字符串对象的引用,那就在常量池中创建一个指向该字符串对象的引用并返回。
// 在堆中创建字符串对象”Java“
// 将字符串对象”Java“的引用保存在字符串常量池中
String s1 = "Java";
// 直接返回字符串常量池中字符串对象”Java“对应的引用
String s2 = s1.intern();
// 会在堆中在单独创建一个字符串对象
String s3 = new String("Java");
// 直接返回字符串常量池中字符串对象”Java“对应的引用
String s4 = s3.intern();
// s1 和 s2 指向的是堆中的同一个对象
System.out.println(s1 == s2); // true
// s3 和 s4 指向的是堆中不同的对象
System.out.println(s3 == s4); // false
// s1 和 s4 指向的是堆中的同一个对象
System.out.println(s1 == s4); //true
String 类型的变量和常量做“+”运算
字符串不加 final 关键字拼接
String str1 = "str";
String str2 = "ing";
String str3 = "str" + "ing";
String str4 = str1 + str2;
String str5 = "string";
System.out.println(str3 == str4);//false
System.out.println(str3 == str5);//true
System.out.println(str4 == str5);//false
对于常量字符串:对于编译期可以确定值的字符串,也就是常量字符串 ,jvm 会将其存入字符串常量池。并且,字符串常量拼接得到的字符串常量在编译阶段就已经被存放字符串常量池,这个得益于编译器的优化。
**对于引用类型字符串:**引用的值在程序编译期是无法确定的,编译器无法对其进行优化。对象引用和“+”的字符串拼接方式,实际上是通过 StringBuilder 调用 append() 方法实现的,拼接完成之后调用 toString() 得到一个 String 对象 。
字符串使用 final 关键字声明:可以让编译器当做常量来处理。
final String str1 = "str";
final String str2 = "ing";
// 下面两个表达式其实是等价的
String c = "str" + "ing";// 常量池中的对象
String d = str1 + str2; // 常量池中的对象
System.out.println(c == d);// true