java枚举类型学习

用的不多,但用的时候仅仅简单的使用,不太明白原理,今天就系统的学一下枚举。参考:java编程思想。

Update:

  • 枚举可以当做数据字典来存储,通常只要一个字段即instance本身,toString()或者name()打印的string。
  • 枚举的数据都是一个实例对象,比如 enum Test{A}中A就是一个对象,A的toString和name()的结果是“A”。而如果一个字符串为"A",可以转为对应的枚举实例:Test.valueOf("A")

 

 


 

1.简单创建

枚举就是一个固定的集合,内容是声明的类。

package com.test.java.tenum;

/**
 * 一个简单的enum实例
 * Created by Administrator on 2016/3/30.
 */
public enum SimpleEnumUse {
    NOT,MILD,MEDIUM,HOT,FLAMING
}

class TestSE{
    public static void main(String[] args) {
        SimpleEnumUse medium = SimpleEnumUse.MEDIUM;
        System.out.println(medium);
        System.out.println(medium.ordinal());
    }
}
View Code

创建enum的时候,里面的元素就是类,默认创建了toString(),odinal()方法以及value字段。其中odinal是声明的顺序(从0开始),value就是声明的名字。简单的使用的话,这样就可以了,配合switch即可。

2.深入了解

2.1基本特性

创建enum时,编译器会为你生成一个相关的类,这个类继承自java.lang.Enum。下面实例:

package com.test.java.tenum;

/**
 * 了解enum特性
 * Created by Administrator on 2016/3/30.
 */
public class EnumClass {
    public static void main(String[] args) {
        for (Shrubbery s : Shrubbery.values()) {
            //s继承Enum
            //ordinal表示声明的顺序
            System.out.print(s+" ordinal:"+s.ordinal());
            //enum自动实现了Comparable接口
            System.out.println(" compareTo:"+s.compareTo(Shrubbery.CRAWING));
            //编译器提供了equals和hashCode
            System.out.println(s.equals(Shrubbery.CRAWING));
            System.out.println(s==Shrubbery.CRAWING);
            System.out.println(s.getDeclaringClass());
            System.out.println(s.name());
            System.out.println("======================");
        }
        for (String s :
                "HANGING CRAWING GROUND".split(" ")) {
            Shrubbery shrubbery = Enum.valueOf(Shrubbery.class, s);
            System.out.println(shrubbery);

        }
    }
}

enum Shrubbery{
    GROUND,CRAWING,HANGING
}
View Code

ordinal()方法返回一个int值,这个每个enum实例在声明时的次序,从0开始。可以使用==比较enum实例,编译器自动为你提供了equals()和hashCode()方法。Enum类实现了Comparable接口和Serializable接口。

2.1.1静态导入

在使用enum的实例的时候发现人家不是EnumClass.INSTANCE,即不是通过类名调用的,而是直接调用,看了下。原来是在包下静态导入了。即:

import static package.EnumClass.*。

究竟显示的修饰enum实例还是静态导入要看代码的复杂度,看看静态导入是不是会让代码难以理解。

2.2可以在enum中添加自己的方法

enum可以添加方法和构造器。

package com.test.java.tenum;

/**
 * 可以在enum中添加方法和构造器
 * Created by Administrator on 2016/3/30.
 */
