不可變類


不可變類

先來科普2個概念,可變類和不可變類。

1),不可變類的意思就是創建該類的實例後,該實例的實例變量是不可改變的。Java提供的8個包裝類和String類都是不可變類,當創建他們的實例後,其實例的實例變量是不

可改變的。

2),與不可變類對應的是可變類,可變類的含義是該類的實例變量是可變的。大部分時候所創建的類都是可變類,特別是JavaBean,因爲總是在其實例變量提供了setter和

getter方法。

看下面的代碼:

Double d = new Double(6.5);
		String linkin = "LinkinPark";
上面的程序創建了一個Double對象和一個String對象,併爲這兩個對象傳入了6.5和"LinkinPark"字符串作爲參數,那麼Double類和String類肯定需要提供實例變量來保存這兩個

參數,但程序無法修改這兩個實例變量的值,因此Double類和String類沒有提供修改它們的方法。


如果需要創建自定義的不可變類,要遵守以下的規則:

1),使用private和final修飾該類的成員變量

2),提供帶參數的構造器,用於根據傳入參數來初始化該類的成員變量

3),僅爲該類提供getter方法,不要提供setter方法,因爲普通的方法不能改變這個類的屬性

4),如果有必要,重寫equals和hashcode方法。

/**
 * 不可變類
 * 
 * @author LinkinPark
 * 
 *         <pre>
 *         1,屬性使用private final修飾
 *         2,構造器對屬性賦值
 *         3,只提供get方法,不提供set方法
 *         4,如果有需要就重寫equals和hashCode方法
 *         </pre>
 */
public class LinkinPark
{

	private final String name;
	private final Integer age;

	public LinkinPark(String name, Integer age)
	{
		super();
		this.name = name;
		this.age = age;
	}

	public String getName()
	{
		return name;
	}

	public Integer getAge()
	{
		return age;
	}

	public static void main(String[] args)
	{
		new LinkinPark("LinkinPark", 25);
	}

}

與可變類相比,不可變類的實例在整個生命週期中永遠出於初始化階段,它的實例變量不可改變,因此對不可變類的實例的控制將更加簡單。

前面介紹final關鍵字時提到,當使用final修飾引用類型變量時,僅表示這個引用類型變量不可被重新賦值,但引用類型變量所指向的對象依然可以改變。

這就產生了一個問題,當創建一個不可變類時,如果它包含成員變量的類型是可變的,那麼其對象的成員變量的值依然是可變的,那這個不可變類其實是失敗的

看下面的例子:

/**
 * 引用類型的變量導致不可變類失敗
 * 
 * @author LinkinPark
 */
public class LinkinPark
{

	private final String name;
	private final Linkin linkin;

	public LinkinPark(String name, Linkin linkin)
	{
		super();
		this.name = name;
		this.linkin = linkin;
	}

	public String getName()
	{
		return name;
	}

	public Linkin getLinkin()
	{
		return linkin;
	}

	@Override
	public String toString()
	{
		return getClass().getSimpleName() + " [name=" + name + ", linkin=" + linkin + "]";
	}

	public static void main(String[] args)
	{
		Linkin linkin = new Linkin();
		linkin.setName("NightWish1");
		linkin.setAge(25);
		LinkinPark linkinPark = new LinkinPark("LinkinPark", linkin);
		System.out.println(linkinPark);

		linkin.setAge(24);
		linkin.setName("NightWish2");
		System.out.println(linkinPark);
		// LinkinPark [name=LinkinPark, linkin=Linkin [name=NightWish1, age=25]]
		// LinkinPark [name=LinkinPark, linkin=Linkin [name=NightWish2, age=24]]
	}

}

class Linkin
{
	private String name;
	private Integer age;

	public String getName()
	{
		return name;
	}

	public void setName(String name)
	{
		this.name = name;
	}

	public Integer getAge()
	{
		return age;
	}

	public void setAge(Integer age)
	{
		this.age = age;
	}

	@Override
	public String toString()
	{
		return getClass().getSimpleName() + " [name=" + name + ", age=" + age + "]";
	}

}
運行上面的代碼,我們也看到了,引用類型的變量導致我創建了一個失敗的不可變類。那應該要怎麼做呢?看下面代碼:

/**
 * 引用類型的變量導致不可變類失敗
 * 所以要針對引用類型的變量做專門的處理
 * 
 * <pre>
 * 1,構造器中不要直接使用傳入的引用類型變量,自己取值然後重新new一次
 * 2,引用類型的變量用來存儲剛纔那個初始化的對象
 * 3,防止get方法直接返回剛纔那個變量從而改變引用的那個對象,同樣的方式處理
 * </pre>
 * 
 * @author LinkinPark
 */
public class LinkinPark
{

	private final String name;
	private final Linkin linkin;

	/**
	 * @param name
	 * @param linkin
	 */
	public LinkinPark(String name, Linkin linkin)
	{
		super();
		this.name = name;
		this.linkin = new Linkin(linkin.getName(), linkin.getAge());
	}

	public String getName()
	{
		return name;
	}

	public Linkin getLinkin()
	{
		return new Linkin(linkin.getName(), linkin.getAge());
	}

	@Override
	public String toString()
	{
		return getClass().getSimpleName() + " [name=" + name + ", linkin=" + linkin + "]";
	}

