java编程思想 第19章 枚举类型

java编程思想 第19章 枚举类型

Java SE5 添加了一个新的特性, enum 关键字. 用来声明枚举类型集. 可以代替 常量类型集,即类似功能的一组常量.

一,引入枚举

使用 enum 关键字,以前, 我们这样声明和使用常量集:

/**
     *  声明一组常量集 fruits
     */
    public final static int APPLE = 0;
    public final static int BANANA = 1;
    public final static int PEAR = 2;
    public final static int ORANGE = 3;
    public final static int TOMATO = 4;


    public static void main(String[] args) {
        int fruitKind = TOMATO;
        switch (fruitKind) {
        case APPLE:
            System.out.println("I want to eat apple");
            break;
        case BANANA:
            System.out.println("I want to eat banana");
            break;
        case PEAR:
            System.out.println("I want to eat pear");
            break;
        case ORANGE:
            System.out.println("I want to eat orange");
            break;
        case TOMATO:
            System.out.println("I want to eat tomato");
            break;
        default:
            System.out.println("there is not any fruit");
            break;
        }
    }
/**
     *  enum 声明
     * @author aloe
     *
     */
    public enum Fruits{
        APPLE, BANANA, PEAR, ORANGE, TOMATO
    }

enum 声明的常量类型集,功能很强大,常量限定在一定范围内,很安全.

