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 = https://www.cnblogs.com/uwupu/archive/2022/10/07/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頁,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/510982.html
標籤:其他
