枚舉和注解——讀《編寫高質(zhì)量代碼:改善Java程序的151個(gè)建議》(六)

讀書,收獲,分享
建議后面的五角星僅代表筆者個(gè)人需要注意的程度。
Talk is cheap.Show me the code

建議83:推薦使用枚舉定義常量★★☆☆☆

  1. 枚舉常量更簡(jiǎn)單

    枚舉常量只需要定義每個(gè)枚舉項(xiàng),不需要定義枚舉值

  2. 枚舉常量屬于穩(wěn)態(tài)型

    這也是我們最看重枚舉的地方:在編譯期間限定類型,不允許發(fā)生越界的情況。

  3. 枚舉具有內(nèi)置方法

    通過(guò)values方法即可獲得所有的枚舉項(xiàng),得益于枚舉內(nèi)置的方法,每個(gè)枚舉都是java.lang.Enum的子類,該基類提供了諸如獲得排序值的ordinal方法、compareTo比較方法等,大大簡(jiǎn)化了常量的訪問(wèn)。

  4. 枚舉可以自定義方法

    枚舉常量不僅可以定義靜態(tài)方法,還可以定義非靜態(tài)方法,而且還能夠從根本上杜絕常量類被實(shí)例化。

枚舉類型是不能有繼承的,也就是說(shuō)一個(gè)枚舉常量定義完畢后,除非修改重構(gòu),否則無(wú)法做擴(kuò)展

注意:在項(xiàng)目開發(fā)中,推薦使用枚舉常量代替接口常量或類常量。

建議84:使用構(gòu)造函數(shù)協(xié)助描述枚舉項(xiàng)★☆☆☆☆

推薦在枚舉定義中為每個(gè)枚舉項(xiàng)定義描述,特別是在大規(guī)模的項(xiàng)目開發(fā)中,大量的常量項(xiàng)定義使用枚舉項(xiàng)描述比在接口常量或類常量中增加注釋的方式友好得多,簡(jiǎn)潔得多。如下:

public enum OrderStateEnum {

    ORDER_PAY_SUCC(1,"訂單支付成功");
    
    private Integer code;
    private String  name;

    OrderStateEnum(Integer code, String name) {
        this.code = code;
        this.name = name;
    }

}

建議85:小心switch帶來(lái)的空值異常★★☆☆☆

示例如下:

    public static void main(String[] args) {
        orderUpt(null);
    }
    public static void  orderUpt(OrderStateEnum orderStateEnum){
        switch (orderStateEnum){
            case ORDER_PAY_SUCC:
                  System.out.println("訂單支付成功,執(zhí)行后續(xù)操作");
                break;
            default:
                System.out.println("未匹配到訂單狀態(tài),無(wú)操作");
        }
    }

運(yùn)行結(jié)果:

Exception in thread "main" java.lang.NullPointerException
    at com.jyswm.demo.Client.orderUpt(Client.java:13)
    at com.jyswm.demo.Client.main(Client.java:10)

因?yàn)榫幾g時(shí),編譯器判斷出switch語(yǔ)句后的參數(shù)是枚舉類型,然后就會(huì)根據(jù)枚舉的排序值繼續(xù)匹配,也就是說(shuō)上面的代碼與以下代碼相同:

    public static void  orderUpt(OrderStateEnum orderStateEnum){
        switch (orderStateEnum.ordinal()){
            case OrderStateEnum.ORDER_PAY_SUCC.ordinal():
                break;
            default:
                System.out.println("未匹配到訂單狀態(tài),無(wú)操作");
        }
    }
//switch語(yǔ)句是先計(jì)算orderStateEnum變量的排序值,然后與枚舉常量的每個(gè)排序值進(jìn)行對(duì)比的。
//在我們的例子中orderStateEnum變量是null值,無(wú)法執(zhí)行ordinal方法,于是報(bào)空指針異常了

建議86:在switch的default代碼塊中增加AssertionError錯(cuò)誤★☆☆☆☆

default后直接拋出一個(gè)AssertionError錯(cuò)誤,其含義就是“不要跑到這里來(lái),一跑到這里就會(huì)出問(wèn)題",這樣可以保證在增加一個(gè)枚舉項(xiàng)的情況下,若其他代碼未修改,運(yùn)行期馬上就會(huì)報(bào)錯(cuò),這樣一來(lái)就很容易查找到錯(cuò)誤,方便立刻排除。