	public static void main(String[] args)
	{
		Linkin linkin = new Linkin("NightWish1", 25);
		LinkinPark linkinPark = new LinkinPark("LinkinPark", linkin);
		System.out.println(linkinPark);

		linkin.setAge(24);
		linkin.setName("NightWish2");
		System.out.println(linkinPark);
		// LinkinPark [name=LinkinPark, linkin=Linkin [name=NightWish1, age=25]]
		// LinkinPark [name=LinkinPark, linkin=Linkin [name=NightWish2, age=24]]
	}

}

class Linkin
{
	private String name;
	private Integer age;

	public Linkin(String name, Integer age)
	{
		super();
		this.name = name;
		this.age = age;
	}

	public String getName()
	{
		return name;
	}

	public void setName(String name)
	{
		this.name = name;
	}

	public Integer getAge()
	{
		return age;
	}

	public void setAge(Integer age)
	{
		this.age = age;
	}

	@Override
	public String toString()
	{
		return getClass().getSimpleName() + " [name=" + name + ", age=" + age + "]";
	}

}


緩存實例的不可變類

不可變類的實例狀態不可改變,可以很方便的被多個對象所共享。如果程序經常需要使用相同的不可變實例,則應該考慮緩存這種不可變類的實例。畢竟重複創建相同的對象沒

有太大的意義,而且加大系統開銷。如果可能,應該將已經創建的不可變類的實例進行緩存。

緩存是軟件設計中一個非常有用的模式,緩存的實現方式也有很多種,不同的實現方式可能存在較大的性能差別,關於緩存的性能問題和實現方式我會在後面的博客中整理一個

分類,此處不做贅述。

OK,前面我已經使用了不可變類LinkinPark,現在我自己用一個數組寫一個緩存池,從而實現一個緩存LinkinPark實例的緩存池

當然也可以直接在LinkinPark類中寫緩存,這樣子將實現一個緩存自己實例的不可變類。

public class LinkinParkCache
{
	// 定義一個數組+一個下標+數組最大容量
	private static int POS_INDEX = 0;
	private static final int MAX_SIZE = 10;
	private static final LinkinPark[] cache = new LinkinPark[MAX_SIZE];

	// 定義一個name標示用來重寫hashCode方法
	private final String name;

	private LinkinParkCache(String name)
	{
		this.name = name;
	}

	public String getName()
	{
		return name;
	}

	public static LinkinPark valueOf(String name)
	{
		// 1,循環獲取緩存的實例
		for (int i = 0; i < cache.length; i++)
		{
			if (cache[i] != null && cache[i].getName().equals(name))
			{
				return cache[i];
			}
		}
		// 2,循環結束後沒有找見實例,則向緩存中添加
		if (POS_INDEX == MAX_SIZE)
		{
			cache[0] = new LinkinPark(name, new Linkin("LinkinPark", 25));
			POS_INDEX = 1;
		}
		else
		{
			cache[POS_INDEX++] = new LinkinPark(name, new Linkin("LinkinPark", 25));
		}
		return cache[POS_INDEX - 1];
	}

	@Override
	public boolean equals(Object obj)
	{
		if (this == obj)
		{
			return true;
		}
		if (obj != null && obj.getClass() == this.getClass())
		{
			LinkinPark linkinPark = (LinkinPark) obj;
			return linkinPark.getName().equals(this.getName());
		}
		return false;
	}

	@Override
	public int hashCode()
	{
		return name.hashCode();
	}

	public static void main(String[] args)
	{
		LinkinPark linkin = LinkinParkCache.valueOf("林肯的緩存池");
		LinkinPark linkinPark = LinkinParkCache.valueOf("林肯的緩存池");
		// 下面代碼輸出true,使用了緩存
		System.out.println(linkin == linkinPark);
	}

}

/**
 * 不可變類
 * 
 * @author LinkinPark
 */
class LinkinPark
{

	private final String name;
	private final Linkin linkin;

	public LinkinPark(String name, Linkin linkin)
	{
		super();
		this.name = name;
		this.linkin = new Linkin(linkin.getName(), linkin.getAge());
	}

	public String getName()
	{
		return name;
	}

	public Linkin getLinkin()
	{
		return new Linkin(linkin.getName(), linkin.getAge());
	}

	@Override
	public String toString()
	{
		return getClass().getSimpleName() + " [name=" + name + ", linkin=" + linkin + "]";
	}

	public static void main(String[] args)
	{
		Linkin linkin = new Linkin();
		linkin.setName("NightWish1");
		linkin.setAge(25);
		LinkinPark linkinPark = new LinkinPark("LinkinPark", linkin);
		System.out.println(linkinPark);

		linkin.setAge(24);
		linkin.setName("NightWish2");
		System.out.println(linkinPark);
		// LinkinPark [name=LinkinPark, linkin=Linkin [name=NightWish1, age=25]]
		// LinkinPark [name=LinkinPark, linkin=Linkin [name=NightWish1, age=25]]
	}

}

/**
 * 不可變類中的引用類型變量的定義
 * 
 * @author LinkinPark
 */
class Linkin
{
	private String name;
	private Integer age;

	public Linkin()
	{
		super();
	}

	public Linkin(String name, Integer age)
	{
		super();
		this.name = name;
		this.age = age;
	}

	public String getName()
	{
		return name;
	}

	public void setName(String name)
	{
		this.name = name;
	}

	public Integer getAge()
	{
		return age;
	}

	public void setAge(Integer age)
	{
		this.age = age;
	}

	@Override
	public String toString()
	{
		return getClass().getSimpleName() + " [name=" + name + ", age=" + age + "]";
	}

}


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