Java Web 系统总结之一 常量枚举

常量

常量其实没什么可说的, 只要注意两点. 一是需要甄别是否是真正的常量, 并不是所有的 static final 都是常量. 二是不要把所有常量都集中写在一个类里, 而是根据实际关系, 有组织的放到各个包甚至各个类中. 如果写的规矩一点, 常量类应像工具类一样, 类添加 final 关键字, 同时私有化构造方法, 防止被继承和实例化. 示例如下:

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

    public static final String FAILED = "failed";
    
}

枚举

当前项目中经常看到的这样形式的枚举类: 带有两个字段, 一个字段为整型, 表示枚举值, 一个字段为字符串, 表示枚举值的描述. 常见写法如下:

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

其中每个枚举类都有根据值获取描述和根据描述获取值的方法, 每次写一个枚举类都要复制粘贴修改一份类似代码. 此类重复代码臃肿而无必要, 其实可利用反射写一个工具类, 用于通用处理所有这样的枚举类.

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> 用于带有一个 Integer 和 String 的枚举类
 * <p> 可根据值获取名称, 或根据名称获取值
 */
public final class ValueNameEnumUtils {

    private ValueNameEnumUtils() {
    }

    /**
     * 判断给定值是否在枚举类选项的值.
     * <p> 如无整型字段, 抛出异常
     * <p> 如有多个整型字段, 使用第一个
     */
    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("枚举类字段取值方法调用失败: " + enumClass.getSimpleName());
        }

        return false;
    }

    /**
     * 返回给定整型值的对应的枚举类.
     * <p> 如无整型字段, 抛出异常
     * <p> 如值对应多个枚举, 使用第一个
     */
    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("枚举类字段取值方法调用失败: " + enumClass.getSimpleName());
        }

        return null;
    }

    /**
     * 返回给字符串的对应的枚举类.
     * <p> 如无字符串字段, 抛出异常
     * <p> 如字符串对应多个枚举, 使用第一个
     */
    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("枚举类字段取值方法调用失败: " + 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("枚举类无数值字段: " + 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);
    }
}

上述代码没有什么好讲, 跟反射获取对象中字段, 并调用对应 getter 方法基本一样. 唯一需要注意的点是枚举泛型的写法<E extends Enum<E>>.

同时, 利用 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;
}

这样写枚举, 看上去是不是简洁清爽了很多? 再写新的枚举, 只需要关注枚举项即可.

除此之外, 写枚举还有一些小经验或注意事项. 首先是命名, 可以不加后面的 "Enum", 通常凡是叫 XXXStatus 或者 XXXType 的, 基本上一望可知, 不是常量就是枚举, 而且 Java 是强类型语言, 用名称表明类型多少有些啰嗦. 其次, 如果枚举中的数值是自己设置, 最好不要设成 1, 2, 3 而是最好设置成 10, 20, 30 或者 100, 200, 300; 这样方便后续扩展, 可以在中间添加枚举项. 而且同一类枚举, 值设置在各系统中最好统一, 比如审核状态, 均设置 100 为审核通过, -100 为审核不通过, 这样可以加快不同项目代码的上手速度.

枚举常见的使用错误主要在比较判断上. 一种是取 value 值 Integer, 不使用 equals 而用 == 判断. 另一种是使用了 equals, 却不小心拿枚举类型和整型比较. 前者属于基本类型与包装类型不分. 由于整型池原理, 枚举 value 值设置在 127 以内, 估计一时都察觉不到问题. 一般都要吃过一次大亏, 上过一次大当之后, 才能增长此类经验教训. 上文中摘抄的代码其实就有这个问题. 后一种错误多是无心之失, 代码写快了总是难易避免. 不过, 如果养成良好习惯, 提交代码之前, 使用 findbug 插件扫描一下, 这两种错误就能轻松发现了.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342