public enum OzWitch {
    WEST("Miss Gulch, aka the Wiched 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;
    private OzWitch(String description){
        this.description = description;
    }
    public String getDescription(){
        return description;
    }

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

通常,将enum的构造方法声明private,而实际上对于它的可访问性来说没有什么变化,因为即使不是private也只能在enum内部使用创建enum实例。一旦enum定义结束,编译器就不允许我们再使用其构造器来创建任何实例了。另外,必须优先声明实例,然后声明方法或属性,否则编译报错

2.3探究values()

经过一下检测,values是编译器添加到你创建的enum类的,而Enum类本身中并没有values方法。我们可以通过Class对象获取所有的enum实例。

package com.test.java.tenum;

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

/**
 * 通过反射查看enum的values
 * 结果:values是编译器添加到enum类的,而Enum类本身没有values方法
 * Created by Administrator on 2016/3/30.
 */
public class Reflection {
    public static Set<String> analyze(Class<?> enumClass){
        System.out.println("-----------Analyzing "+enumClass+"-------");
        System.out.println("Interfaces:");
        for (Type t :
                enumClass.getGenericInterfaces()) {
            System.out.println(t);
        }
        System.out.println("Base: "+enumClass.getSuperclass());
        System.out.println("Method:");
        Set<String> methods = new TreeSet<>();
        for (Method m :
                enumClass.getMethods()) {
            methods.add(m.getName());
        }
        System.out.println(methods);
        return methods;
    }
    public static void main(String[] args){
        Set<String> exploreMethods = analyze(Explore.class);
        Set<String> enumMethods = analyze(Enum.class);
        System.out.println("Explore.containsAll(Enum)?"+enumMethods.containsAll(enumMethods));
        System.out.println("Explore.removeAll(Enum):");
        exploreMethods.removeAll(enumMethods);
        System.out.println(exploreMethods);

    }
}
enum Explore{
    HERE,THERE
}
/************************通过class获取实例********************************/
enum Search{
    HITHER,YON
}
class UpcastEnum{
    public static void main(String[] args) {
        Search[] values = Search.values();
        Enum e = Search.HITHER;
//        e.values();//编译报错,说明No values() in Enum
        for (Enum anEnum : e.getClass().getEnumConstants()) {
            System.out.println(anEnum);
        }
    }
}

class NonEnum{
    public static void main(String[] args) {
        Class<Integer> integerClass = Integer.class;
        try{
            for (Object en : integerClass.getEnumConstants()) {
                System.out.println(en);
            }
        }catch (Exception e){
            System.out.println(e);
        }
    }
}
View Code

2.4通过实现接口而不是继承他类来扩展

应为所有enum类都继承java.lang.Enum类。由于java不支持多继承,所以你的enum不能再继承其他类。但是可以实现多个接口。

2.5随机选取

package com.test.java.tenum;

import java.util.Random;

/**
 * 随机抽取的工具类
 * Created by Administrator on 2016/3/30.
 */
public class Enums {
    private static Random random = 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[random.nextInt(values.length)];
    }
}
View Code

2.6使用接口组织枚举

无法从enum继承子类有时很令人沮丧。这种需求有时源自我们希望扩展原enum中的元素,有时我们希望使用子类将一个enum中的元素进行分组。

在一个接口内部创建实现该接口的枚举,以此将元素进行分组,可以达到将枚举元素分类的目的。举例来说,假设想用enum表示不同的食物,同时还希望每个enum元素仍然保持Food类型。可以这样:

package com.test.java.tenum;

/**
 * 使用接口组织枚举
 * Created by Administrator on 2016/3/30.
 */
public interface Food {
    //开胃食物
    enum Appetizer implements Food{
        SALAD,SOUP,SPRING_ROLLS;
    }
    //主菜
    enum MainCourse implements Food{
        LASNGE,BURRITO,PAD_THAI,LENTILS,HUMMOUS,VINDALOO;
    }
    //甜点
    enum Dessert implements Food{
        TIRAMISU,GELATO,BLACK_FOREST_CAKE,FRUIT,CREME_CARMEL;
    }
    //coffe
    enum Coffee implements Food{
        BLACK_COFFEE,DECAF_COFFEE,ESPRESSO,LATTE,CAPPUCCINO,TEA,HERB_TEA;
    }
    //....
}
class TypeOfFood{
    public static void main(String[] args) {
        Food food = Food.Appetizer.SALAD;
        food = Food.MainCourse.PAD_THAI;
    }
    
}
View Code

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

然而当你需要与一大堆类型打交道时,接口就不如enum好用。例如,你想创建一个枚举的枚举。那么可以创建一个新的enum,然后用其实例包装Food中的每一个enum类。

package com.test.java.tenum;

/**
 * 枚举的枚举
 * Created by Administrator on 2016/3/30.
 */
public enum Course {
    APPETIZER(Food.Appetizer.class),
    MAINCOURSE(Food.MainCourse.class);

    private Food[] values;
    private Course(Class<? extends Food> kind){
        values = kind.getEnumConstants();
    }
    public Food randomSelection(){
        return Enums.random(values);
    }
}
View Code

在上面的程序中,每个Course实例都将其对应的Class对象作为构造器的参数。通过getEnumConstants方法,可以从该Class对象中取得某个Food子类的所有enum实例。因此,我们通过随机可以生成一份菜单:

class Meal{
    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            for (Course course : Course.values()) {
                Food food = course.randomSelection();
                System.out.println(food);
            }
            System.out.println("-------");
        }
    }
}
View Code

在这个例子中,我们通过遍历每个Course实例来获得枚举的枚举的值。此外,还可以更简洁:将一个enum嵌套在另一个enum内。

package com.test.java.tenum;

/**
 * 枚举嵌套
 * Created by Administrator on 2016/3/30.
 */
public enum SecurityCategory {
    STOCK(Security.Stock.class),
    BOND(Security.Bond.class);

    Security[] values;
    SecurityCategory(Class<? extends Security> kind){
        values = kind.getEnumConstants();
    }
    interface Security{
        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 random = Enums.random(SecurityCategory.class);
            System.out.println(random+":"+random.randomSelection());
        }
    }
}
View Code

Security接口的作用是将其所包含的enum组成一个公共类型,这一点是必要的。然后SecurityCategory才能将Security中的enum作为其构造器的参数作用,以起到组织的效果。

 如果我们将这种方式应用于Food实例,结果是这样:

