Java設計模式之immutable(不可變)模式

來源:互聯網
上載者:User

標籤:

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(不可變)模式

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.