Java学习之枚举类型

简介

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



基本enum特性

通过values()方法可以遍历enum实例,而且该数组中的元素严格保持在enum中声明时的顺序。
创建enum时,编译器会生成一个相关的类,这个类继承自java.lang.Enum,下面例子中演示了Enum提供的一些功能:

public class EnumDemo1 {
    
    
    public static void main(String[] args) {
    
    
        for (Fruit fruit:Fruit.values()){
    
    
            System.out.println(fruit.ordinal());//返回声明的次序
            System.out.print(fruit.compareTo(Fruit.APPLE)+" ");
            System.out.print(fruit.equals(Fruit.APPLE)+" ");
            System.out.println(fruit == Fruit.APPLE);
            System.out.println(fruit.getDeclaringClass());
            System.out.println(fruit.name());
            System.out.println("------------------------------");

            /**
             * 0
             * 0 true true
             * class org.example.enummmm.Fruit
             * APPLE
             * ------------------------------
             * 1
             * 1 false false
             * class org.example.enummmm.Fruit
             * BANANA
             * ------------------------------
             * 2
             * 2 false false
             * class org.example.enummmm.Fruit
             * ORANGE
             * ------------------------------
             */
        }
        /**
         * APPLE
         * BANANA
         * ORANGE
         */
    }
}
enum Fruit{
    
    
    APPLE,BANANA,ORANGE
}


向enum中添加新方法

除了不能继承自一个enum之外,我们基本上可以将enum看作一个常规的类,也就是说,我们可以向enum中添加方法。enum甚至可以有main方法。
一般来说,我们希望每个枚举实例都能够携带一些对自身的描述信息,而不仅仅是作为一个简单的标识集合。为此,我们可以提供一个构造器,专门负责处理这个额外的信息,然后添加一个方法,返回这个描述信息。

public class EnumDemo2 {
    
    
    public static void main(String[] args) {
    
    
        for(Car car:Car.values()){
    
    
            System.out.println(car.name()+": "+car.getBrand());
        }
        /**
         * BMW: 宝马
         * BENZ: 奔驰
         */
    }
}

enum Car{
    
    
    BMW("宝马"),
    BENZ("奔驰");

    private String brand;
    Car(String brand){
    
    this.brand = brand;}

    public String getBrand(){
    
    
        return brand;
    }
}



switch语句中的enum

在switch语句中使用枚举类是一个非常便利的方法,它可以通过枚举类来限定switch的选项

public class EnumDemo3 {
    
    
   public static void main(String[] args) {
    
    
       Fruit fruit = Fruit.APPLE;
       switch (fruit){
    
    
           case APPLE:
               System.out.println("这是苹果");
               break;
           case BANANA:
               System.out.println("这是香蕉");
               break;
           case ORANGE:
               System.out.println("这是香蕉");
               break;
           default:
               break;
       }
       /**
        * 这是苹果
        */
   }
}
enum Fruit{
    
    
   APPLE,BANANA,ORANGE
}


values()的神秘之处

前面提到,我们的enum类全部都继承自Enum类。然而,如果看一下Enum类我们就会发现并没有values()方法。事实上,values()是由编译器添加的static方法。因此,如果将enum类向上转型为Enum将无法使用values()方法。不过,在Class中有一个getEnumConstants()方法,所以即使不使用values()我们仍然可以通过Class对象取获取所有enum实例

public static void main(String[] args) {
    
    
        for(Fruit fruit:Fruit.class.getEnumConstants()){
    
    
            System.out.println(fruit.name());
        }
    }
enum Fruit{
    
    
        APPLE,BANANA,ORANGE
    }


使用接口组织枚举

有些情况下我们会希望通过枚举来进行分组,但由于枚举类无法被继承,所以我们可以这么做

public static void main(String[] args) {
    
    
        Food food = Food.Meat.BEEF;
    }

interface Food{
    
    
        enum Fruit implements Food{
    
    
            APPLE,ORANGE
        }
        enum Meat implements Food{
    
    
            CHICKEN,BEEF
        }
    }

对于枚举类而言,实现接口是其子类化的唯一方法。因此,现在Food接口中的所有类型它都可以认为是一个食物

但是当我们与一大堆类打交道时,接口就不如enum好用了。因此我们还有一种方式来创建一种枚举的枚举

public class EnumDemo4 {
    
    
    public static void main(String[] args) {
    
    
        for (FoodKind kind:FoodKind.values()){
    
    
            for (Food food:kind.getValues()){
    
    
                System.out.println(kind+": "+food);
            }
        }
    }