public static void main(String[] args) {
// 使用 enum 就类似使用普通的 class,创建一个 enum 类型的引用,并给其赋值
        Fruits oneFruit = Fruits.APPLE;
        System.out.println(oneFruit);

打印结果:

APPLE

之所以会打印这样的结果,是因为:创建 enum 时,编译器会自动添加一些有用的属性, 可以理解为 Fruits 是 Enum.class 的一个实例.

java编程思想 第19章 枚举类型_第1张图片

图 1.1

因此 APPLE, BANANA, PEAR, ORANGE, TOMATO 都是 Enum.class 的实例,都具有这些属性和方法. 其中 toString() 的实现如下:

 private final String name;
public String toString() {
        return name;
    }

所以可以很方便的显示 enum 实例的名字, 其中 ordinal() 返回的是 ordinal,是 每个 enum 常量的声明顺序序号,
values() 返回 enum 按常量声明的顺序的常量数组.

for (Fruits fruit : Fruits.values()) {
            System.out.println(fruit + ".ordinal " + fruit.ordinal());
        }

打印结果:

APPLE.ordinal 0
BANANA.ordinal 1
PEAR.ordinal 2
ORANGE.ordinal 3
TOMATO.ordinal 4

尽管 enum 看起来像是一种新的数据类型,但是这个关键字只是为 enum生成对应的类时,产生了某些编译行为,因此在很大程度上,你可以将 enum 当作其他任何类来处理.
switch 要在有限的可能集合中进行选择,因此与 enum配合使用,是绝佳的组合. enum 的名字可以倍加的展示程序意欲何为.

/**
     *  enum 声明
     * @author aloe
     *
     */
    public enum Fruits{
        APPLE, BANANA, PEAR, ORANGE, TOMATO
    }
    private Fruits oneFruit;
    private EnumKeyword(Fruits oneFruit){
        this.oneFruit = oneFruit;
    }
    private void choiceOneFruit(){
        switch (oneFruit) {
        case APPLE:
            System.out.println("I want to eat apple");
            break;
        case BANANA:
            System.out.println("I want to eat banana");
            break;
        case PEAR:
            System.out.println("I want to eat pear");
            break;
        case ORANGE:
            System.out.println("I want to eat orange");
            break;
        case TOMATO:
            System.out.println("I want to eat tomato");
            break;
        default:
            System.out.println("there is not any fruit");
            break;
        }
    }
    public static void main(String[] args) {
        EnumKeyword enumKeyword = new EnumKeyword(Fruits.BANANA);
        enumKeyword.choiceOneFruit();
        }
I want to eat banana

二,基本的 enum 特性

创建 enum 时,编译器会生成一个 相关的类,上面例子中,就生成了一个 EnumKeyword$Fruits.class 这个 类是继承于 java.lang.Enum 的,所以 图1.1的 属性和方法, 生成的 Fruits.class 都有. 下图为 eclipse 编译生成的 class 文件

java编程思想 第19章 枚举类型_第2张图片

图1.2

enum 的 values() 方法,生成一个 Fruits 实例类型的数组,并且是按照 enum 生成时的顺序,所以 用, values()就可以遍历 enum实例.

for (Fruits fruit : Fruits.values()) {
            //enum 实例的序列号,从0开始
            System.out.println(fruit + " fruit.ordinal " + fruit.ordinal() + " enum 实例的序列号,从0开始 ");

            //enum 的实例名字 和 toString() 返回的一样
            System.out.println(fruit + " fruit.name " + fruit.name() + " enum 的实例名字 和 toString() 返回的一样 ");
            System.out.print(fruit.compareTo(Fruits.PEAR) + " compareTo() 返回序列号的差 ");
            System.out.print("||||");

//          public final boolean equals(Object other) {
//          return this==other;
//      }
            System.out.print(fruit.equals(Fruits.PEAR) + " equals() 用 == 比较的 ");
            System.out.print("||||");
            System.out.println(fruit == Fruits.PEAR);
            System.out.println(fruit.getDeclaringClass() + " enum声明的类型 ");
            System.out.println("====================================================");
        }
        System.out.println("====================================================");
        System.out.println("====================================================");
        // produce an enum value from a string name
        for (String string : "APPLE BANANA PEAR ORANGE TOMATO".split(" ")) {
            System.out.println(Enum.valueOf(Fruits.class, string));
        }
PPLE fruit.ordinal 0 enum 实例的序列号,从0开始 
APPLE fruit.name APPLE enum 的实例名字 和 toString() 返回的一样 
-2 compareTo() 返回序列号的差 ||||false equals() 用 == 比较的 ||||false
class com.crg.enumDemo.EnumKeyword$Fruits enum声明的类型 
====================================================
BANANA fruit.ordinal 1 enum 实例的序列号,从0开始 
BANANA fruit.name BANANA enum 的实例名字 和 toString() 返回的一样 
-1 compareTo() 返回序列号的差 ||||false equals() 用 == 比较的 ||||false
class com.crg.enumDemo.EnumKeyword$Fruits enum声明的类型 
====================================================
PEAR fruit.ordinal 2 enum 实例的序列号,从0开始 
PEAR fruit.name PEAR enum 的实例名字 和 toString() 返回的一样 
0 compareTo() 返回序列号的差 ||||true equals() 用 == 比较的 ||||true
class com.crg.enumDemo.EnumKeyword$Fruits enum声明的类型 
====================================================
ORANGE fruit.ordinal 3 enum 实例的序列号,从0开始 
ORANGE fruit.name ORANGE enum 的实例名字 和 toString() 返回的一样 
1 compareTo() 返回序列号的差 ||||false equals() 用 == 比较的 ||||false
class com.crg.enumDemo.EnumKeyword$Fruits enum声明的类型 
====================================================
TOMATO fruit.ordinal 4 enum 实例的序列号,从0开始 
TOMATO fruit.name TOMATO enum 的实例名字 和 toString() 返回的一样 
2 compareTo() 返回序列号的差 ||||false equals() 用 == 比较的 ||||false
class com.crg.enumDemo.EnumKeyword$Fruits enum声明的类型 
====================================================
====================================================
====================================================
APPLE
BANANA
PEAR
ORANGE
TOMATO

三,enum 的静态导入

package com.crg.staticimport;

public enum Fruits {
    APPLE, BANANA, PEAR, ORANGE, TOMATO
}
package com.crg.staticimport;
import static com.crg.staticimport.Fruits.*;

public class TestStaticImport {
    private Fruits fruit;
    private TestStaticImport(Fruits fruit){
        this.fruit = fruit;
    }
    @Override
    public String toString() {
        return fruit.name();
    }
    public static void main(String[] args) {
        System.out.println(new TestStaticImport(APPLE));
        System.out.println(new TestStaticImport(ORANGE));
        System.out.println(new TestStaticImport(PEAR));
        System.out.println(new TestStaticImport(BANANA));
        System.out.println(new TestStaticImport(TOMATO));
    }
}
APPLE
ORANGE
PEAR
BANANA
TOMATO

使用 import static 能够将 enum的实例标识符带入当前的命名空间,所以无需再用 enum类型来修饰 enum 实例. 这是 隐式的使用 enum 的实例,有利有弊,编译器自然可以确保使用正确的类型,程序不容易理解.

四, 向 enum 中添加新的方法

enum 除了不能继承于一个 enum 外,可以将 enum看做一个常规的类.我们可以向 enum种添加 方法,enum甚至可以有main()方法.我们可能希望每个枚举实例可以返回自己的描述,而 toString() 只能返回 实例的名称.我们可以通过添加一个构造器,专门处理这个额外的信息,添加一个方法,返回这个额外的信息.

package com.crg.staticimport;

public enum FruitsAddAdditonalInfo {

    /**
     *  枚举实例 必须被定义在方法的前面
     */
    APPLE("这是苹果"), 
    BANANA("这是香蕉"), 
    PEAR("这是梨子"), 
    ORANGE("这是橘子"), 

    // 最后一个枚举实例后面必须添加一个";"
    TOMATO("这是西红柿");
    private String description;

    /**
     *  构造器的访问权限必须是 private 或者 package
     * @param description
     */
    private FruitsAddAdditonalInfo(String description){
        this.description = description;
    }
    public String getDescription(){
        return description;
    }

    @Override
    public String toString() {
        // TODO Auto-generated method stub
        return name() + " : " + getDescription();
    }
    public static void main(String[] args) {
        for (FruitsAddAdditonalInfo fruit : FruitsAddAdditonalInfo.values()) {
            System.out.println(fruit + " : " + fruit.getDescription());
        }
    }

}
APPLE : 这是苹果 : 这是苹果
BANANA : 这是香蕉 : 这是香蕉
PEAR : 这是梨子 : 这是梨子
ORANGE : 这是橘子 : 这是橘子
TOMATO : 这是西红柿 : 这是西红柿

注意: 定义自己的方法,必须在enum实例的最后添加分号,必须先定义 enum实例,然后才能定义属性和方法,否则编译器会报错;上面的代码中,我们覆盖了 toString()方法 上例的 构造器的访问权限是 private,对于它的可访问性而言,并没有什么变化,因为构造器只能在enum的内部使用. 一旦enum定义结束,编译器就不允许我们再使用构造器创建任何手机实例.

五, values() 的神秘之处

上面的例子已经看到, enum类继承自 Enum类,但是 Enum类里面并没有 values() 方法.那么 values() 从哪里来的呢? 我们利用发射写一个小的测试程序.

package com.crg.enumDemo;

import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.Set;
import java.util.TreeSet;

import com.crg.enumDemo.EnumKeyword.Fruits;

public class TheSecretOfValues {
    public static Set analyze(Class enumClass){
        System.out.println("-----------analyzing " + enumClass.getSimpleName() +" ----------------");

        //打印该类的接口
        System.out.print(enumClass.getSimpleName() + "'s interfaces: ");
        for (Type type : enumClass.getGenericInterfaces()) {
            System.out.print(type + "; ");
        }
        System.out.println();

        //打印该类的父类
        System.out.println("Base: " + enumClass.getSuperclass());

        //打印该类的所有方法名
        System.out.println("Methods: ");
        Set methods = new TreeSet<>();
        for (Method method : enumClass.getMethods()) {
            methods.add(method.getName());
        }

        System.out.println(methods);
        return methods;
    }
    public static void main(String[] args) {

        // 获得 Enum.class 的所有方法
        Set enumMethods = analyze(Enum.class);

        // 获得 Fruits.class 的所有方法
        Set fruitMethods = analyze(Fruits.class);
        System.out.println("----------------------------------------------------");
        System.out.println("fruitMethods.containsAll(enumMethods) ? : " + fruitMethods.containsAll(enumMethods));
        System.out.println("fruitMethods.removeAll(enumMethods) >>>>>>>>>>>>>>>>>>");
        fruitMethods.removeAll(enumMethods);
        System.out.println("fruitMethods 移除enumMethods后的方法为: ");

        //fruitMethods 移除enumMethods后的方法为:
        System.out.println(fruitMethods);
    }

}
-----------analyzing Fruits ----------------
Fruits's interfaces: 
Base: class java.lang.Enum
Methods: 
[compareTo, equals, getClass, getDeclaringClass, hashCode, name, notify, notifyAll, ordinal, toString, valueOf, values, wait]
----------------------------------------------------
fruitMethods.containsAll(enumMethods) ? : true
fruitMethods.removeAll(enumMethods) >>>>>>>>>>>>>>>>>>
fruitMethods 移除enumMethods后的方法为: 
[values]

我们用 javap -private Fruits 反编译 Fruits.class 得到:

Compiled from "Fruits.java"
public final class com.crg.staticimport.Fruits extends java.lang.Enum{
    public static final com.crg.staticimport.Fruits APPLE;
    public static final com.crg.staticimport.Fruits BANANA;
    public static final com.crg.staticimport.Fruits PEAR;
    public static final com.crg.staticimport.Fruits ORANGE;
    public static final com.crg.staticimport.Fruits TOMATO;
    private static final com.crg.staticimport.Fruits[] ENUM$VALUES;
    static {};
    private com.crg.staticimport.Fruits(java.lang.String, int);
    public static com.crg.staticimport.Fruits[] values();
    public static com.crg.staticimport.Fruits valueOf(java.lang.String);
}

所以可以看出来:
values() 是编译器添加的静态方法; 并且编译器还添加了 valueOf(java.lang.String) 一个参数的方法,继承 java.lang.Enum的 valueOf(Class enumType, String name) 是两个参数的方法.还可以看到,用 final 修饰 Fruits, 所以 Fruits 不能被继承.

values() 是编译器 为 Fruits 添加的方法, Enum 类并没有此方法, 把 Fruits 向上转型为 Enum,就不能使用 values() 方法了.但是可以使用 Class.getEnumConstants() 获得 Fruits 的实例.

Fruits[] fruits = Fruits.values();

        // Upcast 向上转型
        Enum fruit = Fruits.APPLE;
        for (Enum enumFruit : fruit.getClass().getEnumConstants()) {
            System.out.println(enumFruit);
        }
APPLE
BANANA
PEAR
ORANGE
TOMATO

getEnumConstants() 只针对枚举类,如果非枚举类,调用此方法,将会发生异常

Class integerClass = Integer.class;
        for (Object obj : integerClass.getEnumConstants()) {
            System.out.println(obj);
        }
Exception in thread "main" java.lang.NullPointerException
    at com.crg.enumDemo.TheSecretOfValues.main(TheSecretOfValues.java:62)

此时 getEnumConstants() 返回 null ,调用 null的 toString()就会抛出 NullPointerException.
从上面反编译出来的Fruits 代码可以看出来, Fruits 继承 Enum ,所以就不能再继承其他类了,但是可以实现.

package com.crg.staticimport;

import java.util.Random;

public enum Fruits implements Generator{
    APPLE, BANANA, PEAR, ORANGE, TOMATO;

    @Override
    public Fruits next() {
        Random random = new Random();
        return values()[random.nextInt(values().length)];
    }
}
package com.crg.staticimport;

public interface Generator {
    T next();
}
public static  void PrintNext(Generator generator){
        System.out.print(generator.next() + " ");
    }
    public static void main(String[] args) {

        Fruits generator = Fruits.BANANA;
        for (int i = 0; i < 10; i++) {
            PrintNext(generator);
        }
BANANA ORANGE BANANA APPLE PEAR ORANGE ORANGE APPLE APPLE PEAR 

实现一个类似上面的随机产生一个 Enum 实例的 功能:

package com.crg.staticimport;

import java.util.Random;

public enum Fruits implements Generator{
    APPLE, BANANA, PEAR, ORANGE, TOMATO;

    @Override
    public Fruits next() {
        Random random = new Random();
        return values()[random.nextInt(values().length)];
    }

    /**
     *  随机产生一个 Enum 的实例
     * @param ls
     * @return
     */
    public static > T randomEnum(Class ls){
        return random(ls.getEnumConstants());
    }
    /**
     *  随机产生一个 values 的数组元素
     * @param values
     * @return
     */
    public static  T random(T[] values){
        Random random = new Random();
        return values[random.nextInt(values.length)];
    }

    /**
     *  重写该方法
     */
    @Override
    public String toString() {
        return name() + " || ";
    }
}
for (int i = 0; i < 15; i++) {
            System.out.print(Fruits.randomEnum(Fruits.class));
        }
PEAR || BANANA || TOMATO || ORANGE || PEAR || BANANA || PEAR || PEAR || TOMATO || TOMATO || ORANGE || PEAR || BANANA || BANANA || BANANA || 

六, 使用接口组织枚举

enum 不能被继承,也就不能扩展 enum 中的元素,如果我们希望,对enum 中的元素实现分组,可以通过 在一个接口内部,创建实现接口的枚举,来实现分组的目的.

package com.crg.enuminterface;

public interface Food {
    // 油泼面,裤袋面,扯面
    enum Noodle implements Food {POUPO_NOODLE, POCKET_NOODLE, PULLED_NOODLE}

    // 盖浇饭,黄焖鸡,炒饭
    enum Rice implements Food {GAIJIAO_RICE, BRAISED_CHICKEN_RICE, FRIED_RICE}

    // 蔬菜类:西红柿,土豆,西兰花   
    enum Vegetables implements Food {TOMATO, POTATO, BROCCOLI}
}

对enum而言,实现接口,是实现子类化的唯一方式,上面的例子,每个 enum 都实现了 Food 接口,可以向上转型,所有的东西都是一种食物.

package com.crg.enuminterface;

import static com.crg.enuminterface.Food.*;

public class TypeOfFood {

    public static void main(String[] args) {
        Food food = Noodle.POUPO_NOODLE;
        food = Rice.GAIJIAO_RICE;
        food = Vegetables.BROCCOLI;
    }

}

如果要和一大堆类打交道,接口就不如enum好用了,例如一份包括上面食物Food的菜谱,使用 “枚举的枚举”,就很方便.

package com.crg.enuminterface;

import java.util.Random;

public enum Menu {
    NOODLE(Food.Noodle.class),
    RICE(Food.Rice.class),
    VEGETABLES(Food.Vegetables.class);
    private Food[] values;

    private Menu(Class kind){
        values = kind.getEnumConstants();
    }

    /**
     *  随机产生一个 values 的数组元素
     * @param values
     * @return
     */
    public static  T random(T[] values){
        Random random = new Random();
        return values[random.nextInt(values.length)];
    }

    /**
     *  随机点一个食物
     * @return
     */
    public Food randomSelect(){
        return random(values);
    }

    // 生成5份随机的菜单
    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            for (Menu menu : Menu.values()) {
                System.out.println(menu.randomSelect());
            }
            System.out.println("=================================");
        }
    }
}
POUPO_NOODLE
FRIED_RICE
BROCCOLI
=================================
POCKET_NOODLE
FRIED_RICE
POTATO
=================================
POUPO_NOODLE
GAIJIAO_RICE
BROCCOLI
=================================
POUPO_NOODLE
GAIJIAO_RICE
TOMATO
=================================
POCKET_NOODLE
FRIED_RICE
BROCCOLI
=================================

