美文网首页
第十九章:枚举类型

第十九章:枚举类型

作者: MAXPUP | 来源:发表于2017-12-24 17:07 被阅读0次

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

    基本enum特性

    1. 使用enum的values()方法,返回enum实例的数组,保持声明时的顺序。创建enum时,编译器生成java.lang.Enum类
    2. 静态导入:使用static import可以将enum实例的标识符带入当前的命名空间,无需再用enum类型来修饰enum实例。
    3. 向enum中添加新方法
      除了不能继承一个enum之外,可以将enum看作一个常规的类,可以添加方法,甚至是main()方法。
    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;
    
      // !!!Constructor must be package or private access:
      private OzWitch(String description) {
        this.description = description;
      }
    
      public String getDescription() { return description; }
    
      //覆盖toString()
      public String toString(){
        String id = name(); //每个标识符其实就是这个类的实例,name()返回标识符的名字。
        String lower = id.substring(1).toLowerCase();
        return id.charAt(0) + lower;
      }
      public static void main(String[] args) {
        for(OzWitch witch : OzWitch.values())
          print(witch + ": " + witch.getDescription());
      }
    } 
    
    1. switch语句中的enum
      枚举实例天生具备整数值得次序,可以通过ordinal()取得其次序。case中也不一定需要enum类型来修饰实例。
    Signal color = Signal.RED;
      public void change() {
        switch(color) {
          // Note that you don't have to say Signal.RED
          // in the case statement:
          case RED:    color = Signal.GREEN;
                       break;
          case GREEN:  color = Signal.YELLOW;
                       break;
          case YELLOW: color = Signal.RED;
                       break;
        }
      }
    
    1. values()的神秘
      Enum没有values()方法,因为该方法时由编译器添加的static方法,还添加了只需要一个参数的valueOf()方法(默认两个参数)。而且编译器将enum类设为final,因此不可被继承。注意:因为values()是由编译器插入到enum定义中的static方法,所以当向上转型为Enum时,就不能访问该方法。不过,Class对象中有一个getEnumConstants(),可以获得所有enum实例。
    public static void main(String[] args) {
        Search[] vals = Search.values();
        Enum e = Search.HITHER; // Upcast
        // e.values(); // No values() in Enum
        for(Enum en : e.getClass().getEnumConstants())
          System.out.println(en);
      }
    

    实现,而非继承

    enum不能继承其他类,但是可以实现多个接口。

    随机选取

    <T extends Enum<T>>表示是一个enum实例。

    public class Enums {
      private static Random rand = new Random(47);
      public static <T extends Enum<T>> T random(Class<T> ec) {
        return random(ec.getEnumConstants());
      }
      public static <T> T random(T[] values) {
        return values[rand.nextInt(values.length)];
      }
    }
    //测试
    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) + " ");
      }
    }
    

    使用接口组织枚举

    对于enum而言,实现接口是使其子类化的唯一办法。

    //我们可以说下列所有enum都是Food类型
    public interface Food {
      enum Appetizer implements Food {
        SALAD, SOUP, SPRING_ROLLS;
      }
      enum MainCourse implements Food {
        LASAGNE, BURRITO, PAD_THAI,
        LENTILS, 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 static void main(String[] args) {
        Food food = Appetizer.SALAD;
        food = MainCourse.LASAGNE;
        food = Dessert.GELATO;
        food = Coffee.CAPPUCCINO;
      }
    

    当需要枚举的枚举时,还可以用实例包装上面的每一个enum类。

    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);
      }
    } ///:~
    

    还可以使用内部接口的方式组合enum。总之,enum的使用非常灵活。

    使用EnumSet替代标志

    EnumSet替代传统的基于int的“位标志”(用来表示某种“开/关”信息),内部使用long值作为比特向量,非常快速高效。EnumSet的元素必须是enum。一个Long值有64位,而一个enum实例只需要一个bit表示其是否存在。

    EnumMap

    要求键必须来自enum,因为enum本身的特性,所以EnumMap在内部可由数组实现。与EnumSet一样,enum实例定义的次序决定了在EnumMap中的次序。

    常量相关的方法

    enum可以为每个实例编写方法,赋予不同的行为。需要为enum定义abstract方法,然后由每个实例实现。

    //也称为表驱动的代码
    public enum ConstantSpecificMethod {
      DATE_TIME {
        String getInfo() {
          return
            DateFormat.getDateInstance().format(new Date());
        }
      },
      CLASSPATH {
        String getInfo() {
          return System.getenv("CLASSPATH");
        }
      },
      VERSION {
        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());
      }
    } /* (Execute to see output) *///:~
    

    使用enum的职责链

    **职责链设计模式:以多种不同方式解决一个问题,然后将它们链接在一起,当一个请求到来时,遍历这个链,直到链中的某个解决方案能够处理该请求。

    class Mail {
      // The NO's lower the probability of random selection:
      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<Mail> generator(final int count) {
        return new Iterable<Mail>() {
          int n = count;
          public Iterator<Mail> iterator() {
            return new Iterator<Mail>() {
              public boolean hasNext() { return n-- > 0; }
              public Mail next() { return randomMail(); }
              public void remove() { // Not implemented
                throw new UnsupportedOperationException();
              }
            };
          }
        };
      }
    }
    
    public class PostOffice {
      enum MailHandler {
        GENERAL_DELIVERY {
          boolean handle(Mail m) {
            switch(m.generalDelivery) {
              case YES:
                print("Using general delivery for " + m);
                return true;
              default: return false;
            }
          }
        },
        MACHINE_SCAN {
          boolean handle(Mail m) {
            switch(m.scannability) {
              case UNSCANNABLE: return false;
              default:
                switch(m.address) {
                  case INCORRECT: return false;
                  default:
                    print("Delivering "+ m + " automatically");
                    return true;
                }
            }
          }
        },
        VISUAL_INSPECTION {
          boolean handle(Mail m) {
            switch(m.readability) {
              case ILLEGIBLE: return false;
              default:
                switch(m.address) {
                  case INCORRECT: return false;
                  default:
                    print("Delivering " + m + " normally");
                    return true;
                }
            }
          }
        },
        RETURN_TO_SENDER {
          boolean handle(Mail m) {
            switch(m.returnAddress) {
              case MISSING: return false;
              default:
                print("Returning " + m + " to sender");
                return true;
            }
          }
        };
        abstract boolean handle(Mail m);
      }
      static void handle(Mail m) {
        for(MailHandler handler : MailHandler.values())
          if(handler.handle(m))
            return;
        print(m + " is a dead letter");
      }
      public static void main(String[] args) {
        for(Mail mail : Mail.generator(10)) {
          print(mail.details());
          handle(mail);
          print("*****");
        }
      }
    } /* Output:
    Mail 0, General Delivery: NO2, Address Scanability: UNSCANNABLE, Address Readability: YES3, Address Address: OK1, Return address: OK1
    Delivering Mail 0 normally
    *****
    Mail 1, General Delivery: NO5, Address Scanability: YES3, Address Readability: ILLEGIBLE, Address Address: OK5, Return address: OK1
    Delivering Mail 1 automatically
    *****
    Mail 2, General Delivery: YES, Address Scanability: YES3, Address Readability: YES1, Address Address: OK1, Return address: OK5
    Using general delivery for Mail 2
    *****
    Mail 3, General Delivery: NO4, Address Scanability: YES3, Address Readability: YES1, Address Address: INCORRECT, Return address: OK4
    Returning Mail 3 to sender
    *****
    Mail 4, General Delivery: NO4, Address Scanability: UNSCANNABLE, Address Readability: YES1, Address Address: INCORRECT, Return address: OK2
    Returning Mail 4 to sender
    *****
    Mail 5, General Delivery: NO3, Address Scanability: YES1, Address Readability: ILLEGIBLE, Address Address: OK4, Return address: OK2
    Delivering Mail 5 automatically
    *****
    Mail 6, General Delivery: YES, Address Scanability: YES4, Address Readability: ILLEGIBLE, Address Address: OK4, Return address: OK4
    Using general delivery for Mail 6
    *****
    Mail 7, General Delivery: YES, Address Scanability: YES3, Address Readability: YES4, Address Address: OK2, Return address: MISSING
    Using general delivery for Mail 7
    *****
    Mail 8, General Delivery: NO3, Address Scanability: YES1, Address Readability: YES3, Address Address: INCORRECT, Return address: MISSING
    Mail 8 is a dead letter
    *****
    Mail 9, General Delivery: NO1, Address Scanability: UNSCANNABLE, Address Readability: YES2, Address Address: OK1, Return address: OK4
    Delivering Mail 9 normally
    *****
    *///:~
    

    使用enum的状态机

    **状态机模式:一个状态机有有限个特定的状态,根据输入,从一个状态转移到下一个状态,也可能存在瞬时状态,一旦任务结束,就立刻离开瞬时状态。

    enum Category {
      MONEY(NICKEL, DIME, QUARTER, DOLLAR),
      ITEM_SELECTION(TOOTHPASTE, CHIPS, SODA, SOAP),
      QUIT_TRANSACTION(ABORT_TRANSACTION),
      SHUT_DOWN(STOP);
      private Input[] values;
      Category(Input... types) { values = types; }  
      private static EnumMap<Input,Category> categories =
        new EnumMap<Input,Category>(Input.class);
      static {
        for(Category c : Category.class.getEnumConstants())
          for(Input type : c.values)
            categories.put(type, c);
      }
      public static Category categorize(Input input) {
        return categories.get(input);
      }
    }   
    
    public class VendingMachine {
      private static State state = State.RESTING;
      private static int amount = 0;
      private static Input selection = null;
      enum StateDuration { TRANSIENT } // Tagging enum
      enum State {
        RESTING {
          void next(Input input) {
            switch(Category.categorize(input)) {
              case MONEY:
                amount += input.amount();
                state = ADDING_MONEY;
                break;
              case SHUT_DOWN:
                state = TERMINAL;
              default:
            }
          }
        },  
        ADDING_MONEY {
          void next(Input input) {
            switch(Category.categorize(input)) {
              case MONEY:
                amount += input.amount();
                break;
              case ITEM_SELECTION:
                selection = input;
                if(amount < selection.amount())
                  print("Insufficient money for " + selection);
                else state = DISPENSING;
                break;
              case QUIT_TRANSACTION:
                state = GIVING_CHANGE;
                break;
              case SHUT_DOWN:
                state = TERMINAL;
              default:
            }
          }
        },  
        DISPENSING(StateDuration.TRANSIENT) {
          void next() {
            print("here is your " + selection);
            amount -= selection.amount();
            state = GIVING_CHANGE;
          }
        },
        GIVING_CHANGE(StateDuration.TRANSIENT) {
          void next() {
            if(amount > 0) {
              print("Your change: " + amount);
              amount = 0;
            }
            state = RESTING;
          }
        },  
        TERMINAL { void output() { print("Halted"); } };
        private boolean isTransient = false;
        State() {}
        State(StateDuration trans) { isTransient = true; }
        void next(Input input) {
          throw new RuntimeException("Only call " +
            "next(Input input) for non-transient states");
        }
        void next() {
          throw new RuntimeException("Only call next() for " +
            "StateDuration.TRANSIENT states");
        }
        void output() { print(amount); }
      } 
      static void run(Generator<Input> gen) {
        while(state != State.TERMINAL) {
          state.next(gen.next());
          while(state.isTransient)
            state.next();
          state.output();
        }
      }
      public static void main(String[] args) {
        Generator<Input> gen = new RandomInputGenerator();
        if(args.length == 1)
          gen = new FileInputGenerator(args[0]);
        run(gen);
      }
    }   
    
    // For a basic sanity check:
    class RandomInputGenerator implements Generator<Input> {
      public Input next() { return Input.randomSelection(); }
    }
    
    // Create Inputs from a file of ';'-separated strings:
    class FileInputGenerator implements Generator<Input> {
      private Iterator<String> input;
      public FileInputGenerator(String fileName) {
        input = new TextFile(fileName, ";").iterator();
      }
      public Input next() {
        if(!input.hasNext())
          return null;
        return Enum.valueOf(Input.class, input.next().trim());
      }
    } /* Output:
    25
    50
    75
    here is your CHIPS
    0
    100
    200
    here is your TOOTHPASTE
    0
    25
    35
    Your change: 35
    0
    25
    35
    Insufficient money for SODA
    35
    60
    70
    75
    Insufficient money for SODA
    75
    Your change: 75
    0
    Halted
    *///:~
    

    多路分发

    java只支持单路分发,意思是,java的动态绑定只能处理一个的类型,不能对某个操作包含多个类型。所以java实现多路分发一般需要对每个类型都提供一种方法调用。

    interface Item {
      //两路分发入口
      Outcome compete(Item it);
      //第二次分发
      Outcome eval(Paper p);
      Outcome eval(Scissors s);
      Outcome eval(Rock r);
    }
    
    class Paper implements Item {
      public Outcome compete(Item it) { return it.eval(this); }
      public Outcome eval(Paper p) { return DRAW; }
      public Outcome eval(Scissors s) { return WIN; }
      public Outcome eval(Rock r) { return LOSE; }
      public String toString() { return "Paper"; }
    }   
    
    class Scissors implements Item {
      public Outcome compete(Item it) { return it.eval(this); }
      public Outcome eval(Paper p) { return LOSE; }
      public Outcome eval(Scissors s) { return DRAW; }
      public Outcome eval(Rock r) { return WIN; }
      public String toString() { return "Scissors"; }
    }
    
    class Rock implements Item {
      public Outcome compete(Item it) { return it.eval(this); }
      public Outcome eval(Paper p) { return WIN; }
      public Outcome eval(Scissors s) { return LOSE; }
      public Outcome eval(Rock r) { return DRAW; }
      public String toString() { return "Rock"; }
    }   
    
    public class RoShamBo1 {
      static final int SIZE = 20;
      private static Random rand = new Random(47);
      public static Item newItem() {
        switch(rand.nextInt(3)) {
          default:
          case 0: return new Scissors();
          case 1: return new Paper();
          case 2: return new Rock();
        }
      }
      public static void match(Item a, Item b) {
        System.out.println(
          a + " vs. " + b + ": " +  a.compete(b));
      }
      public static void main(String[] args) {
        for(int i = 0; i < SIZE; i++)
          match(newItem(), newItem());
      }
    } 
    

    使用enum分发,依然是两路分发:

    public class RoShamBo {
      public static <T extends Competitor<T>>
      void match(T a, T b) {
      //第一次分发
        System.out.println(
          a + " vs. " + b + ": " +  a.compete(b));
      }
      public static <T extends Enum<T> & Competitor<T>>
      void play(Class<T> rsbClass, int size) {
        for(int i = 0; i < size; i++)
          match(
            Enums.random(rsbClass),Enums.random(rsbClass));
      }
    } ///:~
    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;
      } 
      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);
      }
    } /* Output:
    

    使用常量相关的方法

    public enum RoShamBo3 implements Competitor<RoShamBo3> {
      PAPER {
        public Outcome compete(RoShamBo3 it) {
          switch(it) {
            default: // To placate the compiler
            case PAPER: return DRAW;
            case SCISSORS: return LOSE;
            case ROCK: return WIN;
          }
        }
      },
      SCISSORS {
        public Outcome compete(RoShamBo3 it) {
          switch(it) {
            default:
            case PAPER: return WIN;
            case SCISSORS: return DRAW;
            case ROCK: return LOSE;
          }
        }
      },
      ROCK {
        public Outcome compete(RoShamBo3 it) {
          switch(it) {
            default:
            case PAPER: return LOSE;
            case SCISSORS: return WIN;
            case ROCK: return DRAW;
          }
        }
      };
      public abstract Outcome compete(RoShamBo3 it);
      public static void main(String[] args) {
        RoShamBo.play(RoShamBo3.class, 20);
      }
    } /* Same output as RoShamBo2.java *///:~
    

    还可以进行压缩

    public enum RoShamBo4 implements Competitor<RoShamBo4> {
      ROCK {
        public Outcome compete(RoShamBo4 opponent) {
          return compete(SCISSORS, opponent);
        }
      },
      SCISSORS {
        public Outcome compete(RoShamBo4 opponent) {
          return compete(PAPER, opponent);
        }
      },
      PAPER {
        public Outcome compete(RoShamBo4 opponent) {
          return compete(ROCK, opponent);
        }
      };
      Outcome compete(RoShamBo4 loser, RoShamBo4 opponent) {
        return ((opponent == this) ? Outcome.DRAW
            : ((opponent == loser) ? Outcome.WIN
                                   : Outcome.LOSE));
      }
      public static void main(String[] args) {
        RoShamBo.play(RoShamBo4.class, 20);
      }
    } /* Same output as RoShamBo2.java *///:~
    

    使用EnumMap分发:
    EnumMap能够实现真正的两路分发,是专为enum设计的性能非常好的特殊Map。

    enum RoShamBo5 implements Competitor<RoShamBo5> {
      PAPER, SCISSORS, ROCK;
      static EnumMap<RoShamBo5,EnumMap<RoShamBo5,Outcome>>
        table = new EnumMap<RoShamBo5,
          EnumMap<RoShamBo5,Outcome>>(RoShamBo5.class);
      static {
        for(RoShamBo5 it : RoShamBo5.values())
          table.put(it,
            new EnumMap<RoShamBo5,Outcome>(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);
      }
      public Outcome compete(RoShamBo5 it) {
        return table.get(this).get(it);//一行语句两次分发
      }
      public static void main(String[] args) {
        RoShamBo.play(RoShamBo5.class, 20);
      }
    } /* Same output as RoShamBo2.java *///:~
    

    使用二维数组,最直观简单:

    enum RoShamBo6 implements Competitor<RoShamBo6> {
      PAPER, SCISSORS, ROCK;
      private static Outcome[][] table = {
        { DRAW, LOSE, WIN }, // PAPER
        { WIN, DRAW, LOSE }, // SCISSORS
        { LOSE, WIN, DRAW }, // ROCK
      };
      public Outcome compete(RoShamBo6 other) {
        return table[this.ordinal()][other.ordinal()];
      }
      public static void main(String[] args) {
        RoShamBo.play(RoShamBo6.class, 20);
      }
    } ///:~
    

    java多路分发

    相关文章

      网友评论

          本文标题:第十九章:枚举类型

          本文链接:https://www.haomeiwen.com/subject/xnhiixtx.html