1. 程式人生 > >EffectiveJava第六章:列舉和註解

EffectiveJava第六章:列舉和註解

討論列舉和註解的最佳實踐。

30. 用enum代替int常量

列舉型別(enum type)是指由一組固定的常量組成合法值得型別,在程式語言還沒有引入列舉之前,表示列舉型別的常用模式是宣告一組具名的int常量,稱作int列舉模式

  • int列舉模式的不足

    1. 它在型別安全性和使用方便性方法沒有任何幫助
    2. 採用int列舉模式的程式是十分脆弱的。因為int列舉是編譯時常量,被編譯到使用它們的客戶端中,如果int值發生變化,則客戶端就必須重新編譯。如果不編譯,則它們的行為就是不確定的。
    3. 打印出int列舉常量的字串,並沒有很便利的方法。要便利一個組中所有的int列舉常量、獲取int列舉組大小等這些都沒有很可靠的方法。
  • String列舉模式
    int列舉模式的變體,同樣也有很大的不足。雖然它提供了可列印的字串,但是它會導致效能問題,因為它依賴於字串的比較操作;

  • 列舉enum,Java的列舉本質上是int
    它們就是通過公有的靜態final域為每個列舉常量匯出例項的類,因為沒有可訪問的構造器,列舉型別是真正的final;不能例項化,只能訪問已宣告過的列舉常量。
    即列舉型別是例項受控的,它們是單例的泛型化,本質上式單元素的列舉。

    • 包含同名常量的多個列舉型別可以在同一個系統中和平共處,因為每個型別都有自己的名稱空間(在不同的類下)。
    • 可以增加或重排列舉型別中的常量,而無需重新編譯它的客戶端程式碼,因為匯出的常量域在列舉型別和它的客戶端之間提供了一個隔離層:常量值並沒有被編譯到客戶端程式碼中,而是在int列舉模式之中。
    • 可以呼叫toString()方法將列舉轉換成可列印的字串。
    • 可以新增任意的方法和域,並可以實現任意的介面。
    • 可以遍歷
  • 與列舉常量關聯的有些行為,可能只需要用在定義了列舉的類或者包中。這種行為最好被實現成私有的或者包級私有的方法。

  • 如果一個列舉具有普遍適用性,就應該成為一個頂層類;如果它只是用在一個特定的頂層類中,它應該被作為該頂層類的一個成員類。
    例如:java.math.RoundingMode列舉類。

  • 有時候需要在不同的列舉型別上表現本質不同的行為,採用如下這種方法:在列舉型別中宣告一個抽象方法,並在特定常量的類主體(constant-specific class body)中實現該抽象方法。
    這種方法被稱作特定於常量的方法實現(constant-specific method implementation)

public enum Operation {
    PLUS {
        @Override double apply(double x, double y) {
            return x + y;
        }
    },
    MINUS {
        @Override double apply(double x, double y) {
            return x - y;
        }
    },
    TIMES {
        @Override double apply(double x, double y) {
            return x * y;
        }
    },
    DIVIDE {
        @Override double apply(double x, double y) {
            return x / y;
        }
    };

    abstract double apply(double x, double y);
}
  • 特定於常量的方法實現有一個美中不足的地方,它們使得在列舉常量中共享程式碼變得困難。
    可以增加抽象方法、呼叫不同的輔助方法,但這樣會產生大量的樣板程式碼,降低可讀性。
    可以使用策略列舉(strategy enum),即巢狀一個私有列舉,把大量計算放在其中,外部將呼叫該巢狀列舉常量來委託計算。雖然這種模式沒有switch語句那麼簡潔,但更加安全、靈活。

  • 列舉的效能缺點:
    裝載和初始化列舉時會有空間和時間的成本。

總之,與int常量相比,列舉型別有很大優勢,要易讀、更安全、功能更強大。善用列舉,另外如果多個列舉常量同時共享相同的行為時,考慮使用策略列舉。

31. 用例項域代替序數

可以使用列舉的ordinal方法獲取到該列舉常量在列舉中的排列位置,但依賴該方法的話維護就會很麻煩;比如新增一個列舉或重排,就會導致該位置發生變化。
所以永遠不要根據列舉的序數匯出與它關聯的值,而是使用列舉值的成員變數來儲存,通過構造方法傳入。
最好完全避免使用ordinal方法,它是設計成用在EnumSet、EnumMap這種基於列舉的通用資料結構的。

