重拾Java基础知识:枚举

前言

关键字 enum 可以将一组具名的值的有限集合创建为一种新的类型,而这些具名的值可以作为常规的程序组件使用。这是一种非常有用的功能

基本 enum 特性

调用 enumvalues() 方法,可以遍历 enum 实例 .values() 方法返回 enum 实例的数组,而且该数组中的元素严格保持其在 enum 中声明时的顺序,因此你可以在循环中使用 values() 返回的数组。

public enum EnumClass {
    ONE,TWO,THREE
}
public class Test {
    public static void main(String[] args) {
        for (EnumClass enumClass:EnumClass.values()) {
            System.out.println(enumClass);
        }
        /** Output:
         *  ONE
         *  TWO
         *  THREE
         */
    }

方法添加

除了不能继承自一个 enum 之外,我们基本上可以将 enum 看作一个常规的类。也就是说我们可以向 enum 中添加方法。enum 甚至可以有 main() 方法。

public enum EnumClass {
    ONE("one","一"),
    TWO("two","二"),
    THREE("three","三");

    private String code;
    private String value;

    EnumClass(String code, String value) {
        this.code = code;
        this.value = value;
    }
    
    public static String getValues(String code){
        for (EnumClass enumClass:EnumClass.values()) {
            if(enumClass.code.equals(code)){
                return enumClass.value;
            }
        }
        return null;
    }

    public static void main(String[] args) {
        for (EnumClass enumClass:EnumClass.values()) {
            System.out.println(enumClass.code+","+enumClass.value);
        }
        /** Output: 
         *  one,一
         *  two,二
         *  three,三
         */
    }
}

注意,如果你打算定义自己的方法,那么必须在 enum 实例序列的最后添加一个分号。同时,Java 要求你必须先定义 enum 实例。如果在定义 enum 实例之前定义了任何方法或属性,那么在编译时就会得到错误信息。

enum 中的构造器与方法和普通的类没有区别,因为除了有少许限制之外,enum 就是一个普通的类。所以,我们可以使用 enum 做许多事情(虽然,我们一般只使用普通的枚举类型)

覆盖 enum 的方法

覆盖 toSring() 方法,给我们提供了另一种方式来为枚举实例生成不同的字符串描述信息。 在下面的示例中,我们使用的就是实例的名字,不过我们希望改变其格式。覆盖 enumtoSring() 方法与覆盖一般类的方法没有区别:

public enum EnumClass {
    ONE,
    TWO,
    THREE;

    @Override
    public String toString() {
        String name = name();
        return name.charAt(0)+name.substring(1).toLowerCase();

    }
    public static void main(String[] args) {
        for (EnumClass enumClass:EnumClass.values()) {
            System.out.println(enumClass);
        }
        /** Output:
         *  One
         *  Two
         *  Three
         */
    }
}

switch 语句中的 enum

switch 中使用 enum,是 enum 提供的一项非常便利的功能。一般来说,在 switch 中只能使用整数值,而枚举实例天生就具备整数值的次序,并且可以通过 ordinal() 方法取得其次序(显然编译器帮我们做了类似的工作),因此我们可以在 switch 语句中使用 enum

虽然一般情况下我们必须使用 enum 类型来修饰一个 enum 实例,但是在 case 语句中却不必如此。下面的例子使用 enum 构造了一个小型状态机:

public enum EnumClass {
    ONE,
    TWO,
    THREE;

    public static void main(String[] args) {
        EnumClass enumClass = EnumClass.ONE;
        for (int i = 0; i < 3; i++) {
            switch (enumClass){
                case ONE:
                    enumClass = EnumClass.TWO;
                    System.out.println("one");
                    break;
                case TWO:
                    enumClass = EnumClass.THREE;
                    System.out.println("two");
                case THREE:
                    enumClass = EnumClass.ONE;
                    System.out.println("three");
                    break;
            }
        }
        /** Output:
         *  one
         *  two
         *  three
         *  one
         */
    }
}

values 方法的神秘之处

我们可以利用反射机制编写一个简单的程序,来查看其中的究竟:

public enum EnumClass {
    ONE,
    TWO,
    THREE;

    public static void main(String[] args) {
        Class cla = EnumClass.class;
        System.out.println(cla.getSuperclass());
        Set<String> stringSet = new HashSet<>();
        for (Method method:cla.getMethods()) {
            stringSet.add(method.getName());
        }
        System.out.println(stringSet);
        /** Output:
         *  class java.lang.Enum
         *  [getClass, wait, valueOf, values, notifyAll, main, compareTo, notify, getDeclaringClass, hashCode, equals, name, toString, ordinal]
         */
    }
}

我们已经知道,所有的 enum 都继承自 Java.lang.Enum 类。由于 Java 不支持多重继承,所以你的 enum 不能再继承其他类。反编译后:
在这里插入图片描述
values() 是由编译器添加的 static 方法。由于 values() 方法是由编译器插入到 enum 定义中的 static 方法,所以,如果你将 enum 实例向上转型为 Enum,那么 values() 方法就不可访问了。

EnumSet

Set 是一种集合,只能向其中添加不重复的对象。Java SE5 引入 EnumSet,是为了通过 enum 创建一种替代品,以替代传统的基于 int 的“位标志”。这种标志可以用来表示某种“开/关”信息,不过,使用这种标志,我们最终操作的只是一些 bit,而不是这些 bit 想要表达的概念,因此很容易写出令人难以理解的代码。

EnumSet 的设计充分考虑到了速度因素,因为它必须与非常高效的 bit 标志相竞争(其操作与 HashSet 相比,非常地快),就其内部而言,它(可能)就是将一个 long 值作为比特向量,所以 EnumSet 非常快速高效。使用 EnumSet 的优点是,它在说明一个二进制位是否存在时,具有更好的表达能力,并且无需担心性能。

public enum EnumClass {
    ONE,
    TWO,
    THREE;

