目录
1、枚举类的介绍及使用
2、枚举的实现原理
枚举类型是一种特殊的数据类型,它允许变量为一组预定义的常量。变量的值必须为其预定义的值之一。常见的例子包括罗盘方向(东、西、南、北)和一周的天数。
因为枚举类型的字段是常量,所以它们的名称都是大写字母。
在 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));
}
}
在使用关键字 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 中的枚举类原理及使用介绍完毕。