Thinking in java Chapter19 枚举类型

落爺英雄遲暮 提交于 2019-12-30 16:45:47

enum 将一组具名的值的有限集合创建为新的类型

本章深入enum, 泛型 和 反射,及设计模式

1 基本enum特性

package enumerated;

enum Shrubbery{GROUND,CRAWLING,HANGING} // 灌木  地面 爬行 悬挂 编译器生成Shrubbery类,继承自java.lang.Enum

public class EnumClass {
    public static void main(String[] args) {
        for (Shrubbery s: Shrubbery.values()){// enum的values,可以遍历enum示例。返回enum实例的数组,且顺序为声明顺序。
            System.out.println( s + " ordinal: " + s.ordinal());// ordinal返回int,从0开始
            System.out.print(s.compareTo(Shrubbery.CRAWLING) + " ");
            System.out.print(s.equals(Shrubbery.CRAWLING) + " ");
            System.out.println(s == Shrubbery.CRAWLING);
            System.out.println(s.getDeclaringClass());
            System.out.println(s.name());//实例名称,与toString效果类似
            System.out.println("-------------");;
        }
        for (String s : "GROUND,CRAWLING,HANGING".split(",")){
            Shrubbery shrubbery = Enum.valueOf(Shrubbery.class,s); // 若不存在,则抛出IllegalArgumentException
            System.out.println(shrubbery);
        }
    }
}
/*
GROUND ordinal: 0
-1 false false
class enumerated.Shrubbery
GROUND
-------------
CRAWLING ordinal: 1
0 true true
class enumerated.Shrubbery
CRAWLING
-------------
HANGING ordinal: 2
1 false false
class enumerated.Shrubbery
HANGING
-------------
GROUND
CRAWLING
HANGING
 */

1.1将静态导入用于enum

是静态导入,还是显示修饰实例,具体情况具体分析

package enumerated;

import static enumerated.Spiciness.*;

public class Burrito {
    Spiciness degree;
    public Burrito(Spiciness degree){this.degree = degree;}
    public String toString(){return "Burrito is " + degree;}

    public static void main(String[] args) {
        System.out.println(new Burrito(NOT));
        System.out.println(new Burrito(MEDIUM));
        System.out.println(new Burrito(HOT));
    }
}

2 向enum中添加新方法

enum 是个常规类,但不能继承

可以添加方法,甚至有main方法

package enumerated;

public enum OzWitch {
    WEST("Miss Gulch, aka the Wicked Witch of the West"),
    NORTH("Glinda, the Good Witch of the North"),
    EAST("Wicked Witch of the East, wearer of the Ruby " +
            "Slippers, crushed by Dorothy’s house"),
    SOUTH("Good by inference, but missing"); //分号
    private String description;

    OzWitch(String description){this.description = description;}

    public String getDescription(){return description;}

    public static void main(String[] args) {
        for (OzWitch witch: OzWitch.values())
            System.out.println(witch + ": " + witch.getDescription());
    }
}

2.1 覆盖enum的方法

覆盖toSting

package enumerated;

public enum SpaceShip {
    SCOUT, CARGO, TRANSPORT, CRUISER, BATTLESHIP, MOTHERSHIP;//侦察、货运、运输、巡洋舰、战列舰、母舰;

    public String toString(){
        String id = name();
        String lower = id.substring(1).toLowerCase();
        return id.charAt(0) + lower;
    }

    public static void main(String[] args) {
        for (SpaceShip s: values()){
            System.out.println(s);
        }
    }
}
/*
Scout
Cargo
Transport
Cruiser
Battleship
Mothership
 */

3 switch语句中的enum

枚举实例天生具备整数值的次序

package enumerated;

enum Signal {GREEN, YELLOW, RED}

public class TrafficLight {
    Signal color = Signal.RED;

    public void change() {
        switch (color) {
            case RED:
                color = Signal.GREEN;
                break;
            case GREEN:
                color = Signal.YELLOW;
                break;
            case YELLOW:
                color = Signal.RED;
                break;
        }
    }
    public String toString(){
        return "The traffic light is " + color;
    }

    public static void main(String[] args) {
        TrafficLight t = new TrafficLight();
        for (int i = 0; i < 7; i++){
            System.out.println(t);
            t.change();
        }
    }
}
/*
The traffic light is RED
The traffic light is GREEN
The traffic light is YELLOW
The traffic light is RED
The traffic light is GREEN
The traffic light is YELLOW
The traffic light is RED
 */
 switch 没有default 语句,需要自己确保是否覆盖所有分支
 但若有return语句,则编译器要求有default语句

