Java基础:枚举Enum

在某些情况下,一个类的对象是有限而且固定的,比如季节类,它只有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 枚举类与抽象方法

待续








郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。