枚举类型

——总结自《java编程思想》
关键字enum可以将一组具名的有限集合创建为一种新的类型,而这些具名的值可以作为常规的程序组件使用,这是一种非常有用的功能。

枚举简介

基本enum特性:

  1. values()返回enum实例的数组,而且保持声明的顺序。
  2. ordinal方法返回一个int值,这是每个enum实例在声明时的次序,从0开始。
  3. 可以使用==来比较enum实例,编译器会自动提供equals和hashCode方法。Enum类实现了Comparable接口,所以它具有compareTo()方法。同时还实现了Serializable接口。
    getDeclaringClass()获取其所属的enum类。
  4. name()返回enum实例声明时的名字,与使用toString()效果相同。
  5. valueOf()实在Enum中定义的static方法,它根据给定的名字返回相应的enum实例,如果不存在会抛出异常。
  6. 可以使用static import将enum实例的标识符代入当前的命名空间,无需再用enum类型来修饰enum实例。
    向enum中添加方法:
public enum OzWitch {
    // Instances must be defined first, before methods:
    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;
    }

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

switch方法

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;
        }
    }

    @Override
    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++) {
            print(t);
            t.change();
        }
    }
}
**values神秘之处**

enum类都继承自Enum类,但是Enum并没有values()方法。利用反射机制查看究竟:

enum Explore {HERE, THERE}

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

    public static void main(String[] args) {
        Set<String> exploreMethods = analyze(Explore.class);
        Set<String> enumMethods = analyze(Enum.class);
        print("Explore.containsAll(Enum)? " +
                exploreMethods.containsAll(enumMethods));
        printnb("Explore.removeAll(Enum): ");
        exploreMethods.removeAll(enumMethods);
        print(exploreMethods);
        // Decompile the code for the enum:
        OSExecute.command("javap Explore");
    }
} /*
----- Analyzing class 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 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 {};
}
*/

values()是由编译器添加的static方法。在创建Explore的过程中,编译器还添加了valueOf()方法。虽然Enum类已经有valueOf()方法,不过Enum中的ValueOf()方法需要两个参数,这个新增方法只需一个参数。由于这里使用的Set只存储方法的名字,不考虑签名,所有在调用Expore.removeAll(Enum)之后,就只剩下[values]了。
从最后的输出可以看出,Explore被标记为final类,所以无法继承自enum。 其中还有一个static初始化子句。

由于擦边效应,反编译无法得到Enum的完整信息,所以展示的Explore只是一个原始的Enum,而非事实上的Enum< Explore >。

由于values方法是由编译器插入到enum定义中的static方法,所以enum向上转型为Enum,那么values就不可访问了,不过Class中有一个getEnumConstants方法,所以即便Enum接口中没有vlaues方法,仍然可以通过Class对象取得所有enum实例:

enum Search {HITHER, YON}
public class UpcastEnum {
    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);
        }
    }
} 
/*
HITHER
YON
*/

甚至可以对不是枚举的类调用getEnumConstants方法(只不过,此时该方法返回null):

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
*/

所有的enum都继承自java.lang.Enum类。由于java不支持多重继承,所以enum不能再继承其他类。但是可以同时实现一个或多个接口。
使用接口组织枚举

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 class TypeOfFood {
  public static void main(String[] args) {
    Food food = Appetizer.SALAD;
    food = MainCourse.LASAGNE;
    food = Dessert.GELATO;
    food = Coffee.CAPPUCCINO;
  }
}

如果想创建一个枚举的枚举,可以创建一个新的enum,然后用其实例包装Food中的每一个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);
  }
}

EnumSet

为了通过enum创建一种替代品,以替代传统的基于int的“位标志”。这种标志可以用来表示某种开关信息,不过,使用这种标志,最终操作的只是一些bit。EnumSet设计考虑到了速度因素,因为它必须与非常高效的bit相竞争(其操作与hashSet相比,非常地快)。就其内部来言,它是将一个long值作为比特向量,所以EnumSet非常快速高效。使用EnumSet的优点是,它在说明一个二进制位是否存在时,具有更好的表达能力,并且无需担心性能。
EnumSet中的元素必须来自一个enum。

在研究EnumSet文档,可以发现of()方法被重载了很多次,不但为可变数量参数进行了重载,而且为接受2至5个显式的参数的情况都进行了重载。这也从侧面表现了EnumSet对性能的关注。因为,其实只使用可变参书已经可以解决整个问题,但是对比显式的参数,会有一点性能损失。采用现在这种设计,当只使用2到5个参数调用of()方法时,可以调用重载过的方法,而当使用一个参数或多过5个参数时,调用的将是使用可变参数的of()方法。
EnumSet的基础是long,一个long值有64位,而一个enum实例只需一位bit表示其是否存在。也就是说,在不超过一个long的表达能力的情况下,EnumSet可以应用于最多不超过64个元素的enum。
EnumMap:要求其中的键必须来自于一个enu。由于enum本身的限制,所以EnumMap在内部可由数组实现,因此EnumMap的速度很快。可以使用enum实例在EnumMap中进行查找操作,不过只能将enum的实例作为键来调用put()方法,其他操作与使用一般的Map差不多。

常量相关的方法

Java的enum有一个非常有趣的特性,即它允许程序员为enum实例编写方法,从而为每个enum实例赋予各自不同的行为。要实现常量相关的方法,需要为enum定义一个或多个abstract方法,然后为每个enum实例实现该抽象方法

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());
        }
    }
}

通过相应的enum实例,可以调用其上的方法,称为表驱动的代码(tabled riven code)。enum似乎被当做其超类ConstantSpecificMethod来使用,在调用getInfo()方法时,体现出多态的行为。

职责链

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

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("*****");
        }
    }
} /*
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对其实例有严格限制,非常适合用来表现不同的状态和输入。一般而言,每个状态都具有一些相关的输出。

多路分发

Java只支持单路分发,也就是说,如果要执行的操作包含了不止一个类型未知的对象时,那么Java的动态绑定机制只能处理其中一个类型。所以必须自己来判定其他的类型,从而实现自己的动态绑定行为。
为了达到效果,需要与多个方法一同工作:因为每个分发都需要一个方法调用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值