建議87:使用valueOf前必須進(jìn)行校驗(yàn)★☆☆☆☆

通過(guò)枚舉的valueOf方法把String類型的名稱轉(zhuǎn)變?yōu)槊杜e項(xiàng),示例:

    public static void main(String[] args) {
        //注意此處的"ORDER_PAY_SU"在枚舉中是不存在的
        OrderStateEnum orderStateEnum = OrderStateEnum.valueOf("ORDER_PAY_SU");
    }

運(yùn)行結(jié)果:

Exception in thread "main" java.lang.IllegalArgumentException: No enum constant com.jyswm.demo.OrderStateEnum.ORDER_PAY_SU
    at java.lang.Enum.valueOf(Enum.java:238)
    at com.jyswm.demo.OrderStateEnum.valueOf(OrderStateEnum.java:3)
    at com.jyswm.demo.Client.main(Client.java:11)
    
//這與我們的習(xí)慣用法非常不一致,例如我們從一個(gè)List中查找一個(gè)元素,即使不存在也不會(huì)報(bào)錯(cuò),頂多indexOf方法返回-1

valueOf方法的源代碼:

 //由于valueOf(String name)方法是不可見的,是JVM內(nèi)置的方法  
//下面是通過(guò)閱讀公開的valueOf方法來(lái)模擬其運(yùn)行原理
public static <T extends Enum<T>> T valueOf(Class<T> enumType, String name) {
        //通過(guò)反射,從常量列表中查找
        T result = enumType.enumConstantDirectory().get(name);
        if (result != null)
            return result;
        if (name == null)
            throw new NullPointerException("Name is null");
        //最后拋出無(wú)效參數(shù)異常
        throw new IllegalArgumentException("No enum const " + enumType + "." + name);
   }

注意:使用valueOf前必須進(jìn)行校驗(yàn)

建議88:用枚舉實(shí)現(xiàn)工廠方法模式更簡(jiǎn)潔 ★☆☆☆☆

為什么要使用枚舉類型的工廠方法模式呢?有以下三個(gè)優(yōu)點(diǎn):

  1. 避免錯(cuò)誤調(diào)用的發(fā)生
  2. 性能好,使用便捷
  3. 降低類間耦合

注意:下一次,使用枚舉來(lái)實(shí)現(xiàn)工廠方法模式。

建議89:枚舉項(xiàng)的數(shù)量限制在64個(gè)以內(nèi) ★☆☆☆☆

示例代碼:

    public static void main(String[] args) {
        //創(chuàng)建包含所有枚舉項(xiàng)的EnumSet(為什么強(qiáng)調(diào)64呢?)
        EnumSet enumSet = EnumSet.allOf(OrderStateEnum.class);
    }

allOf源代碼:

public static <E extends Enum<E>> EnumSet<E> allOf(Class<E> elementType) {
        EnumSet<E> result = noneOf(elementType);
        result.addAll();
        return result;
}
public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType) {
        Enum<?>[] universe = getUniverse(elementType);
        if (universe == null)
            throw new ClassCastException(elementType + " not an enum");

        if (universe.length <= 64)
            return new RegularEnumSet<>(elementType, universe);
        else
            return new JumboEnumSet<>(elementType, universe);
}

java是這樣處理的,當(dāng)枚舉項(xiàng)數(shù)量小于等于64時(shí),創(chuàng)建一個(gè)RegularEnumSet實(shí)例對(duì)象,大于64時(shí)則創(chuàng)建一個(gè)JumboEnumSet實(shí)例對(duì)象。

為什么要如此處理呢?看看這兩個(gè)類之間的差異,源代碼如下:

RegularEnumSet類:

class RegularEnumSet<E extends Enum<E>> extends EnumSet<E> {
    //記錄所有枚舉排序號(hào),注意是long型
    private long elements = 0L;
    //構(gòu)造函數(shù)
    RegularEnumSet(Class<E>elementType, Enum<?>[] universe) {
        super(elementType, universe);
    }
    //加入所有元素
    void addAll() {
        if (universe.length != 0)
            elements = -1L >>> -universe.length;
    }
    /***省略其他代碼***/
}

