zoukankan      html  css  js  c++  java
  • [转载] Java中枚举类型的使用

    本文转载自博客 - Java枚举类型, 博主对原文内容及结构作了一定的修改.

    修改时参考到的文章:深入理解Java枚举类型(enum).

    1 枚举类的编译特性

    从JDK 5开始, Java中多了一个关键字 —— enum: 可以将一组具有名称的值(包括String、Integer等)的有限集合创建为一种新的类型, 而这些具名的值可以作为常规的程序组件使用.

    这些具名的值称为枚举值, 这种新的类型称为枚举类型.

    1.1 枚举类演示

    下面是一个简单的表示星期几的枚举类:

    enum Day {
        // 结尾处可以不用“;”, 但若有其他方法, 必须通过“;”结束枚举实例的声明
        SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY
    }
    

    创建enum时, 编译器会自动为enum类添加一些特性, 比如:

    a) 创建toString()方法: 以便显示某个枚举实例的名字;
    b) 创建name()方法: 获取枚举类型中某个实例的名称;
    c) 创建ordinal()方法: 表示某个特定枚举常量的申明顺序, 从0开始;
    d) 创建values()方法: 按照枚举常量的申明顺序产生这些常量构成的数组…...

    关于这些特性, 编写测试方法如下:

    public class EnumTest {
        public static void main(String[] args) {
            // 获取枚举类型的父类型
            System.out.println(Day.class.getSuperclass());
            // 遍历枚举类型中的值
            for (Day day : Day.values()) {
                System.out.println(day.name() + ", ordinal: " + day.ordinal());
            }
        }
    }
    

    程序的输出结果是:

    class java.lang.Enum   // 所有enum的父类都是它
    SUNDAY, ordinal: 0     // ordinal()方法从0开始计数
    MONDAY, ordinal: 1
    TUESDAY, ordinal: 2
    WEDNESDAY, ordinal: 3
    THURSDAY, ordinal: 4
    FRIDAY, ordinal: 5
    SATURDAY, ordinal: 6
    

    从输出结果可以看到: 枚举类型Day的父类是java.lang.Enum——我们的代码中并没有指明extends动作, 所以这是由编译器完成的.

    1.2 反编译查看编译器的操作

    (1) 利用javac编译EnumTest.java文件后, 会生成Day.classEnumTest.class文件, 这里的Day.class就是枚举类型 —— 使用关键字enum定义枚举类型并编译后, 编译器会自动生成一个与枚举相关的类;

    (2) 反编译查看Day.class文件:

    // final修饰的类, 不允许再被继承
    final class Day extends Enum {
     // 编译器添加的静态values()方法
     public static Day[] values() {
         return (Day[])$VALUES.clone();
     }
     // 编译器添加的静态valueOf()方法 —— 间接调用了Enum类的valueOf()方法
     public static Day valueOf(String s) {
         return (Day)Enum.valueOf(com/healchow/java/Day, s);
     }
     // 私有构造函数, 不允许被外部调用
     private Day(String s, int i) {
         super(s, i);
     }
      // 前面定义的7种枚举实例, 都是静态常量
     public static final Day MONDAY;
     public static final Day TUESDAY;
     public static final Day WEDNESDAY;
     public static final Day THURSDAY;
     public static final Day FRIDAY;
     public static final Day SATURDAY;
     public static final Day SUNDAY;
     private static final Day $VALUES[];
    
     static {    
         // 实例化枚举实例, 并指定声明的次序
         MONDAY = new Day("MONDAY", 0);
         TUESDAY = new Day("TUESDAY", 1);
         WEDNESDAY = new Day("WEDNESDAY", 2);
         THURSDAY = new Day("THURSDAY", 3);
         FRIDAY = new Day("FRIDAY", 4);
         SATURDAY = new Day("SATURDAY", 5);
         SUNDAY = new Day("SUNDAY", 6);
         $VALUES = (new Day[] {
             MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
         });
     }
    }
    

    (3) 编译器生成的静态方法:

    values(): 获取枚举类中的所有变量, 并作为数组返回;
    valueOf(String name): 与Enum类中的valueOf()的作用类似 —— 根据名称获取枚举变量, 只不过编译器生成的valueOf()更简洁: 只需传递一个参数.

    ==> 这两个方法是编译器自动加入的, Enum类中并没有, 所以如果我们将枚举实例向上转型为Enum, 那这两个方法就无法被调用了.

    (4) 枚举类不能继承其他任何类:

    由于枚举类已经继承了java.lang.Enum(是一个抽象类), 而Java又不支持多继承, 所以enum不能再继承其他的类, 但是能实现接口. —— 这在反编译后的信息中可以看到, 编译器为enum生成的class被final修饰, 也就是不允许被继承.

    ==> 所以, enum只是看起来像一种新的数据类型, 除了上面讲到的这些特殊的编译行为, 并没有什么特殊的地方.

    2 向枚举类中添加方法

    除了不能继承一个enum外, 基本上可以把enum当作一个普通的类来处理, 也就是说可以向enum中添加方法, 比如返回其自身描述的方法, 还可以添加main方法.

    下面是一个演示enum添加自定义方法和实现接口的示例:

    (1) 定义一个对象描述信息的接口:

    interface ObjectDescription {
        String todo();
    }
    

    (2) 创建枚举类:

     public enum Signal implements ObjectDescription {
        // 结尾处可以不用“;”, 但若有其他方法, 必须通过“;”结束枚举实例的声明
        Red("红灯", "敢过去就是6分, 还要罚款哦"), 
        Yellow("黄灯", "黄灯你也不敢过"), 
        Green("绿灯", "绿灯也得小心过啊");
    
        // 其他属性、方法都必须定义在枚举实例的声明之后, 否则编译器将报错
        private String name;
        private String description;
    
       /**
        * 构造方法, 对内部变量进行初始化
        */
        Signal(String name, String description) {
            this.name = name;
            this.description = description;
        }
    
        public String getName() {
            return name;
        }
    
        public String getDescription() {
            return description;
        }
    
        @Override
        public String todo() {
            return "Signal类用于表示交通信号灯, [" + this + "] 表示 [" + this.getName() + "]";
        }
    
        public static void main(String[] args) {
            // 调用实现的接口中的方法
            for (Signal signal : Signal.values()) {
                System.out.println(signal.todo());
            }
            // 调用自定义的方法
            for (Signal signal : Signal.values()) {
                System.out.println(signal.getName() + ": " + signal.getDescription());
            }
        }
    }
    

    (3) 运行结果如下:

    Signal类用于表示交通信号灯, [Red] 表示 [红灯]
    Signal类用于表示交通信号灯, [Yellow] 表示 [黄灯]
    Signal类用于表示交通信号灯, [Green] 表示 [绿灯]
    红灯: 敢过去就是6分, 还要罚款哦
    黄灯: 黄灯你也不敢过
    绿灯: 绿灯也得小心过啊
    

    使用注意事项:

    a) 如果要自定义方法, 就必须在enum实例序列的最后添加一个分号, 同时Java要求必须先定义enum实例, 否则编译时会报错.
    b) enum的构造器只能是private, 它只能在enum内部被用来创建enum实例, 一旦enum定义结束, 编译器就不允许再使用其构造器来创建任何实例了.

    3 枚举类中使用抽象方法

    与常规抽象类一样, 枚举类允许我们为其定义抽象方法, 然后灵每个枚举实例都实现该方法, 以便产生不同的行为方式.

    注意: abstract关键字对于枚举类来说并不是必须的.

    public enum EnumTest {
        // 枚举实例的声明必须在最前
        FIRST {
            // 实现抽象方法
            @Override
            public String getInfo() {
                return "FIRST TIME";
            }
        },
        SECOND {
            // 实现抽象方法
            @Override
            public String getInfo() {
                return "SECOND TIME";
            }
        }
        
        ;  // 如果之后还有其他成员, 就必须用“;”结束
    
        /**
         * 定义抽象方法
         * @return
         */
        public abstract String getInfo();
    
        //  测试
        public static void main(String[] args) {
          
            System.out.println("First: " + EnumTest.FIRST.getInfo());    // First: FIRST TIME
            System.out.println("Second: " + EnumTest.SECOND.getInfo());  // Second: SECOND TIME
        }
    }
    

    上述方式为每个枚举实例定义了不同的行为.

    我们可能注意到, 枚举类的实例似乎表现出了多态的特性, 可惜的是枚举类型的实例终究不能作为类型传递使用, 就像下面的使用方式, 是不能通过编译器的检查的:

    // 无法通过编译, 毕竟EnumTest.FIRST是个实例对象
    public void text(EnumTest.FIRST instance){ }
    

    4 接口内部创建枚举

    无法使用继承限制了枚举的使用, 比如需要用enum表示食物, 但同时需要区分水果、点心等类型, 这个时候就没不够灵活了.

    我们通过在一个接口内部创建实现该接口的枚举, 从而达到对元素进行分类组织的目的:

    public interface Food {
        /**
         * 开胃菜
         */
        enum Appetizer implements Food {
            // 结尾处可以不用“;”, 但若有其他方法, 必须通过“;”结束枚举实例的声明
            SALAD, SOUP, SPRING_ROLLS
        }
      
        /**
         * 主菜
         */
        enum MainCourse implements Food {
            RICE, NOODLES, VINDALOO, BEEF
        }
    
        /**
         * 甜品
         */
        enum Dessert implements Food {
            TIRAMISU, ICE_CREAM, BISCUIT, FRUIT
        }
      
        /**
         * 咖啡
         */
        enum Coffee implements Food {
            BLACK_COFFEE, DECAF_COFFEE, LATTE
        }
    }
    

    enum而言, 实现接口是使其子类化的唯一方法.

    通过上面的形式, 成功地完成了对不同食物的分组, 并且它们都是Food.

    5 枚举类中使用枚举

    下面是一个枚举的随机选择器, 是一个工具类:

    public class Enums {
        private static Random rand = new Random(47);
    
        public static <T extends Enum<T>> T random(Class<T> enumClazz) {
            return random(enumClazz.getEnumConstants());
        }
    
        public static <T> T random(T[] values) {
            return values[rand.nextInt(values.length)];
        }
    }
    

    结合工具类及上面Food接口的内容, 下面通过枚举的枚举实现一个产生随机菜单的例子:

    public enum Course {
        // 结尾处可以不用“;”, 但若有其他方法, 必须通过“;”结束枚举实例的声明
        APPETIZER(Food.Appetizer.class),
        MAINCOURSE(Food.MainCourse.class), 
        DESSERT(Food.Dessert.class), 
        COFFEE(Food.Coffee.class);
        
        // 其他属性、方法都必须定义在枚举实例的声明之后, 否则编译器将报错
        private Food[] values;
    
        Course(Class<? extends Food> kind) {
            // 返回枚举中所有的元素, 及所有实例构成的数组, 如果kind不是枚举返回null
            values = kind.getEnumConstants();
        }
    
        public Food randomSelection() {
            return Enums.random(values);
        }
    
        public static void main(String[] args) {
            // 产生5份随机菜单
            for (int i = 0; i < 5; i++) {
                for (Course c : Course.values()) {
                    Food food = c.randomSelection();
                    System.out.println(food + "  ");
                }
                System.out.println("---------------");
            }
        }
    }
    

    6 扩展: 验证values()不是通过父类继承的

    下面的代码用来验证values()方法是enum自身的, 而不是继承自父类java.lang.Enum的:

    public enum Signal implements ObjectDescription {
        // 结尾处可以不用“;”, 但若有其他方法, 必须通过“;”结束枚举实例的声明
        Red("红灯", "敢过去就是6分, 还要罚款哦"), 
        Yellow("黄灯", "黄灯你也不敢过"), 
        Green("绿灯", "绿灯也得小心过啊");
    
        // 其他属性、方法都必须定义在枚举实例的声明之后, 否则编译器将报错
        private String name;
        private String description;
    
        Signal(String name, String description) {
            this.name = name;
            this.description = description;
        }
    
        public String getName() {
            return name;
        }
    
        public String getDescription() {
            return description;
        }
    
        @Override
        public String todo() {
            return "Signal类用于表示交通信号灯, [" + this + "] 表示 [" + this.getName() + "]";
        }
    
        public static void main(String[] args) {
            Set<Method> methodSet = new HashSet<Method>();
            // 获取本类的所有方法
            Method[] signalMethods = Signal.class.getMethods();
            for (Method m : signalMethods) {
                methodSet.add(m);
            }
            // 获取父类中的方法
            Method[] superClassMethods = Signal.class.getSuperclass().getMethods();
            // 去除本类中继承的父类方法
            for (Method m : superClassMethods) {
                methodSet.remove(m);
            }
            // 遍历输出本类中独有的方法
            for(Method m : methodSet) {
                System.out.println(m);
            }
        }
    }
    

    结果如下, 其中各个字段的含义依次为访问权限 [是否静态] 返回值类型的全限定名称 方法的全限定名称:

    public static com.healchow.Signal com.healchow.Signal.valueOf(java.lang.String)
    public static com.healchow.Signal[] com.healchow.Signal.values()
    public static void com.healchow.Signal.main(java.lang.String[])
    public java.lang.String com.healchow.Signal.todo()
    

    版权声明

    本文版权归原作者所有, 如有侵权, 请联系博主, 定当立即删除.

    若要转载, 请在文章页面明显位置标明原始链接, 否则一切责任自负.

  • 相关阅读:
    聊天类功能测试用例
    即时通讯软件针对通讯以及协议方面有哪些测试点?
    面试前期准备工作
    黑盒功能业务测试过程
    Web网站实现facebook登录
    Nginx配置SSL实现HTTPS访问
    jQuery判断当前页面是APP内打开还是浏览器打开
    jQuery实现点击图片简单放大效果
    Linux排查PHP-FPM进程过量常用命令
    PHP防止SQL注入攻击和XSS攻击
  • 原文地址:https://www.cnblogs.com/shoufeng/p/10683788.html
Copyright © 2011-2022 走看看