    interface Food{
    
    
        enum Fruit implements Food{
    
    
            APPLE,ORANGE
        }
        enum Meat implements Food{
    
    
            CHICKEN,BEEF
        }
    }

    enum FoodKind{
    
    
        FRUIT(Food.Fruit.class),
        MEAT(Food.Meat.class);
        private Food[] values;

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

        public Food[] getValues(){
    
    
            return values;
        }
    }

}

在上面例子中我们充分利用了可以通过Class来获取枚举实例的特性来完成枚举中的枚举这一结构。

首先,如果我们希望一个类中可以嵌套另一种类,同时通过这种结构实现分组。我们通常会选择使用继承来实现,比如Meat类继承Food类。但是很明显,枚举类之间不存在继承关系,所以我们只能退而求其次,通过接口来实现一个继承结构。当我们通过在Food接口实现多个枚举类时,看似已经完成了分组。事实上这种分组并没有完全使用到枚举类的便利性(values(),枚举类实例等)。因此我们选择让一个新的枚举类去持有Food对象的类引用来完成这种包含关系。因为通过类对象就可以完成values()的功能。



使用EnumSet来代替标志

Set是一种集合,只能向其中添加不重复的对象。当然,enum也要求其成员都是唯一的,所以enum看起来也具有集合的行为。不过由于不能从enum中增加或者删除元素。因此这种集合并没什么用。JavaSE5引入了EnumSet,是为了通过enum创建一种替代品,以替代传统的基于int的”位标志“。这种标志可用来标识某种开关信息。
EnumSet的设计充分考虑到了速度因素,因为它必须和非常高效的bit标志相竞争(其操作与HashSet相比,非常的快)。

EnumSet中的元素顺序是与枚举中声明的顺序是一致的

EnumSet中的元素必须来自于一个enum。下面的enum表示在一座大楼中,警报传感器的安放位置

enum AlarmPoints{
    
    
        STAIR1,STAIR2,LOBBY,OFFICE1,OFFICE2,OFFICE3,
        OFFICE4,BATHROOM,KITCHEN
    }

然后我们使用EnumSet来跟踪警报的状态

public static void main(String[] args) {
    
    
        EnumSet<AlarmPoints> points = EnumSet.noneOf(AlarmPoints.class);
        points.add(BATHROOM);
        points.addAll(EnumSet.of(OFFICE1,OFFICE2,OFFICE3));
        System.out.println(points);
        points = EnumSet.allOf(AlarmPoints.class);
        points.remove(BATHROOM);
        System.out.println(points);
        points = EnumSet.complementOf(points);
        System.out.println(points);
        /**
         * [OFFICE1, OFFICE2, OFFICE3, BATHROOM]
         * [STAIR1, STAIR2, LOBBY, OFFICE1, OFFICE2, OFFICE3, OFFICE4, KITCHEN]
         * [BATHROOM]
         */

    }


使用EnumMap

EnumMap是一种特殊的Map,它要求其中的键必须来自于enum,由于enum本身的限制,所以EnumMap在内部由数组实现。因此EnumMap的速度很快。棕的来说操作起来与一般的Map差不多。

public static void main(String[] args) {
    
    
        EnumMap<Fruit,String> map = new EnumMap<Fruit, String>(Fruit.class);
        map.put(Fruit.APPLE,"新疆阿克苏苹果");
        map.put(Fruit.BANANA,"泰国大香蕉");

        System.out.println(map.get(Fruit.BANANA));
        /**
         * 泰国大香蕉
         */
    }

    enum Fruit{
    
    
        APPLE,ORANGE,BANANA
    }

EnumMap中的元素顺序是与枚举中声明的顺序是一致的。同时enum的每个实例作为键,总是存在的,但是如果没有为其调用put存入相应的值,其对应的值就是null



常量相关的方法

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

public class EnumDemo7 {
    
    
    public static void main(String[] args) {
    
    
        for(ConstantSpecificMethod method:ConstantSpecificMethod.values()){
    
    
            System.out.println(method.getInfo());
        }
    }

    enum ConstantSpecificMethod{
    
    
        DATE_TIME{
    
    
            @Override
            String getInfo(){
    
    
                return DateFormat.getInstance().format(new Date());
            }
        },

        VERSION{
    
    
            @Override
            String getInfo(){
    
    
                return  System.getProperty("java.version");
            }
        };

        abstract String getInfo();
    }
}

在面向对象的设计中,不同的行为与不同的类关联接。而通过常量相关的方法,每个enum实例可以具有自己独特的行为。在上面例子中,enum实例似乎被当作了其基类ConstantSpecificMethod来使用,在调用getInfo()方法时体现出多态的行为。