上面这个例子就是获取”枚举的枚举”的例子.
还可以重新组织代码,使代码有更清晰的结构,enum 内部嵌套另一个 enum:

package com.crg.enuminterface;

import java.util.Random;

public enum MenuEnum {
    NOODLE(Food.Noodle.class),
    RICE(Food.Rice.class),
    VEGETABLES(Food.Vegetables.class);
    private Food[] values;

    private MenuEnum(Class kind){
        values = kind.getEnumConstants();
    }

    /**
     *  随机产生一个 values 的数组元素
     * @param values
     * @return
     */
    public static  T random(T[] values){
        Random random = new Random();
        return values[random.nextInt(values.length)];
    }

    /**
     *  随机点一个食物
     * @return
     */
    public Food randomSelect(){
        return random(values);
    }

    interface Food {
        // 油泼面,裤袋面,扯面
        enum Noodle implements Food {POUPO_NOODLE, POCKET_NOODLE, PULLED_NOODLE}

        // 盖浇饭,黄焖鸡,炒饭
        enum Rice implements Food {GAIJIAO_RICE, BRAISED_CHICKEN_RICE, FRIED_RICE}

        // 蔬菜类:西红柿,土豆,西兰花   
        enum Vegetables implements Food {TOMATO, POTATO, BROCCOLI}
    }