4 values的神秘之处

values()是编译器添加的static方法

编译器还添加了valueof方法,但与Enum类的values需要两个参数不一样,只需要一个参数。

编译器将enum标记为final,故无法继承

还有static子句

package enumerated;

import net.mindview.util.OSExecute;

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

enum Explore{HERE,THERE}

public class Reflection {
    public static Set<String> analyze(Class<?> enumClass){
        System.out.println("----- Analyzing " + enumClass + " ----- ");
        System.out.print("Interfaces:");
        for (Type t :enumClass.getGenericInterfaces()) //接口
            System.out.println(t);
        System.out.println("Base: " + enumClass.getSuperclass()); // 超类
        System.out.println("Methods: ");
        Set<String> methods = new TreeSet<String>();// 方法
        for (Method m: enumClass.getMethods())
            methods.add(m.getName());
        System.out.println(methods);
        return methods;
    }

    static String classPath = "/Users/erin/JavaProject/thinking_in_java_example/target/classes/enumerated/Explore.class";

    public static void main(String[] args) {
        Set<String> exploreMethods = analyze((Explore.class));
        Set<String> enumMethods = analyze(Enum.class);
        System.out.println("Explore.containsAll(Enum)? " + exploreMethods.containsAll(enumMethods));
        System.out.println("Explore.removeAll(Enum): ");
        exploreMethods.removeAll(enumMethods);// 剩下values
        System.out.println(exploreMethods);
        OSExecute.command("javap "+ classPath);
    }
}
/*
----- Analyzing class enumerated.Explore ----- 
Interfaces:
Base: class java.lang.Enum
Methods: 
[compareTo, equals, getClass, getDeclaringClass, hashCode, name, notify, notifyAll, ordinal, toString, valueOf, values, wait]
----- Analyzing class java.lang.Enum ----- 
Interfaces:
java.lang.Comparable<E>
interface java.io.Serializable
Base: class java.lang.Object
Methods: 
[compareTo, equals, getClass, getDeclaringClass, hashCode, name, notify, notifyAll, ordinal, toString, valueOf, wait]
Explore.containsAll(Enum)? true
Explore.removeAll(Enum): 
[values]
Compiled from "Reflection.java"
**final** class enumerated.Explore extends java.lang.Enum<enumerated.Explore> {
  public static final enumerated.Explore HERE;
  public static final enumerated.Explore THERE;
  public static enumerated.Explore[] values();
  public static enumerated.Explore valueOf(java.lang.String);
  static {};
}
 */

通过向上转型,values是编译器插入到enum定义的static方法,故不能访问。
但可以通过class的getEnumConstants方法,获取所有enum的实例。

package enumerated;

enum Search{HITHER,YON}

public class UpcastEnum {
    public static void main(String[] args){
        Search[] vals = Search.values();
        Enum e = Search.HITHER; // 向上转型,
        //e.values();values是编译器插入到enum定义的static方法,故不能访问。
        for (Enum en: e.getClass().getEnumConstants())
            System.out.println(en);
    }
}
/*
HITHER
YON
 */

Class上的方法,对不是枚举的类调用

package enumerated;

public class NonEnum {
    public static void main(String[] args) {
        Class<Integer> intClass = Integer.class;
        try {
            for (Object en : intClass.getEnumConstants())
                System.out.println(en);
        } catch (Exception e) {
            System.out.println(e);
        }
    }
}
/*
java.lang.NullPointerException
 */

5 实现而非继承

必须有一个enum实例才能调用方法

package enumerated;

import net.mindview.util.Generator;
import typeinfo.pets.Pet;

import java.util.Random;

//enum NotPossible extends Pet; //enum 都继承自java.lang.Enum类。java不支持多重继承,所以不能继承其他类

enum CartoonCharacter implements Generator<CartoonCharacter> {
    SLAPPY, SPANKY, PUNCHY, SILLY, BOUNCY, NUTTY, BOB;
    private Random rand = new Random(47);

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

public class EnumInmplementation {
    public static <T> void printNext(Generator<T> rg) {
        System.out.print(rg.next() + ", ");
    }

