Java 中的枚举类原理及使用详解

目录

1、枚举类的介绍及使用        

2、枚举的实现原理


1、枚举类的介绍及使用        

        枚举类型是一种特殊的数据类型,它允许变量为一组预定义的常量。变量的值必须为其预定义的值之一。常见的例子包括罗盘方向(东、西、南、北)和一周的天数。

        因为枚举类型的字段是常量,所以它们的名称都是大写字母。

        在 Java 中,可以使用 enum 关键字定义枚举类型。例如,可以指定一个星期的枚举类型为:

public enum Day {
    SUNDAY, MONDAY, TUESDAY, WEDNESDAY,
    THURSDAY, FRIDAY, SATURDAY 
}

        当需要表示一组固定的常量时,应该使用枚举类型。比如太阳系中的行星,以及在编译时就知道所有可能值的数据集——例如,菜单上的选项、命令行标志等等。

        为什么使用枚举?

        枚举定义常量比使用类定义常量更加简洁,使用枚举在类型安全、使用便捷性、重复值的校验上都要比使用类更加人性化。

        下面是一些代码,展示了如何使用上面定义的 Day 枚举:

public class EnumTest {
    Day day;
    
    public EnumTest(Day day) {
        this.day = day;
    }
    
    public void tellItLikeItIs() {
        switch (day) {
            case MONDAY:
                System.out.println("Mondays are bad.");
                break;
                    
            case FRIDAY:
                System.out.println("Fridays are better.");
                break;
                         
            case SATURDAY: case SUNDAY:
                System.out.println("Weekends are best.");
                break;
                        
            default:
                System.out.println("Midweek days are so-so.");
                break;
        }
    }
    
    public static void main(String[] args) {
        EnumTest firstDay = new EnumTest(Day.MONDAY);
        firstDay.tellItLikeItIs();
        EnumTest thirdDay = new EnumTest(Day.WEDNESDAY);
        thirdDay.tellItLikeItIs();
        EnumTest fifthDay = new EnumTest(Day.FRIDAY);
        fifthDay.tellItLikeItIs();
        EnumTest sixthDay = new EnumTest(Day.SATURDAY);
        sixthDay.tellItLikeItIs();
        EnumTest seventhDay = new EnumTest(Day.SUNDAY);
        seventhDay.tellItLikeItIs();
    }
}

        输出为:

Mondays are bad.
Midweek days are so-so.
Fridays are better.
Weekends are best.
Weekends are best.

        Java 的枚举类比其他语言中的枚举类强大得多。使用 enum 声明一个枚举类。枚举类主体可以包括方法和其他字段,同时编译器在创建枚举时也会自动添加一些特殊方法。例如,它们有一个静态值方法,该方法返回一个数组,该数组按照声明的顺序包含枚举的所有值。此方法通常与 for-each 构造结合使用,用来遍历枚举类的值。例如,下面的代码遍历 Planet 枚类中的所有值。

for (Planet p : Planet.values()) {
    System.out.printf("Your weight on %s is %f%n",
                      p, p.surfaceWeight(mass));
}

        注意:所有枚举都隐式地扩展 java.lang.Enum 类。因为 Java 不支持多重继承,一个类只能继承一个父类,所以枚举类不能再继承其他任何的类。

        例子中的 Planet 是一个枚举类,表示太阳系中的行星。行星的质量和半径一般都会保持不变,所以可以定义为固定的常量。

        Planet 类中的每个枚举常量都声明了质量和半径参数的值,这些值会在创建常量的时侯传递给构造函数。Java 要求先定义常量,然后再定义其他字段或方法。同样,当存在字段和方法时,枚举常量列表也必须以分号结束。// 枚举类中要求先定义常量列表

        注意:枚举类的构造函数必须是包私有或私有访问权限。它会自动创建在枚举体开头部分定义的常量,你不能自己调用枚举类的构造函数。

        除了定义枚举类的属性和构造函数外,在 Planet 类中还可以定义一些方法。比如,用来检索每个行星上物体的表面重力和重量。示例程序如下:// 枚举类它是一个类,用来定义常量,除了不能继承其他类,具备类的其他特征。

public enum Planet {
    MERCURY (3.303e+23, 2.4397e6),
    VENUS   (4.869e+24, 6.0518e6),
    EARTH   (5.976e+24, 6.37814e6),
    MARS    (6.421e+23, 3.3972e6),
    JUPITER (1.9e+27,   7.1492e7),
    SATURN  (5.688e+26, 6.0268e7),
    URANUS  (8.686e+25, 2.5559e7),
    NEPTUNE (1.024e+26, 2.4746e7);

    private final double mass;   // 千克
    private final double radius; // 米

    Planet(double mass, double radius) {
        this.mass = mass;
        this.radius = radius;
    }

    private double mass() { return mass; }
    private double radius() { return radius; }

    // 万有引力常数  (m3 kg-1 s-2)
    public static final double G = 6.67300E-11;

    // 方法:计算表面重力
    double surfaceGravity() {
        return G * mass / (radius * radius);
    }

    // 方法:计算表面重力
    double surfaceWeight(double otherMass) {
        return otherMass * surfaceGravity();
    }