    // 生成5份随机的菜单
    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            for (MenuEnum menu : MenuEnum.values()) {
                System.out.println(menu.randomSelect());
            }
            System.out.println("=================================");
        }
    }
}

七, EnumSet

set 集合不能向其中添加重复的元素, enum 实例也具有唯一性,这一点,两者很相似,但是 enum 不能实现实例的添加和删除,JAVA SE5 引入 EnumSet, EnumSet 的设计考虑到了速度的因素,因此用在判断一个 二进制位是否存在时,非常高效.
EnumSet 的元素必须来源 enum, 下面是一个学校 各个教室响铃的安装位置:

package com.crg.testenumset;

import java.util.EnumSet;
import static com.crg.testenumset.TestEnumSet.AlarmPoints.*;
public class TestEnumSet {
    enum AlarmPoints{classroom1, classroom2, classroom3, classroom4, classroom5, classroom6}
    public static void main(String[] args) {
        // 创建一个空的 EnumSet
        EnumSet points = EnumSet.noneOf(AlarmPoints.class);
        points.add(classroom1);
        System.out.println(points);

        // 添加多个元素
        points.addAll(EnumSet.of(classroom2, classroom3, classroom4));
        System.out.println(points);

        // 添加所有元素
        points = EnumSet.allOf(AlarmPoints.class);
        System.out.println(points);

        // 移除指定所有元素
        points.removeAll(EnumSet.of(classroom1, classroom2, classroom3));
        System.out.println(points);

        //添加单个元素
        points.add(classroom1);
        points.add(classroom2);
        System.out.println(points);

        // 保留指定范围内的所有元素
        points.retainAll(EnumSet.range(classroom1, classroom4));
        System.out.println(points);

        // 移除指定范围内的所有元素
        points.removeAll(EnumSet.range(classroom1, classroom4));
        System.out.println(points);

        // 产生一个新的EnumSet 包含所有元素,去处指定的元素
        points = EnumSet.complementOf(EnumSet.of(classroom1));
        System.out.println(points);

    }
}