/**
 * 同理,food
 */
enum Meal2{
    APPETIZER(Food.Appetizer.class),
    MAINCOURSE(Food.MainCourse.class),
    DESSERT(Food.Dessert.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{
            LASNGE,BURRITO,PAD_THAI,LENTILS,HUMMOUS,VINDALOO;
        }
        //甜点
        enum Dessert implements Food{
            TIRAMISU,GELATO,BLACK_FOREST_CAKE,FRUIT,CREME_CARMEL;
        }
        //coffe
        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 meal2 : Meal2.values()) {
                Food food = meal2.randomSelection();
                System.out.println(food);
            }
            System.out.println("----------------------------");
        }
    }

}
View Code

这仅仅是重新组织了下代码,不过多数情况下,这种方式使你的代码具有更清晰的结构。

 

2.7使用EnumSet代替标识

java se5引入EnumSet,是为了通过enum创建一种替代品,以替代传统的基于int的“标志位”。这种标识可以用来表示某种“开/关”信息。

下面enum表示在一座大楼中,警报传感器的安防位置:

package com.test.java.tenum;

import java.util.EnumSet;

/**
 * 酱爆传感器位置
 * Created by Administrator on 2016/3/31.
 */
public enum AlarmPoints {
    STAR1,STAR2,LOBBY,OFFICE1,OFFICE2,OFFICE3,OFFICE4,BATHROOM,UTILITY,KITCHEN
}

/**
 * 我们使用enumSet来跟踪警报器的状态
 */
class EnumSets{
    public static void main(String[] args) {
        EnumSet<AlarmPoints> points = EnumSet.noneOf(AlarmPoints.class);//Empty set
        points.add(AlarmPoints.BATHROOM);
        System.out.println(points);
        //添加多个
        points.addAll(EnumSet.of(AlarmPoints.STAR1,AlarmPoints.STAR2,AlarmPoints.KITCHEN,AlarmPoints.OFFICE3));
        System.out.println(points);
        //去除office1到office4范围内的
        points.removeAll(EnumSet.range(AlarmPoints.OFFICE1,AlarmPoints.OFFICE4));
        System.out.println(points);
        //其他的
        points = EnumSet.complementOf(points);
        System.out.println(points);
    }
}
View Code

 

2.8 使用EnumMap

EnumMap是一种特殊的Map,它要求其中的key必须来自一个enum。由于enum本身的限制,所以EnumMap在内部可由数组实现。因此EnumMap速度很快。

下面演示了命令设计模式的用法。一般来说,命令模式首先需要一个只有单一方法的接口,然后从该接口实现具有各自不同的行为的多个子类。接下来,程序员就可以构造命令对象,并在需要的时候使用它们。

package com.test.java.tenum;

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

/**

 * Created by Administrator on 2016/3/31.
 */
public class EnumMaps {
    public static void main(String[] args){
        EnumMap<AlarmPoints,Command> em = new EnumMap<AlarmPoints, Command>(AlarmPoints.class);
        em.put(AlarmPoints.KITCHEN, new Command() {
            @Override
            public void action() {
                System.out.println("kitchen fire!");
            }
        });
        em.put(AlarmPoints.BATHROOM, new Command() {
            @Override
            public void action() {
                System.out.println("bathroom alert!");
            }
        });
        for (Map.Entry<AlarmPoints, Command> entry : em.entrySet()) {
            System.out.println(entry.getKey()+":");
            entry.getValue().action();
        }
        try {
            em.get(AlarmPoints.UTILITY).action();
        }catch (Exception e){
            System.out.println(e);
        }
    }
}
interface Command{
    void action();
}
View Code

与EnumSet一样,enum实例定义时的次序决定了其在EnumMap中的顺序。enum的每个实例作为一个键总是存在的,如果你没有为这个键调用put来存入相应的值,则对应的值为null。

与常量相关的方法相比,EnumMap有一个有点,允许程序员改变值对象,而常量相关的方法在编译器就被固定了。

 

2.9常量相关方法

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

package com.test.java.tenum;

import java.text.DateFormat;
import java.util.Date;

/**
 * Created by Administrator on 2016/3/31.
 */
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());
        }
    }
}
View Code

通过相应的enum实例,我们可以调用其上的方法。这通常也成为表驱动的代码(table-driven code),请注意它与前面提到的命令模式的相似之处。

在面向对象的程序设计中,不同的行为与不同的类关联。而通过常量相关的方法,每个enum实例可以具备自己独特的行为,这似乎说明每个enum实例就是一个特殊的类。我们并不能真的将enum实例当做一个类来使用。

与使用匿名内部类相比较,定义常量相关方法的语法更高效和简洁,下面是一个有趣的洗车的例子:

 

posted @ 2016-03-30 18:04  Ryan.Miao  阅读(2760)  评论(0编辑  收藏  举报