    public static void main(String[] args) {
        if (args.length != 1) {
            System.err.println("Usage: java Planet ");
            System.exit(-1);
        }
        double earthWeight = Double.parseDouble(args[0]);
        double mass = earthWeight/EARTH.surfaceGravity();
        for (Planet p : Planet.values())
           System.out.printf("Your weight on %s is %f%n",
                             p, p.surfaceWeight(mass));
    }
}

2、枚举的实现原理

        在使用关键字 enum 创建枚举类并编译后,编译器会生成一个相关的类,这个类继承了 Java API 中的 java.lang.Enum 类,也就是说通过关键字 enum 创建的枚举类在编译后事实上也是一个类,而且该类继承了 java.lang.Enum 类。

        java.lang.Enum 类不允许直接写一个类进行继承,同时 Enum 类内部有一个构造函数,该构造函数只能被编译器调用,无法由程序员手动操作。

        下面定义 EnumDemo.java ,通过编译查看生成的 class 文件来验证这个结论:

public class EnumDemo {
    //定义枚举类型
    enum Day {
        MONDAY, TUESDAY, WEDNESDAY,
        THURSDAY, FRIDAY, SATURDAY, SUNDAY
    }

    public static void main(String[] args){
        //直接引用
        Day day =Day.MONDAY;
    }
}

        编译后生成的 class 文件:

$ ls                  //查看目录下的java文件
EnumDemo.java
$ javac EnumDemo.java //利用javac命令编译EnumDemo.java
$ ls                  //查看生成的class文件,注意有Day.class和EnumDemo.class 两个
Day.class  EnumDemo.class  EnumDemo.java

        利用 javac 编译前面定义的 EnumDemo.java 文件后分别生成了 Day.class 和 EnumDemo.class 文件,而 Day.class 就是枚举类,这也就验证了前面所说的使用关键字 enum 定义枚举类并编译后,编译器会自动帮助我们生成一个与枚举相关的类

        接下来再来看看反编译 Day.class 文件,真相都在这里了:

//反编译Day.class
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/zejian/enumdemo/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
        });
    }
}

        从反编译的代码可以看出,编译器确实帮我们生成了一个 Day.calss ,该类是 final 类型的,所以无法被继承,而且类继承了 java.lang.Enum 类(抽象类),也无法再继承其他类。

        除此之外,编译器还生成了 7 个 Day 类型的实例对象,分别对应枚举类中定义的 7 个日期,这也充分说明了使用关键字 enum 定义的 Day 类型中的每种日期枚举常量,也是实实在在的 Day 实例对象,只不过代表的内容不一样而已。// 定义的常量也是对象

        注意编译器还为我们生成了两个静态方法,分别是 values() 和 valueOf(),稍后还会分析它们的用法,到此我们也就明白了,使用关键字 enum 定义的枚举类型,在编译后,也会转换成为一个实实在在的类,在该类中,会存在每个在枚举类型中定义好的变量的对应实例对象,如上述的 MONDAY 枚举类型对应 public static final Day MONDAY,同时编译器还会为该类创建两个方法,分别是 values() 和 valueOf()。

        ok~,到此相信我们对枚举的实现原理也比较清晰了,下面深入了解一下 java.lang.Enum 类以及 values() 和 valueOf() 的用途。

        Java 从 JDK1.5 开始支持枚举,也就是说,Java 一开始是不支持枚举的,就像泛型一样,都是 JDK1.5 才加入的新特性。通常一个特性如果在一开始没有提供,在语言发展后期才添加,就会遇到向后兼容性的问题。所以,像 Java 在 1.5 中引入的很多特性,为了向后兼容,编译器会帮我们写的源代码做很多事情,比如,泛型为什么会存在类型擦除,为什么会生成桥接方法,foreach 的迭代,自动装箱/拆箱等,这里有个术语叫“语法糖”,而编译器的特殊处理就是“解语法糖”。// Java 的语法糖

        因此,枚举实际上就是对定义常量的一个新的封装,只是对封装处理都由编译器来做。

        编译器生成的 values() 方法与 valueOf() 方法

        values() 方法和 valueOf(String name) 方法是编译器生成的 static 方法,下面通过代码来演示这两个方法的作用:

Day[] days2 = Day.values();
System.out.println("day2:"+Arrays.toString(days2));
Day day = Day.valueOf("MONDAY");
System.out.println("day:"+day);

/**
 输出结果:
 day2:[MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY]
 day:MONDAY
 */

        从结果可知道,values() 方法的作用就是获取枚举类中的所有变量,并作为数组返回,而 valueOf(String name) 方法的作用就是根据名称获取枚举变量,由于 values() 方法是由编译器插入到枚举类中的 static 方法,所以如果将枚举实例向上转型为 Enum,那么 values() 方法将无法被调用,因为 Enum 类中并没有values()方法,valueOf() 方法也是同样的道理。

Day[] ds=Day.values();  //正常使用
Enum e = Day.MONDAY;    //向上转型Enum
e.values();             // error...无法调用,没有此方法

        至此,Java 中的枚举类原理及使用介绍完毕。

你可能感兴趣的:(Java,enum,枚举类,枚举原理,Java,枚举类)