【JavaSE】Java枚举(enum)全面解读

一、枚举的定义

Enhancements in Java SE 5.0

  • Typesafe Enums - This flexible object-oriented enumerated type facility allows you to create enumerated types with arbitrary methods and fields. It provides all the benefits of the Typesafe Enum pattern (“Effective Java,” Item 21) without the verbosity and the error-proneness. (JSR 201)

谷歌翻译如下:
Typesafe Enums - 这种灵活的面向对象的枚举类型工具允许您使用任意方法和字段创建枚举类型。 它提供了 Typesafe Enum 模式(“Effective Java”,第 21 项)的所有优点,而且没有冗长和容易出错的问题。 (JSR 201)

枚举类型(enum type)是Java 5版本提供的一种特殊的引用类型:一种特殊的类,以关键字enum来定义。
枚举和类(Class)一样,可以包含字段、构造函数和方法,也可以实现接口。

二、枚举的特性

2.1、易读性/类型安全性

在没有枚举类型之前,表示一年四季等固定的常量一般是使用 int 或者 String 常量来定义。如:

public  static  final  int  SPRING = 1;
public  static  final  int  SUMMER = 2;
public  static  final  int  AUTUMN = 3;
public  static  final  int  WINTER = 4;
// 或者
public  static  final  String  SPRING = "spring";

这种方法称作int枚举模式String枚举模式,这种做法存在很多不足:

1、int的易读性较差,虽然可以使用常量变量名来区分,但是不用常量直接使用int也不会报错。
2、String的易读性没问题,但是比较依赖于字符串的比较,存在性能问题
3、二者都存在类型安全性问题

所谓类型安全性,是指只要类型匹配,即使值不对代码编译也不会报错,但运行结果并不符合业务逻辑。
如上:一年四季定义为1234,程序使用456等不存在的非法值,或者不使用定义的常量SPRING,而是硬编码拼写spring还手误错写成sprnig 时,编译时正常通过,但是运行时会因为不符合约定而出现业务逻辑的错误。

一个简单的枚举定义如下:

public enum SeasonEnum {
    
     
    SPRING, SUMMER, AUTUMN, WINTER
}

枚举类型保证了编译时的类型安全性,不是指定的枚举编译时就会报错

扫描二维码关注公众号,回复: 14862853 查看本文章

2.2、单例

在这里插入图片描述

如上图所示,枚举通过三个机制保证了 其单例特性:

1、枚举方法不能被 clone
2、枚举不能通过反射实例化
3、枚举不能通过反序列化创建实例

三、枚举的使用

3.1、常量/枚举比较/switch

最基本的用法就是 只声明枚举实例的常量

public enum FruitsEnum {
    
    
    /** 苹果 */
    APPLE,
    /** 香蕉 */
    BANANA,
    /** 橙子 */
    ORANGES,
    /** 葡萄 */
    GRAPES,
    /** 未知 */
    UNKNOWN
}
/**
 * 基本使用
 * <pre>
 * 1、枚举常量测试
 * 2、枚举比较测试
 * 3、switch测试
 * </pre>
 *
 * @author gmlee
 * @date 2023-02-25
 */
public class FruitsEnumTest {
    
    

    @Test
    public void constantsTest() {
    
    
        System.out.println(FruitsEnum.APPLE);
        System.out.println(FruitsEnum.APPLE.name());

        Assertions.assertEquals("APPLE", FruitsEnum.APPLE.name());

        // 输出:
        // APPLE
        // APPLE
    }