32. 用EnumSet代替位域

/*
    使用int常量進行位運算,位域
*/
public class Text {
    public static final int STYLE_BOLD = 1 << 0;
    public static final int STYLE_ITALIC = 1 << 2;
    public static final int STYLE_UNDERLINE = 1 << 3;

    public void applyStyles(int styles) {
    }

    //text.applyStyles(STYLE_BOLD | STYLE_ITALIC);
}

/*
  使用EnumSet代替位域
*/
public class TextEnumSetStyle {
    public enum Style {
        BOLD, ITALIC, UNDERLINE;
    }

    public void applyStyles(Set<Style> styles) { //使用介面接收,說不定傳其他的Set實現
    }

    //text.applyStyles(EnumSet.of(Style.BOLD,Style.ITALIC));
}
  • 位域有著int列舉常量的所有缺點,不能列印、不能遍歷等

  • EnumSet實現了Set介面,提供了豐富的功能和型別安全性,以及可以從其他任何Set實現中得到的互用性。
    在內部實現上,每個EnumSet內容都表示為向量;如果列舉型別<=64個,整個EnumSet就是用單個long來表示,所以效能比得上位域。

EnumSet類集位域的簡潔和效能優勢、及列舉型別的所有優點於一身,但列舉本身是消耗效能的,使用時考慮考慮。

33. 使用EnumMap代替序數索引

  • EnumMap的執行速度方面可以與使用序數的程式相媲美,它沒有不安全的轉換;不必通過索引輸出。

  • EnumMap在內部使用了陣列, 索引執行速度和通過序數索引的陣列一樣。
    EnumMap構造器採用的鍵型別是Class物件:有限制的型別令牌(bounded type token),它提供了執行時的泛型資訊。

EnumMap使用Enum作為鍵,值可為任意。當你需要使用Enum時,並且需要通過該列舉型別作為序數索引時,就考慮使用EnumMap。

34. 用介面模擬可伸縮的列舉

  • 讓列舉實現介面來擴充套件列舉。

  • 用介面來擴充套件列舉的缺點是:列舉無法繼承,所以程式碼不能複用;少量的可以複製貼上,但如果有大量的共享程式碼,則可以封裝在一個輔助類或靜態輔助方法中。

雖然無法編寫可擴充套件的列舉型別,卻可以通過編寫介面以及實現該介面的基礎列舉型別,對它進行模擬。這樣執行客戶端編寫自己的列舉來實現介面。

35. 註解優先於命名模式

舉例:JUnit測試框架原本要求它的使用者一定要用test作為測試方法開頭,後來改成使用註解標明。

  • 命名模式的缺點:
    1. 文字拼寫錯誤會導致失敗,且沒有任何提示
    2. 無法確保它們只用於相應的程式元素上,比如該用在方法上卻用在類上。
    3. 它們沒有提供將引數值和程式元素關聯起來的好方法。

註解很好的解決所有這些問題。

  • 註解中陣列引數的語法十分靈活,它是進行過優化的單元素陣列。
//@Target(ElementType.Method)  
 //@Retention(RetentionPolicy.RUNTIME)
  public @interface MyAnnotation {
     String[] value() default ""; 
  }

使用時@MyAnnotation({"1","2"})來宣告引數。

在編寫框架時,比如JUnit、ButterKnife之類的註解框架,可以有效的使用註解來解耦、提高效能,應該避免使用命名模式。

36. 堅持使用Override註解

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

@Override註解只能用在方法宣告上,表示被註解的方法覆蓋了父類中的一個方法。

  • 應該在你想要覆蓋超類方法的每個方法宣告中使用Override註解。可以避免把覆蓋誤用成過載。這是利用了註解編譯期進行檢查的功能。IDE也會提示你進行了錯誤的覆蓋。

  • 在抽象類或者介面中,應當標註所有你想要的方法,來覆蓋超類或者介面的方法,無論是具體的還是抽象的。

37. 用標記介面定義型別