EnumSet 方法的名字都相当直观,可以参考 API 点击查看API内容

EnumSet 的基础是 long,long是64位,当 元素超过64时,会再添加一个long.

八, 使用 EnumMap

EnumMap 是一种 特殊的 Map ,要求其中的键(key)必须来自 enum,EnumMap 内部用数组实现,因此 查找速度非常快,下面的实例使用enum实例调用put()方法,其他的使用和普通的Map类似.下面这个例子使用命令设计模式的用法使用 EnumMap

package com.crg.testenummap;

import java.util.EnumMap;
import java.util.Map;

interface Command {
    void action();
}

enum AlarmPoints {
    classroom1, classroom2, classroom3, classroom4, classroom5, classroom6
}

public class TestEnumMap {

    public static void main(String[] args) {
        EnumMap en = new EnumMap<>(AlarmPoints.class);
        en.put(AlarmPoints.classroom1, new Command() {

            @Override
            public void action() {
                System.out.println("this is classroom1");
            }
        });
        en.put(AlarmPoints.classroom1, new Command() {

            @Override
            public void action() {
                System.out.println("this is classroom1");
            }
        });
        en.put(AlarmPoints.classroom2, new Command() {

            @Override
            public void action() {
                System.out.println("this is classroom2");
            }
        });
        en.put(AlarmPoints.classroom3, new Command() {

            @Override
            public void action() {
                System.out.println("this is classroom3");
            }
        });
        for (Map.Entry e : en.entrySet()) {
            System.out.println(e.getKey());
            e.getValue().action();
        }

        // EnumMap 里没有这个key
        en.get(AlarmPoints.classroom4).action();
    }
}
classroom1
this is classroom1
classroom2
this is classroom2
classroom3
this is classroom3
Exception in thread "main" java.lang.NullPointerException
    at com.crg.testenummap.TestEnumMap.main(TestEnumMap.java:50)

