Effective Java 閱讀日記 1

来源:https://www.cnblogs.com/uwupu/archive/2022/10/07/16760463.html
-Advertisement-
Play Games

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);
}

優勢

  1. 靜態方法有名字,可以指定一個功能作為方法名;

  2. 實現對象重用,優化程式運行;

    • 在對象使用結束後,可以將對象緩存起來,若下次調用可以再次使用;

    • 相對對象重用創建一個新的對象損耗可能會更大;

    • 在情況允許時,儘量多地使用對象重用,減少創建對象造成額外損耗;

    • 如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;
          }
      }
      
  3. 依據不同的參數,可以返回任何子類的對象,也可以返回不同的對象;

    • 應用:
      • 靜態方法可以返回對象,而無需將對象的類設為公有的;
      • 靜態方法可以通過介面返回不同的對象EnumSet沒有構造器,只能通過靜態工廠創建對象,在OpenJDK實現中,EnmuSet的實現有兩種類型:RegalarEumSetJumboEnumSet;當枚舉元素數量等於小於64時,靜態工廠方法返回RegalarEumSet對象;當枚舉元素數量大於64時,靜態工廠方法返回JumboEnumSet對象。(對於調用者,無需知道背後的實現原理,直接使用就好;對於EnumSet開發者,此做法用於代碼優化。)
  4. 方法返回的對象所屬的類,在編寫靜態工廠方法的類時可以不存在;

    • 如mysql和JDBC;

缺點

  1. 類如果不包含public的構造器,則不能被繼承;

  2. 靜態工廠方法需要程式員主動去尋找,而非構造方法可以直接使用;

    • 示例:
    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頁。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 多表查詢 前面講過的基本查詢都是對一張表進行查詢,但在實際的開發中遠遠不夠。 下麵使用表emp,dept,salgrade進行多表查詢 emp: dept: salgrade: 1.前置-mysql表查詢-加強 1.1查詢增強 使用where子句 如何查找1992.1.1後入職的員工 在mysql中 ...
  • 前文回顧 實現一個簡單的Database1(譯文) 實現一個簡單的Database2(譯文) 實現一個簡單的Database3(譯文) 譯註:cstsck在github維護了一個簡單的、類似SQLite的資料庫實現,通過這個簡單的項目,可以很好的理解資料庫是如何運行的。本文是第四篇,主要是使用rsp ...
  • ruoyi項目在低於3.7.0的版本中,前端字典功能實現比較簡單,每個index.vue頁面都請求dict的api,獲取數據再加工顯示即可。3.7.0之後的版本使用了混入,所以複雜了一些。 ...
  • uni-app介紹 官方網頁 uni-app 是一個使用 Vue.js 開發所有前端應用的框架,開發者編寫一套代碼,可發佈到iOS、Android、H5、以及各種小程式(微信/支付寶/百度/頭條/QQ/釘釘)等多個平臺。 即使不跨端,uni-app同時也是更好的小程式開發框架。 具有vue和微信小程 ...
  • SSM · IOC ·AOP 初識Spring : Spring全家桶 Spring Framework 底層框架,設計性框架 Spring Boot 加速開發,提高開發速度 Spring Cloud 分散式開發 Spring Framework系統架構 Spring Framework是Sprin ...
  • 我的博客 在寫python腳本的時候,例如,我寫一個test1.py def test(n): print(n) if __name__ == '__main__': n = 1 test(1) 接下來打開cmd命令視窗,執行命令 python test.py 結果是把1列印出來 這時候就會有同學有 ...
  • 一、CentOS 7.9 安裝 Python-3.9.5 地址 https://www.python.org https://www.python.org/ftp/python/3.9.5 查看當前python版本 python python -V 可以看到執行python,預設是2.7.5 二、安 ...
  • 1、原因 不知不覺已經從事java開發好幾年了,成了人們口中的老司機。但是一直都是恍恍惚惚過來,對於框架底層實現一直都沒有怎麼瞭解過,只是在面試的時候背些面試題。慢慢地發現不能這樣,需要振作,笑~~~~。從這篇開始,記錄自己對於Spring載入過程的源碼查看。 ## 2、開始 1、在Spring載入 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...