Java併發設計模式--不可變模式(immutable)

擁抱心中的夢想發表於2018-04-02

一、什麼是不可變模式?

不可變,顧名思義,就是物件建立之後就不能夠變化嘛!更具體地說,就是物件建立之後它的屬性值不能夠發生變化!所有對原物件的操作都會返回原物件的拷貝。那麼在java中怎麼做到這一點呢?答案就是使用final關鍵字。下面我將講講如何設計出一個`immutable``物件。

設計一個不可變類應該遵循以下幾點:

  • 1、類的所有屬性宣告為private,去除掉所有的setter方法,防止外界直接對其進行修改
  • 2、類的宣告採用final進行修飾,保證沒有父類對其修改
  • 3、類的屬性宣告為final,如果物件型別為可變型別,應對其重新包裝,重新new一個物件返回

下面是一個不可變類例項:

package com.wokao66;

/**
 * 不可變類
 * @author: huangjiawei
 * @since: 2018年4月2日
 * @version: $Revision$ $Date$ $LastChangedBy$
 *
 */
//採用fianl修飾,防止子類繼承
public final class Immutable {

	/**
	 * 所有的屬性private且final
	 */
	private final String name;
	private final int age;

	/**
	 * 構造方法
	 * @param name
	 * @param age
	 */
	public Immutable(String name, int age) {
		this.name = name;
		this.age = age;
	}

	/**
	 * 去除所有的setter方法
	 */
	public String getName() {
		return name;
	}

	public int getAge() {
		return age;
	}

	/**
	 * 將年齡增加10歲
	 * @param newAge
	 * @return
	 */
	public Immutable addAge(int newAge) {
		/**
		 * 重新返回一個物件
		 */
		return new Immutable(this.getName(), newAge + this.getAge());
	}

	public static void main(String[] args) {
		Immutable immutable = new Immutable("a", 12);
		System.err.println(immutable.getAge());
		Immutable newImmutable = immutable.addAge(10);
		System.err.println(immutable.getAge());
		System.err.println(newImmutable.getAge());
	}
}
複製程式碼

執行結果:

12
12
22
複製程式碼

二、一不小心就設計成可變物件了!

如果上面的不可變類這樣設計,那麼就變成可變的了!

package com.wokao66;

import java.util.Date;

/**
 * 人生處處有驚喜,一不小心就掉進陷阱裡
 * @author: huangjiawei
 * @since: 2018年4月2日
 * @version: $Revision$ $Date$ $LastChangedBy$
 */
public final class Mutable {

	/**
	 * 所有的屬性private且final
	 */
	private final String name;
	private final int age;
	private final Date birthday;

	/**
	 * 構造方法
	 * @param name
	 * @param age
	 */
	public Mutable(String name, int age, Date birthday) {
		this.name = name;
		this.age = age;
		this.birthday = birthday;
	}

	/**
	 * 去除所有的setter方法
	 */
	public String getName() {
		return name;
	}

	public int getAge() {
		return age;
	}

	public Date getBirthday() {
		return birthday;
	}

	/**
	 * 將年齡增加10歲
	 * @param newAge
	 * @return
	 */
	public Mutable addAge(int newAge) {
		/**
		 * 重新返回一個物件
		 */
		return new Mutable(this.getName(), newAge + this.getAge(), this.birthday);
	}

	public static void main(String[] args) {
		Date birthday = new Date();
		Mutable xiaoming = new Mutable("小明", 21, birthday);
		System.err.println("小明的生日為 : " + xiaoming.getBirthday());

		//我設定下我的生日,你會發現我的生日居然可以改變
		birthday.setTime(System.currentTimeMillis() + 1000000000);
		System.err.println("小明的生日為 : " + xiaoming.getBirthday());
	}
}
複製程式碼

輸出如下:

小明的生日為 : Mon Apr 02 15:33:44 CST 2018
小明的生日為 : Sat Apr 14 05:20:24 CST 2018
複製程式碼

可見結果發生了變化,因為Date是可變型別的。將構造方法修改如下:

	/**
	 * 構造方法
	 * @param name
	 * @param age
	 */
	public Mutable(String name, int age, Date birthday) {
		this.name = name;
		this.age = age;
		//對於可變型別的屬性,初始化的時候應該重新生成一個
		//this.birthday = new Date(birthday.getTime());
		this.birthday = birthday;
	}
複製程式碼

輸出如下:

小明的生日為 : Mon Apr 02 15:36:24 CST 2018
小明的生日為 : Mon Apr 02 15:36:24 CST 2018
複製程式碼

二、不可變模式優缺點及應用場景

優點:

  • 1、因為是不可變的,所以不允許程式對其進行修改,避免了程式中修改資料帶來的異常產生
  • 2、由於物件是不可變的,減少了執行緒同步帶來的開銷

缺點:

  • 1、每次返回都建立新的物件,記憶體會有一定的開銷,不容易被垃圾回收器回收,造成資源的浪費

應用場景:

  • 1、不適合大物件、且建立頻繁的場景,因為物件大且建立頻繁會容易導致記憶體洩漏
  • 2、適合表示抽象資料型別(如數字、列舉型別或顏色)的值
  • 3、適合在多執行緒環境中進行同步而不需要考慮執行緒同步

相關文章