Java 枚举

文章目录

    • 一、enum基本特性
    • 二、enum 中加个方法
    • 三、switch case & enum
    • 四、values()解密
    • 五、实现,而非继承
    • 六、randomly pick
    • 七、使用接口组织枚举
    • 八、EnumSet
    • 九、EnumMap&命令模式
    • 十一、常量方法 (constant-specific methods)与多路分发(multiple dispatching)
    • 十二、enum的职责链
    • more will be shown!

一、enum基本特性

先来个DEMO:

public enum Fruit {
  APPLE, PEAR, PEACH;
}

class EnumTest{
  public static void main(String[] args) {
      /*values()*/
      Arrays.stream(Fruit.values()).forEach(e -> {
          System.out.println(e);
          /* compareTo*/
          System.out.println(e.compareTo(Fruit.PEAR));
          System.out.println(e == Fruit.PEAR);
          /* getDeclaringClass VS  getClass()*/
          System.out.println(e.getDeclaringClass());
          /*name()场景: This method is designed primarily for use in specialized situations 
          where correctness depends on getting the exact name, which will not vary from release to release.
          API DOC翻译即为:name()只用在当对枚举的名字精确的要求下的场合,枚举的name()是不变的,但是toString()可能被
          override
          */
          System.out.println(e.name());
          System.out.println("-------");
      });
  }
}

再看:

public enum Fruit extends Food{
    APPLE, PEAR, PEACH;
}

简单API的使用不需赘言,只说几个关键的:

  1. 枚举的命名规范首字母大写的驼峰
  2. 枚举是不能继承的
  3. values() 返回枚举数组,而且是按实例声明的顺序
  4. 编译器会为所有enum生成相关类,这个类继承自 java.lang.enum。看一下它的定义:至少得到一个信息:所有枚举都是可比较的、可序列化的
public abstract class Enum>
        implements Comparable, Serializable {
// .....
}

二、enum 中加个方法

我们使用枚举时多半希望每个枚举实例能够返回对自身的描述,而不仅仅默认返回toString()实现,toString()只能返回枚举的名字而已。此时,我们可以添加对枚举的描述信息,并提供有参构造方法以及get()方法

public enum Color {
    RED("i love red"),
    BLACK("i hate black"),
    ;
    private final String desc;

    Color(String desc) { // 即使是包内可见,编译器还有会把它变成私有构造器
        this.desc = desc;
    }

    public String getDesc() {
        return desc;
    }
}

三、switch case & enum

在switch case 中使用enum,此时enum就相当于一个小型的状态机

public enum Sign {

    GREEN,RED,YELLOW;
}

class TrafficLight{

    Sign sign = Sign.RED;
    public void f() {
        switch (sign) {
            case RED:   // 注意:::这里的状态是流转的! 确实像个小型的状态机
                sign = Sign.GREEN;
                break;
            case GREEN:
                sign = Sign.YELLOW;
                break;
            case YELLOW:
                sign = Sign.RED;
                break;
        }

    }

    @Override
    public String toString() {
        return " light of Color is " + sign;
    }

    public static void main(String[] args) {
        TrafficLight trafficLight = new TrafficLight();
        IntStream.range(0, 10).forEach(e -> {
            trafficLight.f();
            System.out.println(trafficLight.sign);
        });
    }
}

四、values()解密

我们看到,其实java.lang.Enum 类中并没有定义values()这个方法,那它来自何处呢?
values()是编译器自动添加的静态方法,同样valueOf() 也是如此
一段简单的enum定义是这样:

public enum  Explore {
    HERE, THERE,
    ;
}

实际编译完了以后是这样:

final class Explore extends java.lang.Enum{
    public static final Explore HERE;
    public static final Explore THERE;
    public static final Explore[] values();
    public static Explore valueOf(java.lang.String);
    static {};
}

另外,我们看到:

  1. Enum编译后,是有final修饰的,所以自定义枚举不能被继承(不包括java.lang.Enum)。其实,正是因为编译器会将自定义的枚举设定为继承自java.lang.Enum,在Java单继承体系下,才不能继承别的类了。
  2. 所以的枚举实例都是 用publis static final 来修饰的,所以枚举实例是静态、不可变的。
  3. 由于泛型擦除Explore这个枚举的父类只是Enum,而不是Enum< Explore>!
  4. 既然Explore枚举的父类是Enum,那Explore实例肯定可以用Enum接收(强转)。见下例