    /**
     * <li>
     * 枚举本身就是单例的,枚举间的比较用 == 还是 equals效果是一样的,equals底层就是用 == 来比较的
     * <li>
     * 枚举 compareTo()方法比较的是 ordinal 的大小
     * <pre>
     *     public final int compareTo(E o) {
     *         Enum<?> other = (Enum<?>)o;
     *         Enum<E> self = this;
     *         if (self.getClass() != other.getClass() && // optimization
     *             self.getDeclaringClass() != other.getDeclaringClass())
     *             throw new ClassCastException();
     *         return self.ordinal - other.ordinal;
     *     }
     */
    @Test
    public void compareTest() {
    
    
        if (FruitsEnum.APPLE == FruitsEnum.APPLE) {
    
    
            System.out.println("true....");
        }

        if (FruitsEnum.APPLE.equals(FruitsEnum.APPLE)) {
    
    
            System.out.println("true....");
        }

        // ordinal 从0开始自增
        System.out.println("APPLE.ordinal(): " + FruitsEnum.APPLE.ordinal());
        System.out.println("GRAPES.ordinal(): " + FruitsEnum.GRAPES.ordinal());
        Assertions.assertEquals(0, FruitsEnum.APPLE.ordinal());
        Assertions.assertEquals(3, FruitsEnum.GRAPES.ordinal());

        Assertions.assertEquals(-3, FruitsEnum.APPLE.compareTo(FruitsEnum.GRAPES));
        // 输出:
        // true....
        // true....
        // APPLE.ordinal(): 0
        // GRAPES.ordinal(): 3
    }


    @Test
    public void switchTest() {
    
    
        final String apple = fruitSwitch(FruitsEnum.APPLE);
        Assertions.assertEquals("苹果!", apple);

        final String unknown = fruitSwitch(FruitsEnum.UNKNOWN_FRUITS);
        Assertions.assertEquals("未知水果!", unknown);

        // 输出:
        // 水果是:苹果!
        // 水果是:未知水果!
    }


    /**
     * Switch测试
     */
    private String fruitSwitch(FruitsEnum fruit) {
    
    
        String name = "";
        switch (fruit) {
    
    
            case APPLE:
                name = "苹果!";
                break;
            case BANANA:
                name = "香蕉!";
                break;
            case ORANGES:
                name = "橙子!";
                break;
            case GRAPES:
                name = "葡萄!";
                break;
            case UNKNOWN_FRUITS:
            default:
                name = "未知水果!";
        }
        System.out.println("水果是:" + name);
        return name;
    }

}

3.2、成员属性和方法

/**
 * 具有 自定义构造器、属性 的枚举常量,属性查询方法
 * <pre>
 * 《Java开发手册(黄山版)》阿里规约:
 * (九) 注释规约
 * 6.【强制】枚举 enum(括号内)的属性字段必须是私有且不可变。
 * @author gmlee
 * @date 2023-02-25
 */
@Getter
public enum LanguageEnum {
    
    

    JAVA(1, "java", "hello java"),
    PYTHON(2, "python", "hello python"),
    GO(3, "go", "hello golang"),
    PHP(4, "php", "PHP是世界上最美的语言!"),
    UNKNOWN(-1, "unknown", "未知的语言!");

    /**
     * 自定义属性字段
     */
    private final Integer code;

    private final String name;

    private final String desc;

    /**
     * 枚举构造器
     * <pre>
     *    1、同 Class,不显示声明,则自动隐士声明一个无参构造器
     *    2、不同于Class,枚举的构造器 访问修饰符缺省为 private
     *    3、枚举的构造器若声明为 public、protected 时,编译报错
     * </pre>
     */
    LanguageEnum(Integer code, String name, String desc) {
    
    
        this.code = code;
        this.name = name;
        this.desc = desc;
    }

    /**
     * 静态方法,获取枚举方式一:遍历
     */
    public static LanguageEnum getLanguageByCode(Integer code) {
    
    
        for (LanguageEnum value : LanguageEnum.values()) {
    
    
            if (value.code.equals(code)) {
    
    
                return value;
            }
        }
        return UNKNOWN;
    }


    /**
     * 获取枚举方式二:Map
     */
    private static final Map<String, LanguageEnum> LANGUAGE_ENUM_MAP = new HashMap<>();

    static {
    
    
        for (LanguageEnum value : LanguageEnum.values()) {
    
    
            LANGUAGE_ENUM_MAP.put(value.getName(), value);
        }
    }

    public static LanguageEnum getLanguageByName(String name) {
    
    
        return LANGUAGE_ENUM_MAP.get(name);
    }

}
/**
 * 枚举 属性/静态方法测试
 *
 * @author gmlee
 * @date 2023-02-25
 */
public class LanguageEnumTest {
    
    

