Java Web 系統(tǒng)總結(jié)之一 常量枚舉

常量

常量其實(shí)沒什么可說的, 只要注意兩點(diǎn). 一是需要甄別是否是真正的常量, 并不是所有的 static final 都是常量. 二是不要把所有常量都集中寫在一個(gè)類里, 而是根據(jù)實(shí)際關(guān)系, 有組織的放到各個(gè)包甚至各個(gè)類中. 如果寫的規(guī)矩一點(diǎn), 常量類應(yīng)像工具類一樣, 類添加 final 關(guān)鍵字, 同時(shí)私有化構(gòu)造方法, 防止被繼承和實(shí)例化. 示例如下:

public final class Constant {
    
    private Constant() {}
    
    public static final String SUCCESS = "success";

    public static final String FAILED = "failed";
    
}

枚舉

當(dāng)前項(xiàng)目中經(jīng)常看到的這樣形式的枚舉類: 帶有兩個(gè)字段, 一個(gè)字段為整型, 表示枚舉值, 一個(gè)字段為字符串, 表示枚舉值的描述. 常見寫法如下:

public enum AttachmentEnum {

    COMPLETE(1, "完整"),
    NON_COMPLETE(0, "不完整");

    private final Integer value;
    private final String name;

    AttachmentEnum(Integer value, String name) {
        this.value = value;
        this.name = name;
    }

    public int getValue() {
        return value;
    }

    public String getName() {
        return name;
    }

    public static AttachmentEnum of(int value) {
        for (AttachmentEnum o : AttachmentEnum.values()) {
            if (value == o.getValue()) {
                return o;
            }
        }
        return null;
    }

    public static String getName(Integer value) {
        if (value == null) {
            return null;
        }
        for (AttachmentEnum o : AttachmentEnum.values()) {
            if (value == o.getValue()) {
                return o.getName();
            }
        }
        return null;
    }

    public static Integer getValueByName(String name) {
        AttachmentEnum[] attachmentEnums = AttachmentEnum.values();
        for (AttachmentEnum value : attachmentEnums) {
            if (value.getName().equals(name)) {
                return value.getValue();
            }
        }
        return null;
    }
}

其中每個(gè)枚舉類都有根據(jù)值獲取描述和根據(jù)描述獲取值的方法, 每次寫一個(gè)枚舉類都要復(fù)制粘貼修改一份類似代碼. 此類重復(fù)代碼臃腫而無必要, 其實(shí)可利用反射寫一個(gè)工具類, 用于通用處理所有這樣的枚舉類.

import org.apache.commons.lang.StringUtils;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;

/**
 * 枚舉工具類.
 * <p> 用于帶有一個(gè) Integer 和 String 的枚舉類
 * <p> 可根據(jù)值獲取名稱, 或根據(jù)名稱獲取值
 */
public final class ValueNameEnumUtils {

    private ValueNameEnumUtils() {
    }

    /**
     * 判斷給定值是否在枚舉類選項(xiàng)的值.
     * <p> 如無整型字段, 拋出異常
     * <p> 如有多個(gè)整型字段, 使用第一個(gè)
     */
    public static <E extends Enum<E>> boolean valueOfEnum(final Class<E> enumClass, int value) {
        String fieldName = getFirstIntegerField(enumClass).getName();
        String methodName = getGetterName(fieldName);

        try {
            Method getter = enumClass.getMethod(methodName);
            for (E e : Arrays.asList(enumClass.getEnumConstants())) {
                if (value == (Integer) getter.invoke(e)) {
                    return true;
                }
            }
        } catch (NoSuchMethodException e) {
            throw new RuntimeException("枚舉類字段無取值方法: " + enumClass.getSimpleName() + "." + fieldName);
        } catch (IllegalAccessException e) {
            throw new RuntimeException("枚舉類字段取值方法無法訪問: " + enumClass.getSimpleName());
        } catch (InvocationTargetException e) {
            throw new RuntimeException("枚舉類字段取值方法調(diào)用失敗: " + enumClass.getSimpleName());
        }

        return false;
    }

    /**
     * 返回給定整型值的對(duì)應(yīng)的枚舉類.
     * <p> 如無整型字段, 拋出異常
     * <p> 如值對(duì)應(yīng)多個(gè)枚舉, 使用第一個(gè)
     */
    public static <E extends Enum<E>> E getByValue(final Class<E> enumClass, int value) {
        String fieldName = getFirstIntegerField(enumClass).getName();
        String methodName = getGetterName(fieldName);

        try {
            Method getter = enumClass.getMethod(methodName);
            for (E e : Arrays.asList(enumClass.getEnumConstants())) {
                if (value == (Integer) getter.invoke(e)) {
                    return e;
                }
            }
        } catch (NoSuchMethodException e) {
            throw new RuntimeException("枚舉類字段無取值方法: " + enumClass.getSimpleName() + "." + fieldName);
        } catch (IllegalAccessException e) {
            throw new RuntimeException("枚舉類字段取值方法無法訪問: " + enumClass.getSimpleName());
        } catch (InvocationTargetException e) {
            throw new RuntimeException("枚舉類字段取值方法調(diào)用失敗: " + enumClass.getSimpleName());
        }

        return null;
    }