我們知道枚舉項(xiàng)的排序值ordinal是從0、1、2……依次遞增的,沒(méi)有重號(hào),沒(méi)有跳號(hào),RegularEnumSet就是利用這一點(diǎn)把每個(gè)枚舉項(xiàng)的ordinal映射到一個(gè)long類型的每個(gè)位,簡(jiǎn)單地說(shuō),Java把一個(gè)不多于64個(gè)枚舉項(xiàng)的枚舉映射到了一個(gè)long類型變量上。效率和性能肯定是非常優(yōu)秀。

JumboEnumSet類:

class JumboEnumSet<E extends Enum<E>> extends EnumSet<E> {
    //映射所有的枚舉項(xiàng)
    private long elements[];
    //構(gòu)造函數(shù)
    JumboEnumSet(Class<E>elementType, Enum<?>[] universe) {
        super(elementType, universe);
        //默認(rèn)長(zhǎng)度是枚舉項(xiàng)數(shù)量除以64再加1
        elements = new long[(universe.length + 63) >>> 6];
    }

    void addAll() elements{
        //elements中每個(gè)元素表示64個(gè)枚舉項(xiàng)
        for (int i = 0; i < elements.length; i++)
            elements[i] = -1;
        elements[elements.length - 1] >>>= -universe.length;
        size = universe.length;
    }

JumboEnumSet類把枚舉項(xiàng)按照64個(gè)元素一組拆分成了多組,每組都映射到一個(gè)long類型的數(shù)字上,然后該數(shù)組再放置到elements數(shù)組中。簡(jiǎn)單來(lái)說(shuō)JumboEnumSet類的原理與RegularEnumSet相似,只是JumboEnumSet使用了long數(shù)組容納更多的枚舉項(xiàng)。

注意:枚舉項(xiàng)數(shù)量不要超過(guò)64,否則建議拆分。

建議90:小心注解繼承★☆☆☆☆

@Inherited,它表示一個(gè)注解是否可以自動(dòng)被繼承

采用@Inherited元注解有利有弊,利的地方是一個(gè)注解只要標(biāo)注到父類,所有的子類都會(huì)自動(dòng)具有與父類相同的注解,整齊、統(tǒng)一而且便于管理,弊的地方是單單閱讀子類代碼,我們無(wú)從知道為何邏輯會(huì)被改變,因?yàn)樽宇悰](méi)有明顯標(biāo)注該注解??傮w上來(lái)說(shuō),使用@Inherited元注解的弊大于利,特別是一個(gè)類的繼承層次較深時(shí),如果注解較多,則很難判斷出是哪個(gè)注解對(duì)子類產(chǎn)生了邏輯劫持。

建議91:枚舉和注解結(jié)合使用威力更大★☆☆☆☆

訪問(wèn)控制列表設(shè)計(jì)案例,示例如下:

//鑒權(quán)者接口
interface Identifier {
    //無(wú)權(quán)訪問(wèn)時(shí)的禮貌語(yǔ)
    String REFUSE_WORD = "您無(wú)權(quán)訪問(wèn)";
    // 鑒權(quán)
    public boolean identify();
}

//常用鑒權(quán)者
enum CommonIdentifier implements Identifier {
    //權(quán)限級(jí)別
    Reader, Author, Admin;
    //實(shí)現(xiàn)鑒權(quán)
    public boolean identify() {
        return false;
    }
}

//權(quán)限級(jí)別的注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface Access {
    //確定什么級(jí)別可以訪問(wèn)
    CommonIdentifier level() default CommonIdentifier.Admin;
}

//商業(yè)邏輯,默認(rèn)訪問(wèn)權(quán)限是Admin
@Access(level = CommonIdentifier.Author)
class Foo {
}

//模擬實(shí)現(xiàn)
public class Client {
    public static void main(String[] args) {
        //初始化商業(yè)邏輯
        Foo b = new Foo();
        //獲取注解
        Access access = b.getClass().getAnnotation(Access.class);
        //沒(méi)有Access注解或者鑒權(quán)失敗
        if (access == null || !access.level().identify()) {
            //沒(méi)有Access注解或者鑒權(quán)失敗
            System.out.println(access.level().REFUSE_WORD);
        }

    }

}

建議92:注意@Override不同版本的區(qū)別★☆☆☆☆

在多環(huán)境部署應(yīng)用時(shí),需要考慮@Override在不同版本下代表的意義,例如Java 1.6版本的程序移植到1.5版本環(huán)境中,就需要?jiǎng)h除實(shí)現(xiàn)接口方法上的@Override注解。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容