    public static void main(String[] args) {

        EnumSet enumSet = EnumSet.allOf(EnumClass.class);
        enumSet.add(EnumClass.ONE);
        System.out.println(enumSet);
        enumSet.remove(ONE);
        System.out.println(enumSet);
        /** Output:
         *  [ONE, TWO, THREE]
         *  [TWO, THREE]
         */
    }
}

使用 static import 可以简化 enum 常量的使用。EnumSet 的方法的名字都相当直观,你可以查阅 JDK 文档找到其完整详细的描述。如果仔细研究了 EnumSet 的文档,你还会发现 of() 方法被重载了很多次,不但为可变数量参数进行了重载,而且为接收 2 至 5 个显式的参数的情况都进行了重载。这也从侧面表现了 EnumSet 对性能的关注。因为,其实只使用单独的 of() 方法解决可变参数已经可以解决整个问题了,但是对比显式的参数,会有一点性能损失。采用现在这种设计,当你只使用 2 到 5 个参数调用 of() 方法时,你可以调用对应的重载过的方法(速度稍快一点),而当你使用一个参数或多过 5 个参数时,你调用的将是使用可变参数的 of() 方法。注意,如果你只使用一个参数,编译器并不会构造可变参数的数组,所以与调用只有一个参数的方法相比,也就不会有额外的性能损耗。

EnumMap

EnumMap 是一种特殊的 Map,它要求其中的键(key)必须来自一个 enum,由于 enum 本身的限制,所以 EnumMap 在内部可由数组实现。因此 EnumMap 的速度很快,我们可以放心地使用 enum 实例在 EnumMap 中进行查找操作。不过,我们只能将 enum 的实例作为键来调用 put() 可方法,其他操作与使用一般的 Map 差不多。

public enum EnumClass {
    ONE,
    TWO,
    THREE;

    public static void main(String[] args) {
        EnumMap enumMap = new EnumMap(EnumClass.class);
        System.out.println(enumMap);
        enumMap.put(EnumClass.ONE,"one");
        enumMap.put(EnumClass.TWO,"two");
        enumMap.put(EnumClass.THREE,"three");
        System.out.println(enumMap);
        enumMap.put(EnumClass.THREE,"3");
        System.out.println(enumMap);
        /** Output:
         *  {}
         *  {ONE=one, TWO=two, THREE=three}
         *  {ONE=one, TWO=two, THREE=3}
         */
    }
}

EnumSet 一样,enum 实例定义时的次序决定了其在 EnumMap 中的顺序。相比,EnumMap 有一个优点,那 EnumMap 允许程序员改变值对象,而常量相关的方法在编译期就被固定了。

常量特定方法

Javaenum 有一个非常有趣的特性,即它允许程序员为 enum 实例编写方法,从而为每个 enum 实例赋予各自不同的行为。要实现常量相关的方法,你需要为 enum 定义一个或多个 abstract 方法,然后为每个 enum 实例实现该抽象方法。

public enum EnumClass {
    ONE{
        @Override
        void getInfo() {
            System.out.println("one");
        }
    },
    TWO{
        @Override
        void getInfo() {
            System.out.println("two");
        }
    },
    THREE{
        @Override
        void getInfo() {
            System.out.println("three");
        }
    };

    abstract void getInfo();
    
    public static void main(String[] args) {
        for (EnumClass enumClass:values()){
            enumClass.getInfo();
        }
        /** Output:
         *  one
         *  two
         *  three
         */
    }
}

在面向对象的程序设计中,不同的行为与不同的类关联。而通过常量相关的方法,每个 enum 实例可以具备自己独特的行为,这似乎说明每个 enum 实例就像一个独特的类。在上面的例子中,enum 实例似乎被当作其“超类”ConstantSpecificMethod 来使用,在调用 getInfo() 方法时,体现出多态的行为。

除了实现 abstract 方法以外,程序员是否可以覆盖常量相关的方法呢?答案是肯定的,参考下面的程序:

public enum EnumClass {
    ONE {
        @Override
        void getInfo() {
            System.out.println("one");
        }
    },
    TWO {
        @Override
        void getInfo() {
            System.out.println("two");
        }
    },
    THREE {
        @Override
        void getInfo() {
            System.out.println("three");
        }

    };

    void getInfo() {
        System.out.println("getInfo");
    }

    public static void main(String[] args) {
        for (EnumClass enumClass : values()) {
            enumClass.getInfo();
        }
        /** Output:
         *  one
         *  two
         *  three
         */
    }
}

本章小结

虽然 Java 中的枚举比 C 或 **C++**中的 enum 更成熟,但它仍然是一个“小”功能,Java 没有它也已经(虽然有点笨拙)存在很多年了。有时恰恰因为它,你才能够优雅而干净地解决问题。优雅与清晰很重要,正是它们区别了成功的解决方案与失败的解决方案。而失败的解决方案就是因为其他人无法理解它。

虽然枚举类型本身并不是特别复杂,程序员可以将 enumJava 语言的其他功能结合使用,例如多态、泛型和反射。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值