標記介面(marker interface)是沒有包含方法宣告的介面,而只是指明一個類實現了具有某種屬性的介面。

  • 標記介面勝於標記註解

    1. 標記介面定義的型別是由被標記類的例項實現的,標記註解則沒有定義這樣的型別;這個型別可以捕捉到編譯期間的錯誤,而標註註解可能要在執行時才能發現。
    2. 標記介面可以被更精確的鎖定。
      例如Set介面是Collection介面的子型別,雖然它不是標記介面,但可以說成是有限制的標記介面。這種標記介面可以描述整個物件的某個約束條件(例如Set),或者表明例項能夠利用其它每個類的方法進行處理(例如ObjectOutputStream需要Serializable介面進行標記)。
  • 標記註解勝於標記介面

    1. 標記註解可以通過預設的方式新增一個或多個註解型別元素,給已被使用的註解型別新增更多的資訊。即簡單的標記註解型別可以演變成更加豐富的註解型別。這對於標記介面則是不可能的,因為標記介面不能在實現它了之後再隨意新增方法
    2. 標記註解的另一個優點在於它是更大的註解機制的一部分。因此標記註解在那些大量使用註解的框架中具有一致性。
  • 什麼時候用標記介面、標記註解呢,在寫程式碼時可以多考慮考慮
    如果標記是應用到任何元素時,使用標記註解;
    如果只用於類和介面,就要考慮這個被標記的類的例項所用的地方多不多?

    • 如果多,使用標記介面就可以傳入該介面型別,做到編譯期進行檢查。
    • 如果不多,就再考慮是否永遠限制這個標記只用於特殊類、介面麼?如果是永遠限制,就做成一個介面的子介面進行限制;如果不是永遠限制,就可以使用標記註解。

標記介面和標記註解都各有用處:
如果想要定義一個任何新方法都不會與之關聯的型別,標記介面就是最好的選擇;
如果該標記在未來可能要給標記新增更多的資訊,或者標記要適合於已經廣泛使用了註解型別的框架,那麼就選擇標記註解。

相關推薦

EffectiveJava列舉註解

討論列舉和註解的最佳實踐。 30. 用enum代替int常量 列舉型別(enum type)是指由一組固定的常量組成合法值得型別,在程式語言還沒有引入列舉之前,表示列舉型別的常用模式是宣告一組具名的int常量,稱作int列舉模式。 int列舉模式的不

玩轉資料結構——集合對映

集合(Set) 什麼是集合? 集合是承載元素的容器; 特點:每個元素只能存在一次 優點:去重 二分搜尋樹的新增操作add:不能盛放重複元素 是非常好的實現“集合”的底層資料結構 /** * 集合的介面 */ public interface Set<

Taglib原理實現 標籤內常用方法總結

1。支援el表示式: import org.apache.taglibs.standard.lang.support.ExpressionEvaluatorManager; private Object value = null; this.valu

阿里雲linux伺服器------域名購買、備案解析

當我們購買了伺服器後我們可以通過伺服器的公網ip直接訪問它,但在網際網路上你訪問別人的網站一般都是通過這個網站的網址(域名)來訪問它,而不是通過這個網站所在伺服器的ip地址。這樣做的好處:一是更加安全,不會暴露你伺服器的地址、二會起到一定的宣傳作用,因為我們花錢買個域名總會希望它有點特殊的含義。

SpringBoot | 常用註解介紹及簡單使用

前言 之前幾個章節,大部分都是算介紹springboot的一些外圍配置,比如日誌配置等。這章節開始,開始總結一些關於springboot的綜合開發的知識點。由於SpringBoot本身是基於Spring和SpringMvc等各類spring家族的一個解決方案,可快速進行

翻譯libevent參考手冊bufferevent概念入門 (八) (轉)

bufferevent_setcb()函式修改bufferevent的一個或者多個回撥。readcb、writecb和eventcb函式將分別在已經讀取足夠的資料、已經寫入足夠的資料,或者發生錯誤時被呼叫。每個回撥函式的第一個引數都是發生了事件的bufferevent,最後一個引數都是呼叫buffereven

資料庫系統概念(機械工業出版社,七版)複習——資料庫設計E-R模型

E-R模型 實體-聯絡模型:Entity-Relationship Model E-R圖要點 實體(Entity) 客觀存在並可相互區分的事物叫實體(唯一標識)。 實體集(Entity Set) 是具有相同型別及共享相同性質(屬性)的實體集合。如全體學生。組成實體集的各實

2018-03-24 挖掘頻繁模式、關聯相關性基本概念

6.3 模式評估方法  大部分關聯規則挖掘演算法都使用支援度-置信度框架。儘管最小支援度和置信度閥值有助於排除大量無趣規則的探查,但仍然會產生一些使用者不感興趣的規則。強規則不一定是有趣的,甚至會誤導。    如:假設有10000個事務中,資料顯示6000個顧客事務包含計算機遊戲,7500個事務包含錄影,而4