    public static void main(String[] args) {
        // 选择一个实例
        CartoonCharacter cc = CartoonCharacter.BOB;
        for (int i = 0; i < 10; i++)
            printNext(cc);
    }
}
/*
BOB, PUNCHY, BOB, SPANKY, NUTTY, PUNCHY, SLAPPY, NUTTY, NUTTY, SLAPPY, 
 */

6 随机选取

可以利用泛型,使工作一般化。

随机提取枚举实例。

package enumerated;

import java.util.Random;

public class Enums {
    private static Random rand = new Random(47);
    public static <T extends Enum<T>> T random(Class<T> ec){ // <T extends Enum<T>>  T是enum实例
        return random(ec.getEnumConstants()); // 获得枚举数组
    }

    public static <T> T random(T[] values){ // 重载方法
        return values[rand.nextInt(values.length)];
    }
}

以上工具的测试

package enumerated;

enum Activity{SITTING, LYING, STANDING, HOPPING,
    RUNNING, DODGING, JUMPING, FALLING, FLYING}

public class RandomTest {
    public static void main(String[] args) {
        for (int i = 0; i < 20; i++)
            System.out.print(Enums.random(Activity.class) + " ");
    }
}
/*
STANDING FLYING RUNNING STANDING RUNNING STANDING LYING DODGING SITTING RUNNING HOPPING HOPPING HOPPING RUNNING STANDING LYING FALLING RUNNING FLYING LYING
 */

7 使用接口组织枚举

有时,我们需要扩展原enum中的元素,有时,我们想使用子类 将enum的元素进行分组。

通过接口内部,进行元素分组,如例:
对enmu而言,实现接口是其子类化的唯一办法,嵌在Food中的每个enum都实现类Food接口

package enumerated.menu;

public interface Food {
    enum Appetizer implements Food{ // 开胃菜
        SALAD, SOUP, SPRING_ROLLS; // 春卷
    }
    enum MainCourse implements Food{
        LASAGNE, BURRITO, PAD_THAI,
        LENTILS, HUMMOUS, VINDALOO;//千层面,墨西哥卷饼,泰国面,小扁豆,HUMMOUS和vindaloo;
    }

    enum Dessert implements Food{
        TIRAMISU, GELATO, BLACK_FOREST_CAKE,
        FRUIT, CREME_CARAMEL;//提拉米苏,冰淇淋,黑森林蛋糕,水果,奶油焦糖;
    }
    enum Coffee implements Food{
        BLACK_COFFEE, DECAF_COFFEE, ESPRESSO,
        LATTE, CAPPUCCINO, TEA, HERB_TEA;//黑咖啡、低卡咖啡、浓缩咖啡、拿铁、卡布奇诺、茶、草茶;
    }
}

enum 类型实现了 Food 接口,那么我们就可以将其实例向上转型为 Food,所以 所有东西都是 Food。

package enumerated.menu;

import static enumerated.menu.Food.*;

public class TypeOfFood {//向上转型为Food
    public static void main(String[] args) {
        Food food = Appetizer.SALAD;
        food = MainCourse.LASAGNE;
        food = Dessert.GELATO;
        food = Coffee.CAPPUCCINO;
    }
}

创建“枚举的枚举”
可以创建一个新的 enum,然后 其实例包装 Food 中的每一个 enum 类

package enumerated.menu;

import enumerated.Enums;

public enum  Course {
    APPETIZER(Food.Appetizer.class),
    MAINCOURSE(Food.MainCourse.class),
    DESSERT(Food.Dessert.class),
    COFFEE(Food.Coffee.class);
    private Food[] values;
    private Course(Class<? extends Food> kind){
        values = kind.getEnumConstants(); // 获得某个 Food 子类的所有 enum 实例
    }
    public Food randomSelection(){
        return Enums.random(values);
    }
}

对以上菜单进行测试,遍历每个course实例,获得 枚举的枚举 的值

package enumerated.menu;

public class Meal {
    public static void main(String[] args) {
        for (int i = 0; i < 5; i++){
            for (Course course: Course.values()){ //遍历每个course实例,获得 枚举的枚举 的值
                Food food = course.randomSelection();
                System.out.println(food);
            }
            System.out.println("***");
        }
    }
}

更简洁的管理枚举的办法: enum 嵌套

package enumerated;

enum SecurityCategory{
    STOCK(Security.Stock.class),
    BOND(Security.Bond.class);//债券
    Security[] values;
    SecurityCategory(Class<? extends Security> kind){
        values = kind.getEnumConstants();

    }
    interface Security{ //Security 接口的作用是将其所包含的 enum 组合成一个公共类型
        enum Stock implements Security{
            SHORT, LONG, MARGIN
        }
        enum Bond implements Security{
            MUNICIPAL, JUNK //市政、垃圾
        }
    }

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

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++){
            SecurityCategory category = Enums.random(SecurityCategory.class);
            System.out.println(category + ": " + category.randomSelection());
        }
    }
}
/*
BOND: MUNICIPAL
BOND: MUNICIPAL
STOCK: MARGIN
STOCK: MARGIN
BOND: JUNK
STOCK: SHORT
STOCK: LONG
STOCK: LONG
BOND: MUNICIPAL
BOND: JUNK
 */