    /**
     * 返回給字符串的對(duì)應(yīng)的枚舉類.
     * <p> 如無字符串字段, 拋出異常
     * <p> 如字符串對(duì)應(yīng)多個(gè)枚舉, 使用第一個(gè)
     */
    public static <E extends Enum<E>> E getByName(final Class<E> enumClass, String name) {
        String fieldName = getFirstStringField(enumClass).getName();
        String methodName = getGetterName(fieldName);

        try {
            Method getter = enumClass.getMethod(methodName);
            for (E e : Arrays.asList(enumClass.getEnumConstants())) {
                if (name != null && name.equals((String) getter.invoke(e))) {
                    return e;
                }
            }
        } catch (NoSuchMethodException e) {
            throw new RuntimeException("枚舉類字段無取值方法: " + enumClass.getSimpleName() + "." + fieldName);
        } catch (IllegalAccessException e) {
            throw new RuntimeException("枚舉類字段取值方法無法訪問: " + enumClass.getSimpleName());
        } catch (InvocationTargetException e) {
            throw new RuntimeException("枚舉類字段取值方法調(diào)用失敗: " + enumClass.getSimpleName());
        }

        return null;
    }

    private static <E extends Enum<E>> Field getFirstIntegerField(final Class<E> enumClass) {
        for (Field field : enumClass.getDeclaredFields()) {
            if (field.getType().getName().equals("int") || field.getType().getName().equals("java.lang.Integer")) {
                return field;
            }
        }
        throw new IllegalStateException("枚舉類無數(shù)值字段: " + enumClass.getSimpleName());
    }

    private static <E extends Enum<E>> Field getFirstStringField(final Class<E> enumClass) {
        for (Field field : enumClass.getDeclaredFields()) {
            if (field.getType().getName().equals("java.lang.String")) {
                return field;
            }
        }
        throw new IllegalStateException("枚舉類無字符串字段: " + enumClass.getSimpleName());
    }

    private static String getGetterName(String fieldName) {
        return StringUtils.isEmpty(fieldName) ? "" : "get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
    }
}

上述代碼沒有什么好講, 跟反射獲取對(duì)象中字段, 并調(diào)用對(duì)應(yīng) getter 方法基本一樣. 唯一需要注意的點(diǎn)是枚舉泛型的寫法<E extends Enum<E>>.

同時(shí), 利用 lombok 提供的注解, 這類枚舉代碼可簡化如下:

import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public enum Attachment {
    COMPLETE(1, "完整"),
    NON_COMPLETE(0, "不完整");

    private final Integer value;
    private final String name;
}

這樣寫枚舉, 看上去是不是簡潔清爽了很多? 再寫新的枚舉, 只需要關(guān)注枚舉項(xiàng)即可.

除此之外, 寫枚舉還有一些小經(jīng)驗(yàn)或注意事項(xiàng). 首先是命名, 可以不加后面的 "Enum", 通常凡是叫 XXXStatus 或者 XXXType 的, 基本上一望可知, 不是常量就是枚舉, 而且 Java 是強(qiáng)類型語言, 用名稱表明類型多少有些啰嗦. 其次, 如果枚舉中的數(shù)值是自己設(shè)置, 最好不要設(shè)成 1, 2, 3 而是最好設(shè)置成 10, 20, 30 或者 100, 200, 300; 這樣方便后續(xù)擴(kuò)展, 可以在中間添加枚舉項(xiàng). 而且同一類枚舉, 值設(shè)置在各系統(tǒng)中最好統(tǒng)一, 比如審核狀態(tài), 均設(shè)置 100 為審核通過, -100 為審核不通過, 這樣可以加快不同項(xiàng)目代碼的上手速度.

枚舉常見的使用錯(cuò)誤主要在比較判斷上. 一種是取 value 值 Integer, 不使用 equals 而用 == 判斷. 另一種是使用了 equals, 卻不小心拿枚舉類型和整型比較. 前者屬于基本類型與包裝類型不分. 由于整型池原理, 枚舉 value 值設(shè)置在 127 以內(nèi), 估計(jì)一時(shí)都察覺不到問題. 一般都要吃過一次大虧, 上過一次大當(dāng)之后, 才能增長此類經(jīng)驗(yàn)教訓(xùn). 上文中摘抄的代碼其實(shí)就有這個(gè)問題. 后一種錯(cuò)誤多是無心之失, 代碼寫快了總是難易避免. 不過, 如果養(yǎng)成良好習(xí)慣, 提交代碼之前, 使用 findbug 插件掃描一下, 這兩種錯(cuò)誤就能輕松發(fā)現(xiàn)了.

最后編輯于
?著作權(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),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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