julia互操作性 超程式設計(learning julia譯)()

在本章中,我們將重點介紹Julia如何與外部世界互動,使用不同的方式對作業系統(OS)進行系統呼叫,或使用其他語言(如C和Python)的程式碼。 稍後,我們將以超程式設計的形式探索Julia的另一個重要方面。 我們還將研究Julia中預設提供的各種型別的巨集,

Flask 教程 個人主頁頭像

這是Flask Mega-Tutorial系列的第六部分,我將告訴你如何建立個人主頁。 本章將致力於為應用添加個人主頁。個人主頁用來展示使用者的相關資訊,其個人資訊由本人錄入。 我將為你展示如何動態地生成每個使用者的主頁,並提供一個編輯頁面給他們來更新個人

讀構建之法 團隊流程

min 這樣的 程序員 希望 成員 eat 貢獻 核心 不能 團隊有一致的集體目標,團隊要一起完成這目標。一個團隊的成員不一定要同時工作,例如接力賽跑。 團隊成員有各自的分工,互相依賴合作,共同完成任務。 軟件團隊有各種形式,適用於不同的人員和需求。基於直覺形成的團隊模式未

異常機制

() 不同 finall try arr 運行時 運行 ror 則無 第六章:異常機制 異常的定義 異常:在程序運行過程中出現的意外事件,導致程序中斷執行。 異常處理 try...catch 語法:try{ //可能出現異常的代碼}catch(異常類型 異常對象名){

循環結構(二)

結構 不執行 三種 表達式 成了 不改變 條件 運算符 步驟 第六章:循環結構(二) 一. for 循環 1.循環結構的四個組成部分 (1). 初始部分:設置循環的初始狀態,比如我們設置記錄循環次數的變量 i 為 0 . (2). 循環體:重復執行的代碼 .

需求評審如何進行

角色 來源 職責 介紹 技術 產品介紹 好的 通過 協調 前言今天我們講的需求評審包括兩個部分,需求過濾和需求評審。 需求過濾 1.需求分析不是所有需求都要做進產品,我們要根據公司和產品的定位,進行合適地分析和過濾。 我們需要分析出用戶需求所對應的本質,將其轉化為產品能夠提

Node入門教程(8)path 模塊詳解

format QQ 調用 保留 微軟 posix interface join 結果 path 模塊詳解 path 模塊提供了一些工具函數,用於處理文件與目錄的路徑。由於windows和其他系統之間路徑不統一,path模塊還專門做了相關處理,屏蔽了彼此之間的差異。 可移

初始繼承多態

機制 但是 就是 概念 base 類的設計 類重寫 結構 冗余 1.繼承的概念 其實生活中有很多繼承的例子。例如,在馬路上跑的卡車,我們每天都乘坐的公共汽車,它們都是汽車。卡車有自己的特征:有貨艙,有額定載重,行為都是可以拉貨、卸貨。而公共汽車的特征和行為:有客艙,有載客量

R語言編程藝術__因子

女性 案例 子列 認識 改變 posit 程序實現 style 各類 一、因子與水平 1、簡單直接的認識因子和水平   因子可以簡單的理解為包含了更多信息的向量。即因子=向量 + 水平。(當然實際上它們內部機理不同)。水平是對於向量中不同值的記錄,以下面代碼為例: >

Docker | 構建私有倉庫

推送 sun 指定 公司 網絡環境 add 屬性 提示 回收機制 前言 上一章節,講解了利用Dockerfile和commit進行自定義鏡像的構建。大部分時候,公司運維或者實施部門在構建了符合公司業務的鏡像環境後,一般上不會上傳到公共資源庫的。這就需要自己搭建一個私有倉庫

函數調試

如果 怎麽辦 調試工具 將在 工具 argument real special one   在本章中,你將學習以下兩方面的基本知識: 函數 使用Python調試包pdb   函數是程序重要的組成部分,你將在第七章中使用它們,並學習如何使用隨機化

編寫安全應用

利用 flash 網站 這一 ade 第六章 用戶數據 ack else 很多時候,安全應用是以犧牲復雜度(以及開發者的頭痛)為代價的。Tornado Web服務器從設計之初就在安全方面有了很多考慮,使其能夠更容易地防範那些常見的漏洞。安全cookies防止用戶的本地狀態被