九, 常量相关的方法

Java enum 有一个有趣的特性,允许程序员为 enum 编写方法,使每个enum实例拥有不同的行为,用 abstract 在enum里声明一个抽象方法,然后为每个enum 常量实现该抽象方法.

package com.crg.constant;

import java.text.DateFormat;
import java.util.Date;

public enum ConstantSpecificMethod {
    DATE_TIME{

        @Override
        String getInfo() {
            return DateFormat.getDateInstance().format(new Date());
        }},
    CLASSPATH{

        @Override
        String getInfo() {
            return System.getenv("CLASSPATH");
        }

        },
    VERSION{

        @Override
        String getInfo() {
            return System.getProperty("java.version");
        }

        };

    abstract String getInfo();
    public static void main(String[] args) {
        for (ConstantSpecificMethod constantSpecificMethod : ConstantSpecificMethod.values()) {
            System.out.println(constantSpecificMethod.getInfo());
        }
    }
}
Nov 7, 2016
null
1.8.0_92

在这个例子中,每个enum实例,在 调用 getInfo() 具有不同的行为,体现出了多态的行为.enum实例与类的相似之处仅限于此了.并不能把enum实例当做类来使用.
再看一个例子:

package com.crg.constant;

import java.util.EnumSet;


public class TestConstant {
    EnumSet enumSet = EnumSet.of(ConstantSpecificMethod.CLASSPATH);
    private void addConstant(ConstantSpecificMethod constantSpecificMethod){
        enumSet.add(constantSpecificMethod);
    }
    private void PrinterInfo(){
        for (ConstantSpecificMethod constantSpecificMethod : enumSet) {
            System.out.println(constantSpecificMethod.getInfo());
        }
    }
    public static void main(String[] args) {
        TestConstant testConstant = new TestConstant();
        testConstant.addConstant(ConstantSpecificMethod.VERSION);
        testConstant.addConstant(ConstantSpecificMethod.DATE_TIME);
        testConstant.addConstant(ConstantSpecificMethod.CLASSPATH);
        testConstant.addConstant(ConstantSpecificMethod.VERSION);
        testConstant.PrinterInfo();
    }

}
Nov 7, 2016
null
1.8.0_92

从上面这个例子可以看出 EnumSet 的一些特性:
因为是 set集合,元素不能重复,多次,add()同一个实例,EnumSet 会忽略掉;
EnumSet 保存的 enum实例的顺序和添加的先后没有关系,其顺序和 enum 实例声明的顺序一致.
enum 也可以覆盖常量一般的方法:

package com.crg.constant;

import java.text.DateFormat;
import java.util.Date;