将以上例子,用于Food例子

重新组织下代码,使代码结构更清晰

package enumerated.menu;

import enumerated.Enums;

public enum Meal2 {
    APPETIZER(Food.Appetizer.class),
    MAINCOURSE(Food.MainCourse.class),
    DESSERT(Food.Dessert.class),
    COFFEE(Food.Coffee.class);
    private Food[] values;

    private Meal2(Class<? extends Food> kind) {
        values = kind.getEnumConstants();
    }

    public interface Food {
        enum Appetizer implements Food { // 开胃菜
            SALAD, SOUP, SPRING_ROLLS; // 春卷
        }

        enum MainCourse implements Food {
            LASAGNE, BURRITO, PAD_THAI,
            LENTILS, HUMMOUS, VINDALOO;//千层面,墨西哥卷饼,泰国面,小扁豆,HUMMOUS和vindaloo;
        }

        enum Dessert implements Food {
            TIRAMISU, GELATO, BLACK_FOREST_CAKE,
            FRUIT, CREME_CARAMEL;//提拉米苏,冰淇淋,黑森林蛋糕,水果,奶油焦糖;
        }

        enum Coffee implements Food {
            BLACK_COFFEE, DECAF_COFFEE, ESPRESSO,
            LATTE, CAPPUCCINO, TEA, HERB_TEA;//黑咖啡、低卡咖啡、浓缩咖啡、拿铁、卡布奇诺、茶、草茶;
        }
    }

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

    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            for (Meal2 meal : Meal2.values()) {
                Food food = meal.randomSelection();
                System.out.println(food);
            }
            System.out.println("***");
        }
    }
}
/*
SPRING_ROLLS
VINDALOO
FRUIT
DECAF_COFFEE
***
SOUP
VINDALOO
FRUIT
TEA
***
SALAD
BURRITO
FRUIT
TEA
***
SALAD
BURRITO
CREME_CARAMEL
LATTE
***
SOUP
BURRITO
TIRAMISU
ESPRESSO
***
 */

作业的例子,特别有意思

package enumerated.e5;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Random;

public enum  VowelsAndConsonants {
    VOWEL('a','e','i','o','u'){
        public String toString(){return "Vowel";}
    },
    SOMETIMES_A_VOWEL('y','w'){
        public String toString(){return "Sometimes a vowel";}
    },
    CONSONANT{
        public String toString(){return "Consonant";}
    };
    private HashSet<Character> chars = new HashSet<>();

    VowelsAndConsonants(Character...chars){
        if (chars != null)
            this.chars.addAll(Arrays.asList(chars));
    }


    public static VowelsAndConsonants get(Character c){
        if (VOWEL.chars.contains(c))
            return VOWEL;
        if (SOMETIMES_A_VOWEL.chars.contains(c))
            return SOMETIMES_A_VOWEL;
        return CONSONANT;
    }

    public static void main(String[] args) {
        Random rand = new Random(47);
        for (int i = 0 ;i < 100 ;i++){

            int c = rand.nextInt(26) + 'a';
            System.out.print( (char)c  + ", " + c + ": ");
            System.out.println(VowelsAndConsonants.get((char)c).toString());

        }
    }
}