    @Test
    public void memberTest() {
    
    
        // JAVA(1, "java", "hello java")
        Assertions.assertEquals(1, LanguageEnum.JAVA.getCode());
        Assertions.assertEquals("java", LanguageEnum.JAVA.getName());
        Assertions.assertEquals("hello java", LanguageEnum.JAVA.getDesc());
    }

    @Test
    public void staticMethodTest() {
    
    
        // JAVA(1, "java", "hello java")
        Assertions.assertEquals(LanguageEnum.JAVA, LanguageEnum.getLanguageByCode(1));
        Assertions.assertEquals(LanguageEnum.JAVA, LanguageEnum.getLanguageByName("java"));

        Assertions.assertNull(LanguageEnum.getLanguageByName("xxx"));
    }

}

3.3、抽象方法

《Effective Java中文版(原书第3版)》中称为:特定于常量的方法实现(constant-specific method implementation ),也即是策略枚举(strategy enum)

/**
 * 特定于常量的方法实现(constant-specific method  implementation )
 *
 * @author gmlee
 * @date 2023-02-25
 */
@Getter
@AllArgsConstructor
public enum SeasonEnum {
    
    
    SPRING("春") {
    
    
        @Override
        public void printDesc() {
    
    
            System.out.println(this.getName() + ": " + "沾衣欲湿杏花雨,吹面不寒杨柳风!");
        }
    },
    SUMMER("夏") {
    
    
        @Override
        public void printDesc() {
    
    
            System.out.println(this.getName() + ": " + "接天莲叶无穷碧,映日荷花别样红!");
        }
    },
    AUTUMN("秋") {
    
    
        @Override
        public void printDesc() {
    
    
            System.out.println(this.getName() + ": " + "落霞与孤鹜齐飞,秋水共长天一色!");
        }
    },
    WINTER("冬") {
    
    
        @Override
        public void printDesc() {
    
    
            System.out.println(this.getName() + ": " + "千里黄云白日曛,北风吹雁雪纷纷!");
        }
    };

    /**
     * 名称
     */
    private final String name;

    /**
     * 抽象方法
     * <pre>
     *     1、枚举中包含抽象方法会编译报错,除非满足一下条件:
     *     1.1、枚举中至少有一个枚举常量,如本枚举 SPRING("春"),
     *     1.2、所有的枚举常量都在Body体中提供该抽象方法的实现
     * </pre>
     */
    public abstract void printDesc();

}
/**
 * 枚举 抽象方法测试
 *
 * @author gmlee
 * @date 2023-02-25
 */
public class SeasonEnumTest {
    
    

    @Test
    public void abstractMethodTest() {
    
    
        Assertions.assertEquals("春", SeasonEnum.SPRING.getName());

        for (SeasonEnum value : SeasonEnum.values()) {
    
    
            value.printDesc();
        }
    }

}

3.4、实现接口

public interface IWork {
    
    

    String work();

}

方式一

/**
 * 实现接口,同 Class,实现接口,重写接口方法
 *
 * @author gmlee
 * @date 2023-02-25
 */
@Getter
@AllArgsConstructor
public enum Week1Enum implements IWork {
    
    
    MONDAY(1, "周一"),
    TUESDAY(2, "周二"),
    WEDNESDAYS(3, "周三"),
    THURSDAY(4, "周四"),
    FRIDAY(5, "周五"),
    SATURDAY(6, "周六"),
    SUNDAY(7, "周日");

    private final Integer dayOfWeek;

    private final String name;

    @Override
    public String work() {
    
    
        return this.name + ": Week, do work.....";
    }


}

方式二

/**
 * 实现接口,通过枚举常量Body体来实现该接口的方法
 *
 * @author gmlee
 * @date 2023-02-25
 */
@Getter
@AllArgsConstructor
public enum Week2Enum implements IWork {
    
    
    MONDAY(1, "周一") {
    
    
        @Override
        public String work() {
    
    
            return getName() + ": Week1, 一帆风顺!";
        }
    },
    TUESDAY(2, "周二") {
    
    
        @Override
        public String work() {
    
    
            return getName() + ": Week1, 两全其美!";
        }
    },
    WEDNESDAYS(3, "周三") {
    
    
        @Override
        public String work() {
    
    
            return getName() + ": Week1, 三阳开泰!";
        }
    },
    THURSDAY(4, "周四") {
    
    
        @Override
        public String work() {
    
    
            return getName() + ": Week1, 四季发财!";
        }
    },
    FRIDAY(5, "周五") {
    
    
        @Override
        public String work() {
    
    
            return getName() + ": Week1, 五福临门!";
        }
    },
    SATURDAY(6, "周六") {
    
    
        @Override
        public String work() {
    
    
            return getName() + ": Week1, 六六大顺!";
        }
    },
    SUNDAY(7, "周日") {
    
    
        @Override
        public String work() {
    
    
            return getName() + ": Week1, 七星高照!";
        }
    };

