Skip to content

枚举类型

枚举类型的定义

[[枚举类型的使用]]

enum GENDER {
    MALE, FEMALE, NOTTELLING
}

public static void main(String [] args){
    GENDER gender = GENDER.MALE; // 每个枚举值都是是枚举类型的对象示例
    System.out.println(gender.toString() + " " + gender.ordinal()) //编译器自动生成
}
- 每个枚举类型对象都有toString()方法,用于打印实例名称(或使用name()) - 使用类名.values()获取该枚举类所有对象的数组 - 枚举类型都隐式地继承自java.lang.Enum类,因此枚举类不能再去继承其他类,但是可以去实现接口 - 可以直接向上转型为Enum - Enum向下转型e.getClass().getEnumContants()没有枚举的类型调用会返回null

  • 枚举类可以添加自定义方法,枚举类也可以持有数据(并且定义构造函数)
    public enum OzWitch {
      WEST("Miss"),
      NORTH("Glinda"),
      EAST("Wicked"),
      SOUTH("Good");
      private String description;
      private OzWitch(String description) {
        this.description = description;
      }
      public String getDescription() { return description; }
    }
    
  • 要注意的是这个构造函数是private的,仅限于内部初始化预定的枚举实例,不能在外部使用new创建任意对象
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 csm : values())
      System.out.println(csm.getInfo());
  }
}
  • 分组枚举
  • 使用接口实现,外层接口没有定义任何方法,因此任何一个类都可以实现这个接口,每个枚举类都实现这个接口是为了按照一个类型来使用

    public interface Food {
      enum Appetizer implements Food {
        SALAD, SOUP, SPRING_ROLLS;
      }
      enum MainCourse implements Food {
        LASAGNE, BURRITO, PAD_THAI,
        LENTILS, HUMMUS, 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;
      }
    }
    
    package enums.menu;
    import static enums.menu.Food.*;
    
    public class TypeOfFood {
      public static void main(String [] args) {
        Food food = Appetizer.SALAD;
        food = MainCourse.LASAGNE;
        food = Dessert.GELATO;
        food = Coffee.CAPPUCCINO;
      }
    }
    

  • 嵌套的枚举

    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();
        }
        public Food randomSelection() {
            return Enums.random(values);
        }
    }
    

枚举类型的集合

  • EnumSet
  • 用于使用枚举类型标识状态,需要从枚举类型创建
    //创建空枚举
    EnumSet <AlarmPoints> points =
          EnumSet.noneOf(AlarmPoints.class);
    points.add(BATHROOM);
    //从另一个 EnumSet 加入
    points.addAll(EnumSet.of(STAIR1, STAIR2, KITCHEN));
    //从 Enum 加入
    points = EnumSet.allOf(AlarmPoints.class);
    //从 EnumSet 删除
    points.removeAll(EnumSet.of(STAIR1, STAIR2, KITCHEN));
    points.removeAll(EnumSet.range(OFFICE1, OFFICE4));
    points = EnumSet.complementOf(points);
    
  • 通过long存储标志实现,支持多余64个枚举类型(会自动给引入新的long)
  • 存储顺序由在Enum中的声明顺序决定

  • EnumMap

  • 所有的键来源某个枚举类型(实际上就是一个数组)除此之外与普通map一样
  • 所有的枚举类型会被作为键初始化加入到map,但是对应的val为null

枚举类型的高级应用职责链

职责链

  • 把解决问题的方法串成链,当请求到达时会顺着链传递下去知道遇到某个可以处理请求的方法(事件冒泡)
    //一个邮局,针对不同类型的邮件有一系列处理方式,根据优先级高低一次对邮件进行处理直至成功
    public class PostOffice {
      enum MailHandler {
        GENERAL_DELIVERY {
          @Override boolean handle(Mail m) {
            switch(m.generalDelivery) {
              case YES:
                System.out.println(
                  "Using general delivery for " + m);
                return true;
              default: return false;
            }
          }
        },
        MACHINE_SCAN {
          @Override boolean handle(Mail m) {
            switch(m.scannability) {
              case UNSCANNABLE: return false;
              default:
                switch(m.address) {
                  case INCORRECT: return false;
                  default:
                    System.out.println(
                      "Delivering "+ m + " automatically");
                    return true;
                }
            }
          }
        },...
        };
        //每个手段的处理方式
        abstract boolean handle(Mail m);
      }
      static void handle(Mail m) {
          //按照枚举类型的声明顺序依次尝试处理
        for(MailHandler handler : MailHandler.values())
          if(handler.handle(m))
            return;
        System.out.println(m + " is a dead letter");
      }
    }
    

有限状态机

  • 通常使用switch处理输入,选择下一步的状态进行状态转移
public class StateMachine {
    private State currentState;

    public StateMachine() {
        currentState = State.START;
    }

    // 根据当前状态和给定的输入决定下一个状态
    public void nextState(String input) {
        switch (currentState) {
            case START:
                if (input.equals("start")) {
                    currentState = State.IN_PROGRESS;
                } else {
                    currentState = State.ERROR;
                }
                break;
            case IN_PROGRESS:
                if (input.equals("pause")) {
                    currentState = State.PAUSED;
                } else if (input.equals("finish")) {
                    currentState = State.FINISHED;
                } else {
                    currentState = State.ERROR;
                }
                break;
            case PAUSED:
                if (input.equals("resume")) {
                    currentState = State.IN_PROGRESS;
                } else if (input.equals("stop")) {
                    currentState = State.FINISHED;
                } else {
                    currentState = State.ERROR;
                }
                break;
            case FINISHED:
                // 这里可能会有一些重置逻辑
                break;
            case ERROR:
                // 处理错误
                break;
        }
    }
    public State getCurrentState() {
        return currentState;
    }
}