class Reflection{
    public static void main(String[] args) throws IOException {
        Enum here = Explore.HERE; // 将 Explore 枚举的一个实例  强转为 Enum
//        here.values();  values()方法将不可用
//        不过 ,可以通过 下面这种方式 getEnumConstants 获取 枚举实例,这是Class类的API!!
//        所以,即使对象不是 枚举也有这个API,但是返回的结果可能为null而已!
        Arrays.stream(here.getClass().getEnumConstants())
                .forEach(System.out::println);
    }
}

五、实现,而非继承

自定义枚举时不能继承除java.lang.Enum 以外的类,但是可以实现不同的接口。这样,所有的枚举实例都能作为接口的实现类对象传参,有时会显得比较滑稽。囧。。。

public enum Drink implements Generator {
    PEPSI, COKECOLA,
    ;
    private Random random = new Random();

    @Override
    public Drink next() {
        return Drink.values()[random.nextInt(Drink.values().length)];
    }
}

class DrinkTest{
    public static void main(String[] args) {
        Drink drink = Drink.PEPSI;
        // 这里比较搞笑,传入的 明明是 pepsi,但是可以产生多种 实例
        IntStream.range(0, 10).forEach(e -> printNext(drink)); 
    }

    public static  void printNext(Generator generator) {
        System.out.println(generator.next());
    }
}

六、randomly pick

这里来写一个小小的工具类来陶冶一下情操:

public final class Enums {
    private Enums(){}

    private static final Random rand = new Random();
    /*  泛型 >  表示出现在这个泛型方法中的 T 可能为 enum 实例,连其子类都不可以(当然也没
    * 子类。
    *
    * 这个方法作用: 传入一个枚举类Class对象,随机获取一个枚举类的实例
    * */
    private static > T random(Class clazz) {
        return random(clazz.getEnumConstants());
    }

    private static  T random(T[] enumConstants) {
        return enumConstants[rand.nextInt(enumConstants.length)];
    }

    public static void main(String[] args) {
        IntStream.range(0, 5).forEach(e -> System.out.println(random(Color.class)));
    }

}

七、使用接口组织枚举

为啥使用接口来组织枚举?
答曰:因 不能从enum继承子类(注意是enum,不是java.lang.Enum),但是有时我们希望能扩展原enum中的元素,或者将一个enum中元素进行分组。

  1. 在一个接口内部,创建多个实现这个接口的枚举,即可分组
  2. 实现接口是枚举子类化的唯一手段

形式1:

public interface Food {

    enum Vegetable implements Food{
        LETTUCE, RUSHROOM
    }

    enum Meat implements Food{
        PORK, STEAK
    }

    enum MainFood implements Food{
        RICE, BREAD
    }
}

class TypeOfFood{
    public static void main(String[] args) {
        Food food = Food.Vegetable.LETTUCE;
        food = Food.Meat.PORK;
    }
}

从OOP的角度看:所有的内部枚举实例都是Food,因此可以转型为Food。

当要分组的类型太多时,接口就不如枚举好用了,我们构造了类似枚举的枚举

形式2:

public enum GoodFood {

    VEGETABLE(Food.Vegetable.class),
    MAINFOOD(Food.MainFood.class),
    ;
    // 尤其要注意的是:每个GoodFood实例都有着自己的Food[] values 域!!!
    // 虽然 GoodFood的枚举实例写在 GoodFood内部,但最好把它当作 一般实例来看待
    private Food[] values;

    GoodFood(Class clazz) {
        // 只要实例化GoodFood这个枚举,就会调用这个有参构造,打印出“---”;
        // 而且是 有几个 GoodFood枚举实例就调用几次
        System.out.println("---");
        values = clazz.getEnumConstants();
    }

    public Food randomSelection() {
        return Enums.random(values);
    }
    
    
}

class GoodFoodTest {
    public static void main(String[] args) {
        IntStream.range(0, 5).forEach(e -> {
            Arrays.stream(GoodFood.values()).forEach(u -> {
                System.out.println(u.randomSelection());
            });
            System.out.println("++++++++");
        });
    }
}

形式3:

public enum  GoodFood2 {

    VEGETABLE(Food.Vegetable.class),
    MAINFOOD(Food.MainFood.class),
    ;

    private Food[] values;

    GoodFood2(Class clazz) {
        values = clazz.getEnumConstants();
    }

    public Food randomSelection() {
        return Enums.random(values);
    }

    public interface Food {

        enum Vegetable implements Food {
            LETTUCE, RUSHROOM
        }