    private final Integer dayOfWeek;

    private final String name;

}

单元测试

/**
 * 枚举 接口实现测试
 *
 * @author gmlee
 * @date 2023-02-25
 */
public class WeekEnumTest {
    
    

    @Test
    public void interfaceTest() {
    
    
        System.out.println("↓============ Week  ============↓");
        for (Week1Enum value : Week1Enum.values()) {
    
    
            System.out.println(value.work());
        }

        System.out.println("↓============ Week1 ============↓");
        for (Week2Enum value : Week2Enum.values()) {
    
    
            System.out.println(value.work());
        }
    }
        
    // 输出:
    // ↓============ Week  ============↓
    // 周一: Week, do work.....
    // 周二: Week, do work.....
    // 周三: Week, do work.....
    // 周四: Week, do work.....
    // 周五: Week, do work.....
    // 周六: Week, do work.....
    // 周日: Week, do work.....
    // ↓============ Week1 ============↓
    // 周一: Week1, 一帆风顺!
    // 周二: Week1, 两全其美!
    // 周三: Week1, 三阳开泰!
    // 周四: Week1, 四季发财!
    // 周五: Week1, 五福临门!
    // 周六: Week1, 六六大顺!
    // 周日: Week1, 七星高照!
}

四、枚举使用规范-阿里《Java开发手册(黄山版)》

一、编程规约

(一) 命名风格

18.【参考】枚举类名带上 Enum 后缀,枚举成员名称需要全大写,单词间用下划线隔开。
说明:枚举其实就是特殊的常量类,且构造方法被默认强制是私有。
正例:枚举名字为 ProcessStatusEnum 的成员名称:SUCCESS / UNKNOWN_REASON

(二) 常量定义

6.【推荐】如果变量值仅在一个固定范围内变化用 enum 类型来定义。
说明:如果存在名称之外的延伸属性应使用 enum 类型,下面正例中的数字就是延伸信息,表示一年中的第几个季节。
正例:

public enum SeasonEnum {
    
    
    SPRING(1), SUMMER(2), AUTUMN(3), WINTER(4);
    private int seq;
    SeasonEnum(int seq) {
    
    
        this.seq = seq;
    }
    public int getSeq() {
    
    
        return seq;
    }
}

(五) 日期时间

7.【推荐】使用枚举值来指代月份。如果使用数字,注意 Date,Calendar 等日期相关类的月份 month 取
值范围从 0 到 11 之间。
说明:参考 JDK 原生注释,Month value is 0-based. e.g., 0 for January.
正例:Calendar.JANUARY,Calendar.FEBRUARY,Calendar.MARCH 等来指代相应月份来进行传参或比较。

(九) 注释规约

5.【强制】所有的枚举类型字段必须要有注释,说明每个数据项的用途。

(十一) 其他

6.【强制】枚举 enum(括号内)的属性字段必须是私有且不可变。

六、工程结构

(二) 二方库依赖

5.【强制】二方库里可以定义枚举类型,参数可以使用枚举类型,但是接口返回值不允许使用枚举类型或者
包含枚举类型的 POJO 对象。

五、示例代码

https://gitee.com/qbhj/java-cases/tree/master/case-javase/src/main/java/com/qbhj/casejavase/enums

六、参考资料

《Java开发手册(黄山版)》
《Effective Java中文版(原书第3版)》
https://docs.oracle.com/javase/specs/jls/se15/html/jls-8.html#jls-8.9
https://docs.oracle.com/javase/8/docs/technotes/guides/language/enums.html

猜你喜欢

转载自blog.csdn.net/weixin_43582081/article/details/129334701