多路分发

  • 方法的调用依赖于多个对象的动态类型(动态绑定是单路分发)
  • 有几路就进行几次方法调用,每次确定一个类型
  • 可以列举所有的组合,如两个参数都从3个固定类型选择,只需有枚举所有情况,即给每个类型都设置全部3种参数的成员函数

  • 枚举类型分发

    public enum RoShamBo2 implements Competitor <RoShamBo2> {
      PAPER(DRAW, LOSE, WIN),
      SCISSORS(WIN, DRAW, LOSE),
      ROCK(LOSE, WIN, DRAW);
      private Outcome vPAPER, vSCISSORS, vROCK;
      RoShamBo2(Outcome paper,
        Outcome scissors, Outcome rock) {
        this.vPAPER = paper;
        this.vSCISSORS = scissors;
        this.vROCK = rock;
      }
      @Override public Outcome compete(RoShamBo2 it) {
        switch(it) {
          default:
          case PAPER: return vPAPER;
          case SCISSORS: return vSCISSORS;
          case ROCK: return vROCK;
        }
      }
      public static void main(String [] args) {
        RoShamBo.play(RoShamBo2.class, 20);
      }
    }
    

  • 常量特定方法

    //即枚举所有可能的组合 
    PAPER {
        @Override public Outcome compete(RoShamBo3 it) {
          switch(it) {
            default: // To placate the compiler
            case PAPER: return DRAW;
            case SCISSORS: return LOSE;
            case ROCK: return WIN;
          }
        }
      }...
    

  • EnumMap

  • 嵌套实现

    enum RoShamBo5 implements Competitor <RoShamBo5> {
      PAPER, SCISSORS, ROCK;
      static EnumMap <RoShamBo5,EnumMap<RoShamBo5,Outcome> >
        table = new EnumMap <>(RoShamBo5.class);
      static {
        for(RoShamBo5 it : RoShamBo5.values())
          table.put(it, new EnumMap <>(RoShamBo5.class));
        initRow(PAPER, DRAW, LOSE, WIN);
        initRow(SCISSORS, WIN, DRAW, LOSE);
        initRow(ROCK, LOSE, WIN, DRAW);
      }
      static void initRow(RoShamBo5 it,
        Outcome vPAPER, Outcome vSCISSORS, Outcome vROCK) {
        EnumMap <RoShamBo5,Outcome> row =
          RoShamBo5.table.get(it);
        row.put(RoShamBo5.PAPER, vPAPER);
        row.put(RoShamBo5.SCISSORS, vSCISSORS);
        row.put(RoShamBo5.ROCK, vROCK);
      }
      @Override public Outcome compete(RoShamBo5 it) {
          //获取方法
        return table.get(this).get(it);
      }
      public static void main(String [] args) {
        RoShamBo.play(RoShamBo5.class, 20);
      }
    }
    

  • 二维数组

  • 由于枚举类型可以转化为整数值,因此实际上可以直接使用二维数组
    enum RoShamBo6 implements Competitor <RoShamBo6> {
      PAPER, SCISSORS, ROCK;
      private static Outcome [][] table = {
        { DRAW, LOSE, WIN }, // PAPER
        { WIN, DRAW, LOSE }, // SCISSORS
        { LOSE, WIN, DRAW }, // ROCK
      };
      @Override public Outcome compete(RoShamBo6 other) {
        return table [this.ordinal()][other.ordinal()];
      }
      public static void main(String [] args) {
        RoShamBo.play(RoShamBo6.class, 20);
      }
    }
    

模式匹配

  • switch的高级用法
  • 智能转型(JDK16)
    static void smart(Object x) {
        if(x instanceof String s && s.length() > 0) {
          System.out.format(
            "%d %s%n", s.length(), s.toUpperCase());
        }
      }
    //特殊例子
    static void f(Object o) {
        if(!(o instanceof String s)) {
          System.out.println("Not a String");
          throw new RuntimeException();
        }
        //能到达这里说明一定是 String 类型,否则就出错了
        // s is in scope here!
        System.out.println(s.toUpperCase());  // [1]
      }
    
  • 使用instanceof判断类型后在接下来的作用域内就当作这个类型使用
  • 即已经判断成功了,那么这个作用域内当作这个类型处理显然是安全的

  • 模式匹配(JDK17)

  • 允许根据数据的结构和内容来检查和分解数据。
  • 对于一组具有相同基类的类型,不止希望使用基类中的公共方法
  • 使用switch实现

    public class PetPatternMatch {
        //使用非基类方法
      static void careFor(Pet p) {
        switch(p) {
          case Dog d -> d.walk();//使用了只能转型
          case Fish f -> f.changeWater();
          case Pet sp -> sp.feed();
        };
      }
      static void petCare() {
        List.of(new Dog(), new Fish())
          .forEach(p -> careFor(p));
      }
    }
    

  • 守卫:进一步细化匹配条件而不只是简单地匹配类型

  • 可以是任何布尔表达式,如果类型符合并且守卫为true那么就匹配上了
    System.out.println(switch(s) {
        case Circle c && c.area() < 100.0
            -> "Small Circle: " + c;
        case Circle c -> "Large Circle: " + c;
        case Rectangle r && r.side1() == r.side2()
            -> "Square: " + r;
        case Rectangle r -> "Rectangle: " + r;
    });
    
  • 上面的case对下面的具有支配性