不可變類
先來科普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 + "]";
}
}