然而,虽然这种形式与继承中的多态相似,但事实上实例终究只是实例,无法真正地被当作一个类型进行使用。因为enum的每个实例都是enum类型的static final实例。


覆盖常量相关的方法

除了实现抽象方法以外,我们还可以覆盖常量相关的方法

public class EnumDemo8 {
    
    
    public static void main(String[] args) {
    
    
        for(OverrideConstantSpecific method:OverrideConstantSpecific.values()){
    
    
            System.out.println(method.getInfo());
        }
        /**
         * 这是一种好吃的水果!
         * 这是一种好吃的香蕉
         */
    }
    enum OverrideConstantSpecific{
    
    
        APPLE,
        BANANA{
    
    
            @Override
            public String getInfo() {
    
    
                return "这是一种好吃的香蕉";
            }
        };

        public String getInfo(){
    
    
            return "这是一种好吃的水果!";
        }
    }
}



使用enum的职责链

在职责链模式中我们可以使用不同的方式来解决一个问题,然后将它们链接在一起。当一个请求到来时,它遍历这个链,直到链中的某个解决方案能够处理该请求。
而通过常量相关方法我们就能够实现一个类多个解决方案。而对于链的遍历我们也可以通过values()方法来迅速实现。

举例,在公司里,我们经常会遇到有急事需要请假的情况,那这时候我们需要上上级提出请假申请,如果上级同意我们才能被允许请求。但是不同的请假天数需要不同级别的上级批准,你如果请一天,通常组长同意即可,如果请三天,可能就可能不仅需要组长的同意还需要部门老大的同意,如果请一个月,那还需要加上老板的同意(但是注意,请这么久,老板大概率时让你走人的)。因此我们现在根据这个例子实现

首先我们定义一个假条类,包含请假的天数和请假的理由

class RequestForLeave{
    
    
    private Integer day;
    private String reason;

    public RequestForLeave(Integer day, String reason) {
    
    
        this.day = day;
        this.reason = reason;
    }

   //省略getset
}

然后定义处理请求的enum链。

enum RequestHandler{
    
    
    GROUP_HEAD{
    
    
        @Override
        public boolean handle(RequestForLeave request) {
    
    
            if(request.getDay()<3){
    
    
                return true;
            }
            System.out.println("请假超过3天,组长处理不了");
            return false;

        }
    },
    DEPARTMENT_HEAD{
    
    
        @Override
        public boolean handle(RequestForLeave request) {
    
    
            if(request.getDay()<30 && request.getDay()>=3){
    
    
                return true;
            }else if(request.getDay() <3){
    
    
                System.out.println("请假时间太短了,不想处理");
                return false;
            }
            System.out.println("请假超过30天,部长处理不了");
            return false;

        }
    },
    BOSS{
    
    
        @Override
        public boolean handle(RequestForLeave request) {
    
    
            if(request.getDay()>=30){
    
    
                System.out.println("请假时间太长了,还干啥。去财务领这个月薪水走人吧");
                return false;
            }
            System.out.println("请假时间太短了,不想处理");
            return false;

        }
    };

    abstract boolean handle(RequestForLeave request);
}

场景类
现在我们有两份假条,一份时10天的,要回家二婚,因此组长拿到请求时发现处理不了,然后到了部长手里,部长发现有一个10天的假条,是要去二婚,想想下属太辛苦了,二婚还是要放的。但是发现了第二封要请100天。那部长哪敢处理,只能等着老板来处理,老板发现,好家伙,要请100天。直接滚蛋吧。

public class EnumDemo9 {
    
    
    public static void main(String[] args) {
    
    
        RequestForLeave request1 = new RequestForLeave(10,"二婚");
        RequestForLeave request2 = new RequestForLeave(100,"八十大寿");
        handle(request1);
        System.out.println("-------------------");
        handle(request2);
        /**
         * 请假超过3天,组长处理不了
         * 请假成功
         * -------------------
         * 请假超过3天,组长处理不了
         * 请假超过30天,部长处理不了
         * 请假时间太长了,还干啥。去财务领这个月薪水走人吧
         * 请假失败!
         */

    }

    public static void handle(RequestForLeave request){
    
    
        for (RequestHandler handler:RequestHandler.values()){
    
    
            if(handler.handle(request)) {
    
    
                System.out.println("请假成功");
                return;
            }
        }
        System.out.println("请假失败!");
    }
}

猜你喜欢

转载自blog.csdn.net/qq_33905217/article/details/109722775