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