1. 用靜態工廠方法代替構造器 說明 在方法內部添加一個靜態方法,用於獲取一個對象,代替構造器的功能; 比如,在boolean包裝Boolean類中,就有valueOf方法可以代替構造方法獲得一個Boolean對象; public static Boolean valueOf(boolean b) ...
1. 用靜態工廠方法代替構造器
說明
在方法內部添加一個靜態方法,用於獲取一個對象,代替構造器的功能;
比如,在boolean
包裝Boolean類中,就有valueOf
方法可以代替構造方法獲得一個Boolean對象;
public static Boolean valueOf(boolean b) {
return (b ? TRUE : FALSE);
}
優勢
-
靜態方法有名字,可以指定一個功能作為方法名;
-
實現對象重用,優化程式運行;
-
在對象使用結束後,可以將對象緩存起來,若下次調用可以再次使用;
-
相對對象重用,創建一個新的對象損耗可能會更大;
-
在情況允許時,儘量多地使用對象重用,減少創建對象造成額外損耗;
-
如Boolean類:Boolean類載入結束後,預設會創建兩個Boolean對象,分別表示true和false,在使用靜態工廠創建對象時,直接將代表true或false的對象返回,以節約記憶體使用和程式效率。
public final class Boolean implements java.io.Serializable, Comparable<Boolean> { //預設創建兩個Boolean對象,用於表示TRUE和FALSE public static final Boolean TRUE = new Boolean(true); public static final Boolean FALSE = new Boolean(false); // 包裝了boolean類,這裡存值 private final boolean value; // 構造方法新創建了一個Boolean對象 public Boolean(boolean value) { this.value = value; } //使用valueOf方法,直接返回Boolean類載入時創建的兩個靜態對象,無需再次創建對象。 public static Boolean valueOf(boolean b) {return (b ? TRUE : FALSE);} public static Boolean valueOf(String s) { return parseBoolean(s) ? TRUE : FALSE; } }
-
-
依據不同的參數,可以返回任何子類的對象,也可以返回不同的對象;
- 應用:
- 靜態方法可以返回對象,而無需將對象的類設為公有的;
- 靜態方法可以通過介面返回不同的對象。
EnumSet
沒有構造器,只能通過靜態工廠創建對象,在OpenJDK實現中,EnmuSet的實現有兩種類型:RegalarEumSet
和JumboEnumSet
;當枚舉元素數量等於小於64時,靜態工廠方法返回RegalarEumSet對象;當枚舉元素數量大於64時,靜態工廠方法返回JumboEnumSet對象。(對於調用者,無需知道背後的實現原理,直接使用就好;對於EnumSet開發者,此做法用於代碼優化。)
- 應用:
-
方法返回的對象所屬的類,在編寫靜態工廠方法的類時可以不存在;
- 如mysql和JDBC;
缺點
-
類如果不包含public的構造器,則不能被繼承;
-
靜態工廠方法需要程式員主動去尋找,而非構造方法可以直接使用;
- 示例:
class Apple { private String type; private String status; //構造方法名與類名相同,可以直接使用 public Apple(String type, String status) { this.type = type; this.status = status; } //靜態工廠方法需要在API中尋找,沒有構造方法方便 public static Apple getNewApple(){return new Apple("redApple","fresh");} }
-
一些慣例
-
from,從別的類型進行轉換,只有一個參數;
-
of,將多個參數合併;
//將多個參數合併到一起 public Set<Apple> of(String ...colors){ Set<Apple> apples = new HashSet<>(); for (String s : colors) { apples.add(new Apple("Red")); } return apples; }
- valueOf,也是類型轉換;
- createInstance或getInstance,通過參數獲取一個對象,參數可以與成員變數不同;
- createInstance或netInstance,保證每次返回一個新創建的實例;
- getInstance一般用在單例模式。
- getType(這裡可以是getApple),與getInstance一致;
- newType,與netInstance類似;
- type,getType和newType的簡化版。
-
2. 遇到多個構造器參數,可以考慮使用構建器(Builder)
說明
若一個類有多個參數,且對象使用構建器進行創建;
- 有些參數有些時候不需要輸入,但構造器中必須填入一個值;
- JavaBeans模式,即一堆setter方法,這樣可以解決上面的問題,但JavaBeans模式有嚴重的缺點,在構造過程中JavaBean可能處於不一致狀態,即線程不安全。
這個時候,就可以考慮使用建造者Builder模式
public class 建造者模式 {
public static void main(String[] args) {
Cat cat = new Cat.Builder("小黑")
.age(12).color("White").build();
System.out.println(cat);
}
}
class Cat{
private String name;
private int age;
private String color;
private String owner;
public static class Builder{
//必要參數
private String name;
//可選參數
private int age;
private String color;
private String owner;
public Builder(String name) {this.name = name;}
public Builder age(int val){age=val;return this;}
public Builder color(String val){color=val;return this;}
public Builder owner(String val){owner=val;return this;}
public Cat build(){return new Cat(this);}
}
public Cat(Builder builder) {
owner = builder.owner;
color = builder.color;
age = builder.age;
name = builder.name;
}
// toString
}
Builder模擬了具有名字的可選參數,這樣的客戶端易於編寫,易於閱讀;
示例
代碼
//這裡先創建一個抽象類FriedRice
//然後分別創建兩個類繼承FriedRice,分別為FriedRiceWithHam和FriedRiceWithEgg
//fried rice 炒飯 可以添加 老乾媽LaoGanMa、辣條LaTiao、再加一個雞蛋Egg等
// ham 火腿 egg雞蛋
//FriedRiceWithHam 火腿炒飯,可以有:大、中、小 三種 LARGE MEDIUM SMALL
//FriedRiceWithEgg 蛋炒飯,spicy辣度 可以選擇:little微辣 general中辣 very特辣
//具體開發中不要使用中文,也不要使用拼音
//先整一個抽象類FriedRice
abstract class FriedRice{
//額外要加的東西
public enum Ingredient{老乾媽,辣條,Egg}//實際開發不要使用中文
private Set<Ingredient> ingredientSet;
abstract static class Builder<T extends Builder<T>>{
EnumSet<Ingredient> ingredients = EnumSet.noneOf(Ingredient.class);//預設沒有配料
public T addIngredient(Ingredient val){ingredients.add(val);return self();}//添加配料
public abstract FriedRice build();
protected abstract T self();
}
FriedRice(Builder<?> builder){
ingredientSet = builder.ingredients.clone();
}
}
//創建一個FriedRiceWithHam火腿炒飯
@ToString(callSuper = true)//是Lombok插件的註解,可以自動生成toString方法,文章主要講解內容不包含這部分,忽略就好
class FriedRiceWithHam extends FriedRice{
public enum Size{SMALL,MEDIUM,LARGE}
private Size size;//大小
public static class Builder extends FriedRice.Builder<Builder>{
private Size size;
public Builder(Size size){this.size = size;}
@Override public FriedRice build() {return new FriedRiceWithHam(this);}
@Override protected Builder self() {return this;}
}
FriedRiceWithHam(Builder builder) {
super(builder);
this.size = builder.size;
}
}
//創建一個FriedRiceWithEgg雞蛋炒飯
@ToString(callSuper = true)//是Lombok插件的註解,可以自動生成toString方法,文章主要講解內容不包含這部分,忽略就好
class FriedRiceWithEgg extends FriedRice{
public enum Spicy{LITTLE,GENERAL,VERY}
private Spicy spicy;
public static class Builder extends FriedRice.Builder<Builder>{
private Spicy spicy;
public Builder(Spicy spicy){this.spicy = spicy;}
@Override public FriedRice build() {return new FriedRiceWithEgg(this);}
@Override protected Builder self() {return this;}
}
FriedRiceWithEgg(Builder builder) {
super(builder);
spicy = builder.spicy;
}
}
使用
public class Builder模式也適用於類層次結構 {
public static void main(String[] args) {
//創建一個雞蛋炒飯,中辣,添加老乾媽
FriedRice friedRiceWithEgg = new FriedRiceWithEgg.Builder(FriedRiceWithEgg.Spicy.GENERAL)
.addIngredient(FriedRice.Ingredient.老乾媽).build();
//創建一個火腿炒飯,大份,添加雞蛋
FriedRice friedRiceWithHam = new FriedRiceWithHam.Builder(FriedRiceWithHam.Size.LARGE)
.addIngredient(FriedRice.Ingredient.Egg).build();
}
}
3. 用私有構造器或枚舉類型強化Singleton屬性
Singleton,即單例模式;對於一個類,只會被實例化一次,後續通過靜態方法獲取對象也只能獲取到這一個對象,不會再次創建新的對象。
創建一個Singleton,有兩種方式
私有構造器
將構造器私有化,然後通過getInstance方法創建並獲取對象。
發展
預設情況下,可以通過以下方式實現單例模式。
//Chopsticks n.筷子
//這裡假定筷子只能有一根
//這裡創建一個單例對象
class Chopstick{
private static final Chopstick INSTANCE = new Chopstick();//類載入後,自動創建一個Chopstick對象,
private Chopstick(){}//構造器私有化,禁止二次創建
public static Chopstick getInstance(){return INSTANCE;}//獲取實例
}
但是,這個單例是可以通過反射進行破壞;
public static void main(String[] args) throws Exception {
Chopstick instance = Chopstick.getInstance();//第一個實例對象
//第二個實例對象
Class<?> aClass = Class.forName("com.yn.study.chapter1.Chopstick");//獲取Class對象
Constructor<?> declaredConstructor = aClass.getDeclaredConstructor();//獲取Constru對象
declaredConstructor.setAccessible(true);//跳過private檢查
Chopstick chopstick = (Chopstick) declaredConstructor.newInstance();//創建實例對象
System.out.println(instance);
System.out.println(chopstick);
/**輸出結果如下:
* com.yn.study.chapter1.Chopstick@7f31245a
* com.yn.study.chapter1.Chopstick@6d6f6e28
* 表示這兩個對象不是同一個對象
*/
}
所以,可以在構造方法裡面添加判斷,讓第二次創建過程拋出錯誤來解決破壞;
class Chopstick{
private static final Chopstick INSTANCE = new Chopstick();//類載入後,自動創建一個Chopstick對象,
private Chopstick() {if (INSTANCE!=null)throw new Error("請不要二次創建對象");}//構造器私有化,禁止二次創建
public static Chopstick getInstance(){return INSTANCE;}//獲取實例
}
如果要使對象變得可序列化,必須聲明readResolve方法
如果要使對象變得可序列化,僅僅在聲明中加上implements Serializable
是不夠的,為了維護Singleton,必須聲明所有實例域是transient(瞬時)的,並聲明readResolve方法;
否則,每當反序列化一個對象,都會創建一個新的對象;
public static void main(String[] args) throws Exception {
//單例破解方案——序列化:將對象存儲於文件中,然後從文件中讀取
//創建一個單例對象
Chopstick chopstick1 = Chopstick.getInstance();
//將對象寫入文件
File file = new File("Chopstick.dat");
FileOutputStream os = new FileOutputStream(file);
ObjectOutputStream oos = new ObjectOutputStream(os);
oos.writeObject(chopstick1);
oos.close();os.close();
//將對象從文件中讀取
FileInputStream is = new FileInputStream(file);
ObjectInputStream ois = new ObjectInputStream(is);
Chopstick chopstick2 = (Chopstick) ois.readObject();//第二個實例化對象
System.out.println(chopstick1);
System.out.println(chopstick2);
/**
* 輸出結果
* com.yn.study.chapter1.Chopstick@2503dbd3
* com.yn.study.chapter1.Chopstick@7ef20235
* 表示這兩個對象不是一個對象
*/
}
聲明readResolve方法
private Object readResolve(){return INSTANCE;}
這樣,上面的結果獲得的將是同一個對象。
com.yn.study.chapter1.Chopstick@2503dbd3
com.yn.study.chapter1.Chopstick@2503dbd3
使用
//Chopsticks n.筷子
//這裡假定筷子只能有一根
//這裡創建一個單例對象
class Chopstick implements Serializable {
private static final Chopstick INSTANCE = new Chopstick();//類載入後,自動創建一個Chopstick對象,
private Chopstick() {if (INSTANCE!=null)throw new Error("請不要二次創建對象");}//構造器私有化,禁止二次創建
public static Chopstick getInstance(){return INSTANCE;}//獲取實例
private Object readResolve(){return INSTANCE;}//寫readResolve方法,防止反序列化破壞單例
}
枚舉類
枚舉本就是一個單例對象,而且不可破壞。
enum ChopstickPlus{
INSTANCE;
ChopstickPlus getInstance(){return INSTANCE;}
}
4. 通過私有構造器,使得類不可實例化
有些類只包含靜態方法或靜態域,這樣的類不希望會被實例化,因為這些類被實例化是沒有意義的;
這裡我表示疑惑:應該一般情況下沒有人會去嘗試實例化一個只有靜態方法的類,嗯..但是...,書上說有一些時候會無意識的初始化該類??下麵繼續記筆記。
對於沒有特別聲明構造器的類,其構造器預設是public的,
-
這裡可以通過將構造器私有化,來避免不必要的實例化。
-
同樣,為避免通過反射創建對象,可以在構造方法里添加拋出錯誤,防止類實例化。
//EasyMath 一個簡單的,無意義的,僅用於學習的,計算類
class EasyMath{
public static long sum(long a,long b){return a+b;}//一個求和的靜態方法
//不希望不必要的工具類實例化
private EasyMath(){throw new AssertionError();}
}
但這樣有個缺點:這個類不能有父類。
5. 優先考慮依賴註入引用資源
這裡..就只寫個標題吧。。
詳見Effective Java 第三版 P16頁。