“心情放松了不少。”三妹说,“可以开始学 Java 了,二哥。”

    “OK。”

    “枚举(enum),是 Java 1.5 时引入的关键字,它表示一种特殊类型的类,继承自 java.lang.Enum。”

    “我们来新建一个枚举 PlayerType。”

    “二哥,我没看到有继承关系呀!”

    “别着急,看一下反编译后的字节码,你就明白了。”

    1. {
    2. public static PlayerType[] values()
    3. {
    4. return (PlayerType[])$VALUES.clone();
    5. }
    6. public static PlayerType valueOf(String name)
    7. {
    8. return (PlayerType)Enum.valueOf(com/cmower/baeldung/enum1/PlayerType, name);
    9. }
    10. private PlayerType(String s, int i)
    11. {
    12. super(s, i);
    13. }
    14. public static final PlayerType TENNIS;
    15. public static final PlayerType FOOTBALL;
    16. public static final PlayerType BASKETBALL;
    17. private static final PlayerType $VALUES[];
    18. static
    19. {
    20. TENNIS = new PlayerType("TENNIS", 0);
    21. FOOTBALL = new PlayerType("FOOTBALL", 1);
    22. BASKETBALL = new PlayerType("BASKETBALL", 2);
    23. $VALUES = (new PlayerType[] {
    24. TENNIS, FOOTBALL, BASKETBALL
    25. });
    26. }
    27. }

    “看到没?Java 编译器帮我们做了很多隐式的工作,不然手写一个枚举就没那么省心省事了。”

    • 要继承 Enum 类;
    • 要声明静态变量和数组;
    • 要用 static 块来初始化静态变量和数组;
    • 要提供静态方法,比如说 values()valueOf(String name)

    “确实,作为开发者,我们的代码量减少了,枚举看起来简洁明了。”三妹说。

    “既然枚举是一种特殊的类,那它其实是可以定义在一个类的内部的,这样它的作用域就可以限定于这个外部类中使用。”我说。

    1. public class Player {
    2. public enum PlayerType {
    3. TENNIS,
    4. FOOTBALL,
    5. BASKETBALL
    6. }
    7. public boolean isBasketballPlayer() {
    8. return getType() == PlayerType.BASKETBALL;
    9. }
    10. public PlayerType getType() {
    11. return type;
    12. }
    13. public void setType(PlayerType type) {
    14. this.type = type;
    15. }
    16. }

    PlayerType 就相当于 Player 的内部类。

    由于枚举是 final 的,所以可以确保在 Java 虚拟机中仅有一个常量对象,基于这个原因,我们可以使用“==”运算符来比较两个枚举是否相等,参照 isBasketballPlayer() 方法。

    “那为什么不使用 equals() 方法判断呢?”三妹问。

    1. if(player.getType().equals(Player.PlayerType.BASKETBALL)){};

    “我来给你解释下。”

    “==”运算符比较的时候,如果两个对象都为 null,并不会发生 NullPointerException,而 equals() 方法则会。

    另外, “==”运算符会在编译时进行检查,如果两侧的类型不匹配,会提示错误,而 equals() 方法则不会。

    “如果枚举中需要包含更多信息的话,可以为其添加一些字段,比如下面示例中的 name,此时需要为枚举添加一个带参的构造方法,这样就可以在定义枚举时添加对应的名称了。”我继续说。

    1. public enum PlayerType {
    2. TENNIS("网球"),
    3. FOOTBALL("足球"),
    4. BASKETBALL("篮球");
    5. private String name;
    6. PlayerType(String name) {
    7. this.name = name;
    8. }

    “get 了吧,三妹?”

    “嗯,比较好理解。”

    “那接下来,我就来说点不一样的。”

    “来吧,我准备好了。”

    “EnumSet 是一个专门针对枚举类型的 Set 接口(后面会讲)的实现类,它是处理枚举类型数据的一把利器,非常高效。”我说,“从名字上就可以看得出,EnumSet 不仅和 Set 有关系,和枚举也有关系。”

    “因为 EnumSet 是一个抽象类,所以创建 EnumSet 时不能使用 new 关键字。不过,EnumSet 提供了很多有用的静态工厂方法。”

    单例的最佳实现方式——枚举 - 图2

    “来看下面这个例子,我们使用 静态工厂方法创建了一个空的 PlayerType 类型的 EnumSet;使用 allOf() 静态工厂方法创建了一个包含所有 PlayerType 类型的 EnumSet。”

    1. public class EnumSetTest {
    2. public enum PlayerType {
    3. TENNIS,
    4. FOOTBALL,
    5. BASKETBALL
    6. }
    7. public static void main(String[] args) {
    8. EnumSet<PlayerType> enumSetNone = EnumSet.noneOf(PlayerType.class);
    9. System.out.println(enumSetNone);
    10. EnumSet<PlayerType> enumSetAll = EnumSet.allOf(PlayerType.class);
    11. System.out.println(enumSetAll);
    12. }
    13. }

    “来看一下输出结果。”

    1. []
    2. [TENNIS, FOOTBALL, BASKETBALL]

    有了 EnumSet 后,就可以使用 Set 的一些方法了,见下图。

    “除了 EnumSet,还有 EnumMap,是一个专门针对枚举类型的 Map 接口的实现类,它可以将枚举常量作为键来使用。EnumMap 的效率比 HashMap 还要高,可以直接通过数组下标(枚举的 ordinal 值)访问到元素。”

    “和 EnumSet 不同,EnumMap 不是一个抽象类,所以创建 EnumMap 时可以使用 new 关键字。”

    有了 EnumMap 对象后就可以使用 Map 的一些方法了,见下图。

    单例的最佳实现方式——枚举 - 图4

    1. EnumMap<PlayerType, String> enumMap = new EnumMap<>(PlayerType.class);
    2. enumMap.put(PlayerType.BASKETBALL,"篮球运动员");
    3. enumMap.put(PlayerType.FOOTBALL,"足球运动员");
    4. enumMap.put(PlayerType.TENNIS,"网球运动员");
    5. System.out.println(enumMap);
    6. System.out.println(enumMap.get(PlayerType.BASKETBALL));
    7. System.out.println(enumMap.containsKey(PlayerType.BASKETBALL));
    8. System.out.println(enumMap.remove(PlayerType.BASKETBALL));

    “来看一下输出结果。”

    1. {TENNIS=网球运动员, FOOTBALL=足球运动员, BASKETBALL=篮球运动员}
    2. 篮球运动员
    3. true
    4. 篮球运动员

    “除了以上这些,《Effective Java》这本书里还提到了一点,如果要实现单例的话,最好使用枚举的方式。”我说。

    “等等二哥,单例是什么?”三妹没等我往下说,就连忙问道。

    “单例(Singleton)用来保证一个类仅有一个对象,并提供一个访问它的全局访问点,在一个进程中。因为这个类只有一个对象,所以就不能再使用 new 关键字来创建新的对象了。”

    “Java 标准库有一些类就是单例,比如说 Runtime 这个类。”

    1. Runtime runtime = Runtime.getRuntime();

    “Runtime 类可以用来获取 Java 程序运行时的环境。”

    “关于单例,懂了些吧?”我问三妹。

    “噢噢噢噢。”三妹点了点头。

    “通常情况下,实现单例并非易事,来看下面这种写法。”

    “要用到 volatile、synchronized 关键字等等,但枚举的出现,让代码量减少到极致。”

    1. public enum EasySingleton{
    2. INSTANCE;

    “就这?”三妹睁大了眼睛。

    “对啊,枚举默认实现了 Serializable 接口,因此 Java 虚拟机可以保证该类为单例,这与传统的实现方式不大相同。传统方式中,我们必须确保单例在反序列化期间不能创建任何新实例。”我说。

    “好了,关于枚举就讲这么多吧,三妹,你把这些代码都手敲一遍吧!”

    “好勒,这就安排。二哥,你去休息吧。”

    “嗯嗯。”讲了这么多,必须跑去抽烟机那里安排一根华子了。


    Java 程序员进阶之路》预计一个月左右会有一次内容更新和完善,大家在我的公众号 沉默王二 后台回复“03” 即可获取最新版!如果觉得内容不错的话,欢迎转发分享!