public enum ConstantSpecificMethod {
    DATE_TIME{

        @Override
        String getInfo() {
            return DateFormat.getDateInstance().format(new Date());
        }
        @Override
        public void sayHello() {
            // TODO Auto-generated method stub
            super.sayHello();
            System.out.println("DATE_TIME say hello");
        }
    },
    CLASSPATH{

        @Override
        String getInfo() {
            return System.getenv("CLASSPATH");
        }

        },
    VERSION{

        @Override
        String getInfo() {
            return System.getProperty("java.version");
        }

        };
    /**
     *  抽象方法
     * @return
     */
    abstract String getInfo();

    /**
     *  一般方法
     */
    public void sayHello(){
        System.out.println("say hello");
    }

    public static void main(String[] args) {
        for (ConstantSpecificMethod constantSpecificMethod : ConstantSpecificMethod.values()) {
            System.out.println(constantSpecificMethod.getInfo());
        }
    }
}
package com.crg.constant;

import java.util.EnumSet;


public class TestConstant {
    EnumSet enumSet = EnumSet.of(ConstantSpecificMethod.CLASSPATH);
    private void addConstant(ConstantSpecificMethod constantSpecificMethod){
        enumSet.add(constantSpecificMethod);
    }
    private void PrinterInfo(){
        for (ConstantSpecificMethod constantSpecificMethod : enumSet) {
            System.out.println(constantSpecificMethod.getInfo());
            constantSpecificMethod.sayHello();
        }
    }
    public static void main(String[] args) {
        TestConstant testConstant = new TestConstant();
        testConstant.addConstant(ConstantSpecificMethod.VERSION);
        testConstant.addConstant(ConstantSpecificMethod.DATE_TIME);
        testConstant.addConstant(ConstantSpecificMethod.CLASSPATH);
        testConstant.addConstant(ConstantSpecificMethod.VERSION);
        testConstant.PrinterInfo();
    }

}
Nov 7, 2016
say hello
DATE_TIME say hello
null
say hello
1.8.0_92
say hello

十, 使用enum 的职责链

在职责链(Chain of Responsibility)的设计模式中,程序员以多种不同的方式解决同一个问题,然后把他们链接在一起.当一个请求到来时,它遍历这个链,直到链中的某个解决方案能够处理该请求.

下面的例子是利用enum常量的相关的方法,和责任链设计模式,实现的一个简单的责任链.
以邮局发送邮件为模型,邮局处理每个邮件使用通用模式,不断的尝试处理邮件,直到邮件被确认为 是死邮件;每一次尝试可以看做一个策略,而完整的处理方式,就是一个责任链.

package com.crg.mail;

import java.util.Iterator;
import java.util.Random;

/**
 *  邮件对象
 * @author aloe
 *
 */
public class Mail {
    enum GeneralDelivery {YES, NO1, NO2, NO3, NO4, NO5}
    enum Scannability {UNSCANNABLE, YES1, YES2, YES3, YES4}
    enum Readability {ILLEGIBLE, YES1, YES2, YES3, YES4}
    enum Address {INCORRECT, OK1, OK2, OK3, OK4, OK5, OK6}
    enum ReturnAddress {MISSING, OK1, OK2, OK3, OK4, OK5}

    GeneralDelivery generalDelivery;
    Scannability scannability;
    Readability readability;
    Address address;
    ReturnAddress returnAddress;

    static long counter = 0;
    long id = counter++;

    public String toString(){
        return "Mail " + id;
    }

    public String details(){
        return toString() + " |GeneralDelivery: " + generalDelivery
                + " |Scannability: " + scannability
                + " |Readability: " + readability
                + " |Address: " + address
                + " |ReturnAddress: " + returnAddress;
    }

    /**
     * generate test Mail
     * @return
     */
    public static Mail randomMail(){
        Mail mail = new Mail();
        mail.generalDelivery = randomEnum(GeneralDelivery.class);
        mail.scannability = randomEnum(Scannability.class);
        mail.readability = randomEnum(Readability.class);
        mail.address = randomEnum(Address.class);
        mail.returnAddress = randomEnum(ReturnAddress.class);
        return mail;
    }

    /**
     *  随机产生一个 Enum 的实例
     * @param ls
     * @return
     */
    public static > T randomEnum(Class ls){
        return random(ls.getEnumConstants());
    }

    /**
     *  随机产生一个 values 的数组元素
     * @param values
     * @return
     */
    public static  T random(T[] values){
        Random random = new Random();
        return values[random.nextInt(values.length)];
    }

