一、不可變類簡介
不可變類:所謂的不可變類是指這個類的執行個體一旦建立完成後,就不能改變其成員變數值。如JDK內部內建的很多不可變類:Interger、Long和String等。
可變類:相對於不可變類,可變類建立執行個體後可以改變其成員變數值,開發中建立的大部分類都屬於可變類。
二、不可變類的優點
說完可變類和不可變類的區別,我們需要進一步瞭解為什麼要有不可變類?這樣的特性對JAVA來說帶來怎樣的好處?
1.安全執行緒
不可變對象是安全執行緒的,線上程之間可以相互共用,不需要利用特殊機制來保證同步問題,因為對象的值無法改變。可以降低並發錯誤的可能性,因為不需要用一些鎖機制等保證記憶體一致性問題也減少了同步開銷。
2.易於構造、使用和測試
3....
三、不可變類的設計方法
對於設計不可變類,個人總結出以下原則:
1. 類添加final修飾符,保證類不被繼承。
如果類可以被繼承會破壞類的不可變性機制,只要繼承類覆蓋父類的方法並且繼承類可以改變成員變數值,那麼一旦子類以父類的形式出現時,不能保證當前類是否可變。
2. 保證所有成員變數必須私人,並且加上final修飾
通過這種方式保證成員變數不可改變。但只做到這一步還不夠,因為如果是對象成員變數有可能再外部改變其值。所以第4點彌補這個不足。
3. 不提供改變成員變數的方法,包括setter
避免通過其他介面改變成員變數的值,破壞不可變特性。
4.通過構造器初始化所有成員,進行深拷貝(deep copy)
如果構造器傳入的對象直接賦值給成員變數,還是可以通過對傳入對象的修改進而導致改變內部變數的值。例如:
public final class ImmutableDemo { private final int[] myArray; public ImmutableDemo(int[] array) { this.myArray = array; // wrong } }
這種方式不能保證不可變性,myArray和array指向同一塊記憶體位址,使用者可以在ImmutableDemo之外通過修改array對象的值來改變myArray內部的值。
為了保證內部的值不被修改,可以採用深度copy來建立一個新記憶體儲存傳入的值。正確做法:
public final class MyImmutableDemo { private final int[] myArray; public MyImmutableDemo(int[] array) { this.myArray = array.clone(); } }
5. 在getter方法中,不要直接返回對象本身,而是複製對象,並返回對象的拷貝
這種做法也是防止對象外泄,防止通過getter獲得內部可變成員對象後對成員變數直接操作,導致成員變數發生改變。
四、String對象的不可變性
string對象在記憶體建立後就不可改變,不可變對象的建立一般滿足以上5個原則,我們看看String代碼是如何?的。
public final class Stringimplements java.io.Serializable, Comparable<String>, CharSequence{/** The value is used for character storage. */private final char value[];/** The offset is the first index of the storage that is used. */private final int offset;/** The count is the number of characters in the String. */private final int count;/** Cache the hash code for the string */private int hash; // Default to 0....public String(char value[]) {this.value = Arrays.copyOf(value, value.length); // deep copy操作}...public char[] toCharArray() {// Cannot use Arrays.copyOf because of class initialization order issueschar result[] = new char[value.length];System.arraycopy(value, 0, result, 0, value.length);return result;}...}
如上代碼所示,可以觀察到以下設計細節:
1.String類被final修飾,不可繼承
2.string內部所有成員都設定為私人變數
3.不存在value的setter
4.並將value和offset設定為final。
5.當傳入可變數組value[]時,進行copy而不是直接將value[]複製給內部變數.
6.擷取value時不是直接返回對象引用,而是返回對象的copy.
這都符合上面總結的不變類型的特性,也保證了String類型是不可變的類。
五、String對象的不可變性的優缺點
從上一節分析,String資料不可變類,那設定這樣的特性有什麼好處呢?我總結為以下幾點:
1.字串常量池的需要.
字串常量池可以將一些字元常量放在常量池中重複使用,避免每次都重新建立相同的對象、節省儲存空間。但如果字串是可變的,此時相同內容的String還指向常量池的同一個記憶體空間,當某個變數改變了該記憶體的值時,其他遍曆的值也會發生改變。所以不符合常量池設計的初衷。
2. 安全執行緒考慮。
同一個字串執行個體可以被多個線程共用。這樣便不用因為安全執行緒問題而使用同步。字串自己便是安全執行緒的。
3. 類載入器要用到字串,不可變性提供了安全性,以便正確的類被載入。譬如你想載入java.sql.Connection類,而這個值被改成了myhacked.Connection,那麼會對你的資料庫造成不可知的破壞。
4. 支援hash映射和緩衝。
因為字串是不可變的,所以在它建立的時候hashcode就被緩衝了,不需要重新計算。這就使得字串很適合作為Map中的鍵,字串的處理速度要快過其它的鍵對象。這就是HashMap中的鍵往往都使用字串。
缺點:
1.如果有對String對象值改變的需求,那麼會建立大量的String對象。
六、String對象的是否真的不可變
雖然String對象將value設定為final,並且還通過各種機制保證其成員變數不可改變。但是還是可以通過反射機制的手段改變其值。例如:
//建立字串"Hello World", 並賦給引用sString s = "Hello World"; System.out.println("s = " + s); //Hello World//擷取String類中的value欄位Field valueFieldOfString = String.class.getDeclaredField("value");//改變value屬性的存取權限valueFieldOfString.setAccessible(true);//擷取s對象上的value屬性的值char[] value = (char[]) valueFieldOfString.get(s);//改變value所引用的數組中的第5個字元value[5] = '_';System.out.println("s = " + s); //Hello_World
列印結果為:
s = Hello Worlds = Hello_World
發現String的值已經發生了改變。也就是說,通過反射是可以修改所謂的“不可變”對象的
總結
不可變類是執行個體建立後就不可以改變成員遍曆的值。這種特性使得不可變類提供了安全執行緒的特性但同時也帶來了對象建立的開銷,每更改一個屬性都是重新建立一個新的對象。JDK內部也提供了很多不可變類如Integer、Double、String等。String的不可變特性主要為了滿足常量池、安全執行緒、類載入的需求。合理使用不可變類可以帶來極大的好處。
以上所述是小編給大家介紹的JAVA不可變類(immutable)機制與String的不可變性(推薦),希望對大家有所協助,如果大家有任何疑問請給我留言,小編會及時回複大家的。在此也非常感謝大家對雲棲社區網站的支援!