/*
y, 121: Sometimes a vowel
n, 110: Consonant
z, 122: Consonant
b, 98: Consonant
r, 114: Consonant
n, 110: Consonant
y, 121: Sometimes a vowel
...
打印部分

8 使用EnumSet替代标识

enum 也要求其成员都是唯一的,由于不能从 enum 中删除或添加元素,所以它只能算是不太有用的集合

传统的基于 int 的“位标志”,可以用来表示某种“开/关”信息,不过,我们最终操作的只是一些 bit

而不是这些 bit 想要表达的概念,因此很容易写出令人难以理解的代码。

EnumSet非常快速高效(与 HashSet比,非常地快), long 值作为比特向量,具有更好的表达能力,无需担心性能。

EnumSet 中的元素必须来自一个 enum。

package enumerated;

import java.util.EnumSet;
import static enumerated.AlarmPoints.*;

public class EnumSets {
    public static void main(String[] args) {
        EnumSet<AlarmPoints> points = EnumSet.noneOf(AlarmPoints.class); // 清空
        points.add(BATHROOM);
        System.out.println(points);
        points.addAll(EnumSet.of(STAIR1, STAIR2, KITCHEN)); // 部分
        System.out.println(points);
        points = EnumSet.allOf(AlarmPoints.class); // 全部
        System.out.println(points);
        points.removeAll(EnumSet.of(STAIR1, STAIR2, KITCHEN));
        System.out.println(points);
        points.removeAll(EnumSet.range(OFFICE1,OFFICE4));//范围
        System.out.println(points);
        points = EnumSet.complementOf(points);//补全
        System.out.println(points);
    }
}
/*
[BATHROOM]
[STAIR1, STAIR2, BATHROOM, KITCHEN]
[STAIR1, STAIR2, LOBBY, OFFICE1, OFFICE2, OFFICE3, OFFICE4, BATHROOM, UTILITY, KITCHEN]
[LOBBY, OFFICE1, OFFICE2, OFFICE3, OFFICE4, BATHROOM, UTILITY]
[LOBBY, BATHROOM, UTILITY]
[STAIR1, STAIR2, OFFICE1, OFFICE2, OFFICE3, OFFICE4, KITCHEN]
 */

of() 方法被重载了很多次,不但为可变数量参数进行了重载,

而且为接收 2 至 5 个显式的参数的情况都进行了重载。这也从侧面表现了 EnumSet 对性能的关注。

EnumSet 的基础是 long,一个 long 值有 64 位,而一个 enum 实例只需一位 bit 表示其是否存在

        if (universe.length <= 64)
            return new RegularEnumSet<>(elementType, universe);
        else
            return new JumboEnumSet<>(elementType, universe);

Enum 会在必要的时候增加一个 long

package enumerated;

import java.util.EnumSet;

public class BigEnumSet {
    enum Big{
        A0, A1, A2, A3, A4, A5, A6, A7, A8, A9,
        A10, A11, A12, A13, A14, A15, A16, A17, A18, A19,
        A20, A21, A22, A23, A24, A25, A26, A27, A28, A29,
        A30, A31, A32, A33, A34, A35, A36, A37, A38, A39,
        A40, A41, A42, A43, A44, A45, A46, A47, A48, A49,
        A50, A51, A52, A53, A54, A55, A56, A57, A58, A59,
        A60, A61, A62, A63, A64, A65, A66, A67, A68, A69,
        A70, A71, A72, A73, A74, A75,
        }

    public static void main(String[] args) {
//        for (int i = 0 ;i < 75; i++){
//            System.out.print("A" + i + ", ");
//        }
        EnumSet<Big> bigEnumSet = EnumSet.allOf(Big.class);
        System.out.print(bigEnumSet);
    }
}

9 使用EnumMap

键(key)必须来自一个 enum
EnumMap 在内部可由数组实现

命令设计模式的用法。命令模式首先需要一个只有单一方法的接口,然后从该接口实现具有各自不同的行为的多个子类

package enumerated;

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

import static enumerated.AlarmPoints.*;

interface  Command{
    void action();
}
public class EnumMaps {
    public static void main(String[] args) {
        EnumMap<AlarmPoints,Command> em = new EnumMap<>(AlarmPoints.class);
        em.put(KITCHEN, () -> System.out.println("Kitchen fire!")); // ->是lambda表达式,就是匿名函数
        em.put(BATHROOM,() -> System.out.println("Bathroom fire!"));
        for (Map.Entry<AlarmPoints,Command> e: em.entrySet()) {
            System.out.print(e.getKey() + ": ");
            e.getValue().action();
        }
        try {
            em.get(UTILITY).action();
        }catch (Exception e){
            System.out.println("Exception: " + e);
        }
    }
}
/*
BATHROOM: Bathroom fire!
KITCHEN: Kitchen fire!
Exception: java.lang.NullPointerException
 */

10 常量相关的方法

11 多路分发

11 总结

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!