    public static Iterable generator(final int count){
        return new Iterable() {
            int n = count;
            @Override
            public Iterator iterator() {
                // TODO Auto-generated method stub
                return new Iterator() {

                    @Override
                    public boolean hasNext() {
                        // TODO Auto-generated method stub
                        return n-- > 0;
                    }

                    @Override
                    public Mail next() {
                        // TODO Auto-generated method stub
                        return randomMail();
                    }
                    @Override
                    public void remove() {
                        // TODO Auto-generated method stub
                        throw new UnsupportedOperationException();
                    }
                };
            }
        };
    }
}
package com.crg.mail;

public class PostOffice {
    enum MailHandler {
        GENERAL_DELIVERY{

            @Override
            boolean handle(Mail mail) {
                switch (mail.generalDelivery) {
                case YES:
                    System.out.println("using general delivery for " + mail);
                    return true;

                default:
                    return false;
                }
            }},

        MACHINE_SCAN {

            @Override
            boolean handle(Mail mail) {
                switch (mail.scannability) {
                case UNSCANNABLE:

                    return false;

                default:
                    switch (mail.address) {
                    case INCORRECT:
                        return false;
                    default:
                        System.out.println("delivering " + mail + " automatically");
                        return true;
                    }
                }
            }
            },
        VISUAL_INSPECTION {

            @Override
            boolean handle(Mail mail) {
                switch (mail.readability) {
                case ILLEGIBLE:
                    return false;

                default:
                    switch (mail.address) {
                    case INCORRECT:
                        return false;
                    default:
                        System.out.println("delivering " + mail + " normally");
                        return true;
                    }
                }
            }

            },

        RETURN_TO_SENDER {

            @Override
            boolean handle(Mail mail) {
                switch (mail.returnAddress) {
                case MISSING:
                    return false;
                default:
                    System.out.println("returning " + mail + " to sender");
                    return true;
                }
            }

            };

        abstract boolean handle(Mail mail);
    }
    static void handleMail(Mail mail){
        for (MailHandler mailHandler : MailHandler.values()) {
            if (!mailHandler.handle(mail)) {
                System.out.println(mail + " is a dead letter");
                return;
            }

        }
    }
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        for (Mail mail : Mail.generator(10)) {
            System.out.println(mail.details());
            handleMail(mail);
            System.out.println("==================================================");
        }
    }

}
Mail 0 |GeneralDelivery: YES |Scannability: YES1 |Readability: YES4 |Address: OK6 |ReturnAddress: OK4
using general delivery for Mail 0
delivering Mail 0 automatically
delivering Mail 0 normally
returning Mail 0 to sender
==================================================
Mail 1 |GeneralDelivery: NO2 |Scannability: YES3 |Readability: YES3 |Address: OK3 |ReturnAddress: MISSING
Mail 1 is a dead letter
==================================================
Mail 2 |GeneralDelivery: NO5 |Scannability: YES4 |Readability: ILLEGIBLE |Address: INCORRECT |ReturnAddress: OK3
Mail 2 is a dead letter
==================================================
Mail 3 |GeneralDelivery: NO2 |Scannability: YES1 |Readability: YES2 |Address: INCORRECT |ReturnAddress: OK5
Mail 3 is a dead letter
==================================================
Mail 4 |GeneralDelivery: NO2 |Scannability: YES2 |Readability: YES1 |Address: OK1 |ReturnAddress: OK3
Mail 4 is a dead letter
==================================================
Mail 5 |GeneralDelivery: NO2 |Scannability: YES1 |Readability: YES2 |Address: OK2 |ReturnAddress: OK2
Mail 5 is a dead letter
==================================================
Mail 6 |GeneralDelivery: NO4 |Scannability: YES1 |Readability: YES2 |Address: OK1 |ReturnAddress: OK1
Mail 6 is a dead letter
==================================================
Mail 7 |GeneralDelivery: NO2 |Scannability: YES4 |Readability: YES1 |Address: OK1 |ReturnAddress: OK1
Mail 7 is a dead letter
==================================================
Mail 8 |GeneralDelivery: NO5 |Scannability: YES3 |Readability: YES1 |Address: OK3 |ReturnAddress: OK3
Mail 8 is a dead letter
==================================================
Mail 9 |GeneralDelivery: NO3 |Scannability: YES4 |Readability: ILLEGIBLE |Address: OK4 |ReturnAddress: OK5
Mail 9 is a dead letter
==================================================

责任链由 enum MailHandler 实现,enum 的定义次序决定了各个解决策略在应用时的次序.对每一封邮件都按该次序尝试每一个解决策略,如果有一个策略,失败,该邮件被判定为死件.

==============================================================================
上面的代码下载地址,请点击打开:

你可能感兴趣的:(javase)