        enum Meat implements Food {
            PORK, STEAK
        }

        enum MainFood implements Food {
            RICE, BREAD
        }
    }
}

class GoodFood2Test{
    public static void main(String[] args) {
        Arrays.stream(GoodFood2.values()).forEach(e -> {
            System.out.println(e.randomSelection());
        });
    }
}

第三种形式和第二种形式只是代码组织上不同,但是显得结构更清晰一点。

八、EnumSet

Set集合中元素是不重复的,从这一点上看,enum有异曲同工之妙。JDK引入EnumSet,是为了替代传统的基于int 来表征“开关量”(有或没有)的做法,而是使用 bit vectors(位向量)来表征。所以EnumSet的效率是很高的(具体到什么方法???)

public class EnumSets {

    enum AlarmPoints {
        STAIR1, STAIR2, LOBBY, OFFICE1, OFFICE2, OFFICE3,
        OFFICE4, BATHROOM, UTILITY, KITCHEN
    }
    public static void main(String[] args) {
        EnumSet alarmPoints = EnumSet.noneOf(AlarmPoints.class);
        alarmPoints.add(AlarmPoints.BATHROOM);
        alarmPoints.addAll(EnumSet.of(AlarmPoints.LOBBY, AlarmPoints.UTILITY));
        alarmPoints.addAll(EnumSet.range(AlarmPoints.OFFICE1, AlarmPoints.OFFICE4));
        EnumSet leftPoints = EnumSet.complementOf(alarmPoints); // 补集
        System.out.println(alarmPoints);
        System.out.println(leftPoints);
    }
}

注意一下:

  1. EnumSet是线程不安全的类,并发读写可能导致状态异常。可使用并发容器工具包装一下:

Set s = Collections.synchronizedSet(EnumSet.noneOf(MyEnum.class));

2. 多个enum 实例加入到EnumSet中, 总是按定义时的先后顺序来读写的。这一点要十分小心

九、EnumMap&命令模式

特点:

  1. EnumSet类似,enum定义时的次序决定了其在EnumMap中的顺序。
  2. EnumMap总是以enum实例为KEY
  3. EnumMap内部其实是数组

场景:
利用enum的分发特性,结合命令模式,可以有十分简洁的 结构。


/**
 * 一个典型的  命令模式 ,command pattern。 
 * 命令模式:首先需要一个只有一个单一方法的接口,然后从该接口实现具有各自不同的行为的多个子类
 */
public class EnumMaps {

    enum AlarmPoints {
        STAIR1, STAIR2, LOBBY, OFFICE1, OFFICE2, OFFICE3,
        OFFICE4, BATHROOM, UTILITY, KITCHEN
    }
    public static void main(String[] args) {
        EnumMap em = new EnumMap<>(AlarmPoints.class);
        em.put(AlarmPoints.KITCHEN, () -> System.out.println("fire in kitchen!"));
        em.put(AlarmPoints.BATHROOM, () -> System.out.println("fire in bathroom !"));
        em.forEach((u, v) -> {
            System.out.println(u);
            v.action();
        });
    }


}

@FunctionalInterface
interface Command{
    void action();
}

十一、常量方法 (constant-specific methods)与多路分发(multiple dispatching)

enum有个很有意思的特性:
enum内部每个实例都可以有自己的方法(行为)。同时呢,每个实例其实又是常量类型
再联系到EnumMap:::map的值可变,但是键(enum实例)中的方法却在编译期就固定了

看个例子:

public enum  ConstantSpecificMethod {

    DATE_TIME {
        String getInfo() {
            return DateFormat.getDateInstance().format(new Date());
        }
    },

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

    abstract String getInfo();
}

class ConstantSpecificMethodTest {
    public static void main(String[] args) {
        // 这里的  e(枚举实例)像是被当做 ConstantSpecificMethod  这个父类来使用了(多态)
        Arrays.stream(ConstantSpecificMethod.values()).forEach(e -> System.out.println(e.getInfo()));
    }
}

从OOP思想看,不同的行为与不同的类关联(意思是:同一个类的实例应该具有相同的行为,假如行为不同,就应该定义为不同的类),但是枚举实例(常量)却有着自己独有的方法,好像每个枚举实例就是一个独特的类一样。不过呢,也只是像而已,enum实例不是一个真正的类型,想想编译后的enum就会发现每个enum实例都是一个 枚举类的 static final 实例!

