標籤:
immutable簡介
不可變對象永遠不會發生改變,其欄位的值只在建構函式運行時設定一次,其後就不會再改變。例如JDK中常見的兩種基礎資料型別 (Elementary Data Type)String和Integer,它們都是不可變對象。為了理解immutable與mutable的區別,可以看看下面的一段代碼:
package date0804.demo2;import java.awt.Point;public class ImmutableString {public static void main(String[] args) {//String,immutableString str = new String("new book");System.out.println(str);str.replaceAll("new", "old");System.out.println(str);//Point,mutablePoint point = new Point(0,0);System.out.println(point);point.setLocation(1, 0);System.out.println(point);}}
運行結果為
new booknew bookjava.awt.Point[x=0,y=0]java.awt.Point[x=1,y=0]
我們看到point的值發生了改變,而str的值並沒有改變。String類型的對象一旦在記憶體中建立,它的值就不會發生改變,改變的只能是對該對象引用的指標。若想建立可以改變的String,則可以使用StringBuilder和StringBuffer類建立字串,更加靈活,可以進行添加、插入和追加新的內容等操作。
[拓展,下面一段代碼與immutable無關]再看另外一個有趣的String執行個體:
package date0804.demo2;public class ImmutableString {public static void main(String[] args){String str=new String("xyz");change(str);System.out.println(str);}public static void change(String s) {s="xml";}}
上面這段代碼的輸出結果為:
xyz
根據我的理解,Java中方法的參數是按值傳遞的,在這裡,Java在記憶體堆上建立了一個字串"xyz",然後將該字串的記憶體位址賦給了str變數,即str中儲存了"xyz"記憶體位址的引用,而change()方法是按值傳遞的,相當於再建立了一個字串對象"xml",而這個對象又有另外一個不同的地址引用,並非之前的x儲存的引用。
在並發編程中,一種被普遍認可的原則就是:儘可能的使用不可變對象來建立簡單、可靠的代碼。不可變對象特別有用,由於建立後不能被修改,所以不會出現由於線程幹擾產生的錯誤或是記憶體一致性錯誤。使用不可變對象降低了記憶體回收所產生的額外開銷,也減少了用來確保使用可變對象不出現並發錯誤的一些額外代碼。
immutable的優點
不可變對象可以在很大程度上簡化我們的程式碼,並且具有以下優點:
——不可變對象易於構造,使用和測試;
——自動地為安全執行緒型,避免了一些繁雜的同步問題;
——不需要一個複製建構函式(copy constructor);
——不需要一個clone的實現;
——允許hashCode()方法的懶漢模式實現,並且緩衝它的傳回值;
——當作為一個欄位時,不需要複製;
——一旦構造以後,具有類型不變性,不用檢查;
——[引用名言]if an immutable object throws an exception, it‘s never left in an undesirable or indeterminate state。
immutable的設計方法
——確保類不會被覆寫,即該類不會被繼承,實現這一點要加上修飾符final;或者使用靜態工廠建立方法,確保建構函式私人的;
——所有的欄位必須是私人的,並且加上修飾符final;
——所有的設定只需簡單的一個建構函式即可,不要使用空建構函式,不要使用Javabean風格的建構函式;
——不要提供任何可以改變對象的方法,不僅僅是setter,一切能夠修改對象狀態的方法都要避免;
——如果該類有可變欄位的對象,則必須進行保守型複製,並且只能由該類自身進行修改。
immutable類的代碼執行個體
final public class ImmutableRGB { // 常量欄位 final private int red; final private int green; final private int blue; final private String name; //私人方法,檢查三基色的值是否在0~255之間,若不符合,則拋出異常 private void check(int red, int green, int blue) { if (red < 0 || red > 255 || green < 0 || green > 255 || blue < 0 || blue > 255) { throw new IllegalArgumentException(); } } //唯一的一個構造方法,先檢查欄位是否合法,然後傳遞所有欄位 public ImmutableRGB(int red, int green, int blue, String name) { check(red, green, blue); this.red = red; this.green = green; this.blue = blue; this.name = name; } //get方法 public int getRGB() { return ((red << 16) | (green << 8) | blue); } //get方法 public String getName() { return name; } //對顏色值進行求逆 public ImmutableRGB invert() { return new ImmutableRGB(255 - red, 255 - green, 255 - blue, "Inverse of " + name); }}
從上面一段代碼可以看出,這是一個不可變的類,其對象是一個不可變對象。對象一旦建立,就不會被修改。最後的一段代碼,反映了一旦對對象進行修改,不要返回對象本身,而是拷貝一份返回。這種不可變對象很好地解決了並發編程中的競爭問題,不用考慮線程的同步,因為對象一旦建立,不會改變。
immutable類的不正確使用
下面一段代碼顯示了表面上看起來是一個immutable類,實際上則是一個mutable類:
import java.util.Date;public final class BrokenPerson{private String firstName;private String lastName;private Date dob;public BrokenPerson( String firstName, String lastName, Date dob){this.firstName = firstName;this.lastName = lastName;this.dob = dob;}public String getFirstName(){return this.firstName;}public String getLastName(){return this.lastName;}public Date getDOB(){return this.dob;}}
這段代碼看起來沒有問題,但是 請注意dob是一個Date對象,則說明它是一個mutable對象欄位,如果客戶用下面一段代碼去調用:
Date myDate = new Date();BrokenPerson myPerson = new BrokenPerson( "David", "O‘Meara", myDate );System.out.println( myPerson.getDOB() );myDate.setMonth( myDate.getMonth() + 1 );System.out.println( myPerson.getDOB() );
那麼,返回的結果有可能是
Mon Mar 24 21:34:16 GMT+08:00 2013Thu Apr 24 21:34:16 GMT+08:00 2013
因此,我們看到myPerson實際上是一個mutable對象,問題就出在BrokenPerson具有一個mutable對象的欄位,那麼 我們應該使用它的拷貝來建立對象,而不是直接引用:
import java.util.Date;public final class BetterPerson{private String firstName;private String lastName;private Date dob;public BetterPerson( String firstName, String lastName, Date dob){this.firstName = firstName;this.lastName = lastName;this.dob = new Date( dob.getTime() );} //....... public Date getDOB() {return new Date( this.dob.getTime() ); }
注意,對於所有mutable的欄位,應該要使用拷貝來在類中"進進出出",這樣才比較安全。
總結
本文簡單介紹了immutable的優點及其設計方法,不可變對象最大的缺點就是建立對象的開銷,因為每一步操作都會產生一個新的對象,然而合理地使用immutable可以帶來極大的好處。不可變類最適合表示抽象資料類型(如數字、枚舉類型或顏色)的值。Java 類庫中的基礎資料型別 (Elementary Data Type)的封裝類(如Integer 、 Long 和 Float )都是不可變的,其它數字類型(如 BigInteger 和 BigDecimal )也是不可變的。表示複數或任意精度的有理數的類將比較適合設計為不可變類。甚至包含許多離散值的抽象類別型(如向量或矩陣)也很適合設計成不可變類,這取決於你的應用程式。
另一個適合用不可變類實現的好樣本就是事件 。事件的生命期較短,而且常常會在建立它們的線程之外的線程中消耗,所以使它們成為不可變的是利大於弊。大多數 AWT 事件類別都沒有嚴格的作為不可變類來實現。同樣地,在通訊系統的組件間進行訊息傳遞,將訊息對象設計成不可變的是明智的。
Java設計模式之immutable(不可變)模式