在某些情况下,一个类的对象是有限而且固定的,比如季节类,它只有4个对象:春、夏、秋、冬。这种实例有限而且固定的类,在Java里被称为枚举类。
1 手动实现枚举类
示例:
package com.demo2; public class Season { public static final Season SPRING = new Season("春天", "趁春踏青"); public static final Season SUMMER = new Season("夏天", "夏日炎炎"); public static final Season FALL = new Season("秋天", "秋高气爽"); public static final Season WINTER = new Season("冬天", "围炉观雪"); public static Season getSeason(int seasonNum) { switch (seasonNum) { case 1: return SPRING; case 2: return SUMMER; case 3: return FALL; case 4: return WINTER; default: return null; } } private final String name; private final String desc; private Season(String name, String desc) { this.name = name; this.desc = desc; } public String getName() { return name; } public String getDesc() { return desc; } }
package com.demo2; public class Test2 { public Test2(Season s) { System.out.println(s.getName() + "-----" + s.getDesc()); } public static void main(String[] args) { new Test2(Season.SPRING); } }
如果需要手动实现枚举,可以采用如下设计方式:
- 通过private把构造器隐藏起来。
- 把这个类的所有实例都使用public static final修饰的类变量来保存。
- 如果有必要,提供一些静态方法,允许其他程序根据特定参数来获取与之匹配的实例。
PS:有些程序员喜欢使用简单地定义几个静态常量来表示枚举类。
例如:
public static final int SEASON_SPRING=1; public static final int SEASON_SUMMER=2; public static final int SEASON_FALL=3; public static final int SEASON_WINTER=4;
这种方式存在几个问题:
- 类型不安全。例子中都是整型,所以季节可以进行+,-,*,/等int运算。
- 没有命名空间。这些静态常量只能靠前缀“SEASON_”来划分所属类型。
- 打印输出,意义不明确。如果使用System.out.println()打印SEASON_SPRING,输出是1,不是SPRING。
2 枚举类Enum
JDK 5新增了一个关键字Enum,它与class,interface的地位相同,用来定义枚举类。枚举类其实是一个特殊的类,它可以有自己的Field,方法,构造函数,可以实现一个或多个接口。
2.1 定义枚举类
示例:
package com.demo2; public enum SeasonEnum{ SPRING, SUMMER, FALL, WINTER; }
编译该段代码,将生成一个SeasonEnum.class文件,说明枚举类是一种特殊的类。
定义枚举类时,需要显式地列出所有的枚举值,例如上面的SPRING, SUMMER, FALL, WINTER;所示,所有的枚举值之间以英文逗号(,)隔开,枚举值列举结束后,以英文分号作为结束。这些枚举值代表了该枚举类的所有可能实例。
2.2 枚举类本质
将2.1中生成的SeasonEnum.class文件反编译:
javap -c SeasonEnum.class > SeasonEnum.txt
结果如下:
Compiled from "SeasonEnum.java" public final class com.demo2.SeasonEnum extends java.lang.Enum<com.demo2.SeasonEnum> { public static final com.demo2.SeasonEnum SPRING; public static final com.demo2.SeasonEnum SUMMER; public static final com.demo2.SeasonEnum FALL; public static final com.demo2.SeasonEnum WINTER; static {}; Code: 0: new #1 // class com/demo2/SeasonEnum 3: dup 4: ldc #15 // String SPRING 6: iconst_0 7: invokespecial #16 // Method "<init>":(Ljava/lang/String;I)V 10: putstatic #20 // Field SPRING:Lcom/demo2/SeasonEnum; 13: new #1 // class com/demo2/SeasonEnum 16: dup 17: ldc #22 // String SUMMER 19: iconst_1 20: invokespecial #16 // Method "<init>":(Ljava/lang/String;I)V 23: putstatic #23 // Field SUMMER:Lcom/demo2/SeasonEnum; 26: new #1 // class com/demo2/SeasonEnum 29: dup 30: ldc #25 // String FALL 32: iconst_2 33: invokespecial #16 // Method "<init>":(Ljava/lang/String;I)V 36: putstatic #26 // Field FALL:Lcom/demo2/SeasonEnum; 39: new #1 // class com/demo2/SeasonEnum 42: dup 43: ldc #28 // String WINTER 45: iconst_3 46: invokespecial #16 // Method "<init>":(Ljava/lang/String;I)V 49: putstatic #29 // Field WINTER:Lcom/demo2/SeasonEnum; 52: iconst_4 53: anewarray #1 // class com/demo2/SeasonEnum 56: dup 57: iconst_0 58: getstatic #20 // Field SPRING:Lcom/demo2/SeasonEnum; 61: aastore 62: dup 63: iconst_1 64: getstatic #23 // Field SUMMER:Lcom/demo2/SeasonEnum; 67: aastore 68: dup 69: iconst_2 70: getstatic #26 // Field FALL:Lcom/demo2/SeasonEnum; 73: aastore 74: dup 75: iconst_3 76: getstatic #29 // Field WINTER:Lcom/demo2/SeasonEnum; 79: aastore 80: putstatic #31 // Field ENUM$VALUES:[Lcom/demo2/SeasonEnum; 83: return public static com.demo2.SeasonEnum[] values(); Code: 0: getstatic #31 // Field ENUM$VALUES:[Lcom/demo2/SeasonEnum; 3: dup 4: astore_0 5: iconst_0 6: aload_0 7: arraylength 8: dup 9: istore_1 10: anewarray #1 // class com/demo2/SeasonEnum 13: dup 14: astore_2 15: iconst_0 16: iload_1 17: invokestatic #39 // Method java/lang/System.arraycopy:(Ljava/lang/Object;ILjava/lang/Object;II)V 20: aload_2 21: areturn public static com.demo2.SeasonEnum valueOf(java.lang.String); Code: 0: ldc #1 // class com/demo2/SeasonEnum 2: aload_0 3: invokestatic #47 // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum; 6: checkcast #1 // class com/demo2/SeasonEnum 9: areturn }
java.lang.Enum的源码结构如下:
根据字节码和java.lang.Enum源码结构可以得出如下结论:
- 使用enum定义的枚举类默认直接继承了java.lang.Enum类,而不是直接继承java.lang.Object。其中java.lang.Enum实现了java.lang.Comparable<E>, java.lang.Serializable两个接口。
- 使用enum定义、非抽象的枚举类默认会使用final修饰,因此非抽象枚举类不能派生子类。
- 枚举类的构造函数只能使用private访问控制符。如果省略了构造函数的访问控制符,默认会使用private修饰;如果强制指定访问控制符,只能使用private。
- 枚举类的所有实例必须在枚举类的第一行显示列出,否则这个枚举类永远不都不能产生实例。列出这些实例时,系统会自动添加public statci final修饰符,无须程序员显示添加。
Java实现枚举的本质:
- java.lang.Enum定义枚举对象的组成成分。一个枚举对象包含两个属性“ordinal”和“name”,一系列实例方法,例如toString、compareTo等等。“ordinal”是索引值,根据枚举声明的位置赋值,从0开始。“name”是枚举对象的名称。例如public enum SeasonEnum{SPRING, SUMMER, FALL, WINTER;}中,SPRING的索引值为0,name为“SPRING”;SUMMER的索引值为1,name为“SUMMER”.........。
- 编译时,编译器(javac)在自定义枚举类的.class文件中,添加static{}初始化块,初始化块包括生成枚举对象的指令。在示例字节码中,static{}初始化块里依次生成SPRING枚举对象并赋值索引值0,name为“SPRING”,生成SUMMER枚举对象并赋值索引值1,name为“SUMMER”..........最后,在SeasonEnum类中还定义了一个静态成员“ENUM$VALUES”,它是一个SeasonEnum数组,依据索引对象的“ordinal”值按顺序存放索引对象。
2.3 枚举类与switch结构
如果需要使用枚举类的某个实例,可以使用“枚举类.某个实例”的形式,例如SeasonEnum.SPRING。
示例:
package com.demo2; public class Test { public static void judge(SeasonEnum s) { switch (s) { case SPRING: System.out.println("春天,趁春踏青"); break; case SUMMER: System.out.println("夏天,夏日炎炎"); break; case FALL: System.out.println("秋天,秋高气爽"); break; case WINTER: System.out.println("冬天,围炉观雪"); break; } } public static void main(String[] args) { judge(SeasonEnum.FALL); } }
上面程序中的switch表达式中,使用了SeasonEnum对象作为表达式,这是JDK 5增加枚举后对switch的扩展:switch的控制表达式可以是任何枚举类型。不仅如此,当switch控制表达式使用枚举类型时,后面case表达式中的值直接使用枚举值名字,无须添加枚举类作为限定。
3 枚举类与构造函数
4 枚举类与接口
5 枚举类与抽象方法
待续