下面来一发小demo来说明枚举实例内定义方法的场景:
自助洗车时,机器上会有选择菜单让客户自行选择洗车的具体内容,每项内容对应一个动作。这个时候我们可以利用枚举来建模:每个枚举实例相当于一个服务具体内容,内容的动作抽象成一个抽象类,由各个内容自行定义。

public class CarWash {

 public enum Cycle{
     UNDERDBODY{
         @Override
         void action() {
             Cycle.print(" spraying the underbody!");
         }
     },
     WHEELWASH {
         void action() { print("Washing the wheels"); }
     },
     PREWASH {
         void action() { print("Loosening the dirt"); }
     },
     BASIC {
         void action() { print("The basic wash"); }
     },
     HOTWAX {
         void action() { print("Applying hot wax"); }
     },
     RINSE {
         void action() { print("Rinsing"); }
     },
     BLOWDRY {
         void action() { print("Blowing dry"); }
     };

     /*必须是静态方法,应该enum实例编译后实际是 static final实例*/
     private static void print(String s) {
         System.out.println(s);
     }

     abstract void action();
 }

 EnumSet cycles = EnumSet.of(Cycle.UNDERDBODY, Cycle.RINSE);

 public void add(Cycle cycle) {
     cycles.add(cycle);
 }

 public void wash() {
     cycles.forEach(Cycle::action);
 }
}

class CarWashTest{
 public static void main(String[] args) {
     /*这种表达说明了 Cycle 这个枚举就像是 内部类*/
     CarWash.Cycle cycle = CarWash.Cycle.BASIC;

     CarWash carWash = new CarWash();
     carWash.add(CarWash.Cycle.BASIC);
     carWash.add(CarWash.Cycle.PREWASH);
     carWash.wash();
 }
}

此外呢,constant-specific methods (指上例中的各个枚举常量独有的方法)
可以被override掉

public enum OverrideConstantSpecific {
    NUT, BOLT,
    WASHER {
        void f() {
            /*这里会 override f()*/
            System.out.println(("Overridden method"));
        }
    };

    void f() {
        System.out.println(("default behavior"));
    }
}

class OverrideConstantSpecificTest{
    public static void main(String[] args) {
        NUT.f(); // default behavior
        WASHER.f(); // Overridden method
    }
}

十二、enum的职责链

职责链:
chain of responsibility:直白点说,一个问题,有多个方法处理它,那么把这多种方法封装起来,像一个链一样。使用时,用链上的方法(也可以叫做 策略)遍历尝试解决,若能解决,跳出链,若不能,那遍历完所有“策略”,接下来该干啥干啥了。

假设场景:
邮局处理邮件时会采用各种手段逐个尝试能不能投递成功,每种处理手段可以封装成一个职责链;
邮件本身有很多特征,使用GeneralDelivery 、Scannability ,etc来描述
为了简化模型,每个特征量都只描述为“YES”、“NO”等

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() +
                ", General Delivery: " + generalDelivery +
                ", Address Scanability: " + scannability +
                ", Address Readability: " + readability +
                ", Address Address: " + address +
                ", Return address: " + returnAddress;
    }
    // Generate test Mail:
    public static Mail randomMail() {
        Mail m = new Mail();
        m.generalDelivery= Enums.random(GeneralDelivery.class);
        m.scannability = Enums.random(Scannability.class);
        m.readability = Enums.random(Readability.class);
        m.address = Enums.random(Address.class);
        m.returnAddress = Enums.random(ReturnAddress.class);
        return m;
    }

    public static Iterable generator(final int count) {
        return new Iterable() {
            int n = count;

            @Override
            public Iterator iterator() {
                return new Iterator() {

                    @Override
                    public boolean hasNext() {
                        return n-- > 0;
                    }

                    @Override
                    public Mail next() {
                        return randomMail();
                    }
                };
            }
        };


    }
}


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:
                        System.out.println("  scanning " + mail);
                        return true;
                }
            }
        },

        VISUAL_INSPECTION {
            @Override
            boolean handle(Mail mail) {
                switch (mail.readability) {
                    case ILLEGIBLE:
                        return false;
                    default:
                        System.out.println(" visual inspecting " + mail);
                        return true;
                }
            }
        };
        abstract boolean handle(Mail mail);

    }

    public static void main(String[] args) {
        Mail.generator(10).forEach(e -> {
            Arrays.stream(MailHandler.values()).forEach(u -> {
                u.handle(e);
            });
        });
    }

}

more will be shown!

你可能感兴趣的:(Java基础)