String類爲什麼被設計爲不可變的?

在這裏插入圖片描述

從哪看出來String類是不可變的?

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];
}

String類的值是保存在value數組中的,並且是被private final修飾的

  1. private修飾,表明外部的類是訪問不到value的,同時子類也訪問不到,當然String類不可能有子類,因爲類被final修飾了
  2. final修飾,表明value的引用是不會被改變的,而value只會在String的構造函數中被初始化,而且並沒有其他方法可以修改value數組中的值,保證了value的引用和值都不會發生變化

所以我們說String類是不可變的。

而很多方法,如substring並不是在原來的String類上進行操作,而是生成了新的String類

public String substring(int beginIndex) {
	if (beginIndex < 0) {
		throw new StringIndexOutOfBoundsException(beginIndex);
	}
	int subLen = value.length - beginIndex;
	if (subLen < 0) {
		throw new StringIndexOutOfBoundsException(subLen);
	}
	return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
}

爲什麼String被設置爲不可變的?

字符串常量池

Java有8種基本數據類型
整數類型:byte,short,int,long。包裝類型爲Byte,Short,Integer,Long
浮點類型:float、double。包裝類型爲Float,Double
字符類型:char。包裝類型爲Character
布爾類型:boolean。包裝類型爲Boolean

8種包裝類型中除了Float,Double沒有實現常量池,剩下的都實現了,當然都是通過享元模式實現的

String類的常量池是在JVM層面實現的。

爲什麼要有常量池?

常量池是爲了避免頻繁的創建和銷燬對象而影響系統性能,其實現了對象的共享。
例如字符串常量池,在編譯階段就把所有的字符串文字放到一個常量池中。

  1. 節省內存空間:常量池中所有相同的字符串常量被合併,只佔用一個空間。
  2. 節省運行時間:比較字符串時,== 比equals()快。對於兩個引用變量,只用==判斷引用是否相等,也就可以判斷實際值是否相等。

字符串常量池放在哪?

jdk1.7之前的不討論,從jdk1.7開始,字符串常量池就開始放在堆中,然後本文的所有內容都是基於jdk1.8的

下面這個代碼還是經常被問到的

String str1 = "abc";
String str2 = "abc";
String str3 = new String("abc");
String str4 = new String("abc");
// true
System.out.println(str1 == str2);
// false
System.out.println(str1 == str3);
// false
System.out.println(str3 == str4);

內存中的結構如下
在這裏插入圖片描述
其中常量池中存的是引用

解釋一下上面代碼的輸出,Java中有2種創建字符串對象的方式

String str1 = "abc";
String str2 = "abc";
// true
System.out.println(str1 == str2);

採用字面值的方式創建一個字符串時,JVM首先會去字符串池中查找是否存在"abc"這個對象

如果不存在,則在字符串池中創建"abc"這個對象,然後將池中"abc"這個對象的地址賦給str1,這樣str1會指向池中"abc"這個字符串對象

如果存在,則不創建任何對象,直接將池中"abc"這個對象的地址返回,賦給str2。因爲str1、str2指向同一個字符串池中的"abc"對象,所以結果爲true。

String str3 = new String("abc");
String str4 = new String("abc");
// false
System.out.println(str3 == str4);

採用new關鍵字新建一個字符串對象時,JVM首先在字符串池中查找有沒有"abc"這個字符串對象,

如果沒有,則首先在字符串池中創建一個"abc"字符串對象,然後再在堆中創建一個"abc"字符串對象,然後將堆中這個"abc"字符串對象的地址賦給str3

如果有,則不在池中再去創建"abc"這個對象了,直接在堆中創建一個"abc"字符串對象,然後將堆中的這個"abc"對象的地址賦給str4。這樣,str4就指向了堆中創建的這個"abc"字符串對象;

因爲str3和str4指向的是不同的字符串對象,結果爲false。

緩存HashCode

String類在被創建的時候,hashcode就被緩存到hash成員變量中,因爲String類是不可變的,所以hashcode是不會改變的。這樣每次想使用hashcode的時候直接取就行了,而不用重新計算,提高了效率

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {

    /** Cache the hash code for the string */
    private int hash; // Default to 0
	
}

可以用作HashMap的key

由於String類不可變的特性,所以經常被用作HashMap的key,如果String類是可變的,內容改變,hashCode也會改變,當根據這個key從HashMap中取的時候有可能取不到value,或者取到錯的value

線程安全

不可變對象天生就是線程安全的,這樣可以避免在多線程環境下對String做同步操作

歡迎關注

在這裏插入圖片描述

參考博客

[1]https://mp.weixin.qq.com/s?src=11&timestamp=1592637033&ver=2411&signature=alSwI0tpXmnpwBII3UYs1lFZqTk2rx1VymKrT-tfFt86HNwLbLHfMqxloqZhERqHtwi-Ezrx6ksZ-hL19oc0xbqVzXw2yicU77LuqYkFMBlYoBTohvdysefGXMHY6W9l&new=1

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