关键字enum可以将一组具名的值的有限集合创建为一种新的类型,就是枚举类型。枚举类型是Java 1.5新增的特性,这是一个非常有用的特性。
Enum
在这一小部分,我们将探讨枚举的最基本的用法。首先我们必须要明白一点:enum是一个常规类型,只不过它还有一些限制。定义枚举类型很简单,使用enum关键字把所有的值组织起来,就像下面一样:
enum Season {SPRING,SUMMER,FULL,WINTER}
Season拥有四个实例,在一般的命名规范中,通常用大写的字母表示枚举的实例。枚举类型的实例是有序的,每个实例都有一个对应的序号,从0开始,由于enum天然的有序性,可以在switch()语句中使用枚举示例,这样语义更清晰。在创建enum时,编译器会自动生成一个相关的类,这个类继承自java.lang.Enum,Enum是一个普通类,它提供了一些方法,其中有几个是需要我们关注的:
public final int ordinal():返回枚举常量的序号,从0开始,也就是SPRING的序号是0,SUMMER是1,.....
public static <T extends Enum<T>> T valueOf(Class<T> enumType,String name):将给定的name转换成对应枚举类的实例,name必须要与枚举类中的常量完全匹配,它的实现方式大概是这样:在Enum的内部有一个方法,它把枚举的名称和枚举常量组成一个Map,调用valueOf()方法时,就是去Map中get(name);
出了上面的两个方法比较重要外,它还两个比较诡异的方法,待会再解释一下这两个方法:
public static T[] values():返回枚举的所有常量
public static <T extends Enum<T>> T valueOf(String name):将给定的字符串转换成枚举常量,和valueOf(Class<T> enumType,String name)方法类似。
enum中的方法
enum可以添加构造方法,还可以添加自定义的方法,还可以覆盖父类中的方法,所有的这一切都和普通的类一样,只不过会有一些小限制,还是以Season为例,演示enum中的基本用法
package com.think.enumeration;
public enum Season {
//Java要求必须先定义枚举实例,
SPRING(10,20),
SUMMER(20,32),
FULL(10,20),
//这就是哈尔滨的真实写照
WINTER(-40,-5);//注意这个分号,比不可少
private double lowTemp;
private double highTemp;
//一旦enum定义结束,编译器就不允许我们再使用
//其构造器来创建任何实例了,所以即使不是private也没关系
private Season(double low,double high) {
lowTemp = low;
highTemp = high;
}
//自定义的方法,就像在普通的类中一样
public void setLow(double low) {
this.lowTemp = low;
}
//还可以覆盖父类中的方法
@Override
public String toString() {
return "(" + lowTemp + "," + highTemp + ")";
}
//甚至还可以有main方法
public static void main(String[] args) {
for(Season e : Season.values()) {
if(e == SPRING)
e.setLow(-10);
System.out.println(e);
}
}
}
我们一定要抓住enum的本质:
它是一组常量的集合。所以很多限制都是为了确保“常量性”,比如在定义枚举类时,必须先定义enum,它的构造方法在定义enum实例结束后就不能使用。出了围绕常量性的限制之外,其他的语法与普通类无异,我们可以添加普通方法,可以覆盖父类方法等。在所有的特性中,最具特色的要数
常量相关方法。
在enum中除了给enum类型添加方法之外,每个实例还可以有自己的方法,这样的方法叫做常量相关方法:对于同一个方法,不同的实例可以实现自己的行为。这样甚至有了多态性。实现常量相关的方法时,可以将一个方法实现成抽象方法,每个实例提供具体实现;也可以把方法实现成普通方法,每个实例根据需求,选择是否覆盖该方法,示例如下:
public enum ConstantSpecificMethod {
MALE {
//常量相关
public String getSex() {
return "male";
}
//覆盖公共方法
public void f() {
System.out.println("male and male and ");
}
},
FEMALE {
@Override
public String getSex() {
return "female";
}
};
//将方法定义为抽象方法
abstract public String getSex();
//提供公有的实现,实例可以考虑是否覆盖
public void f() {
System.out.println("nothing else");
}
}
对于abstract方法,每个实例必须提供具体实现,对于提供的默认实现,每个实例课根据实际选择是否覆盖。通过常量相关方法,使枚举实例间具有了多态性,但是枚举实例毕竟不是class类型。
编译器的动作
编译器会为enum类型生成class文件,相应的enum类自动继承Enum<T>,这是编译器的常规处理。在前面我们提到了enum类型有values()方法和只有一个参数的valueOf()方法,但是遍查API文档,在Enum类中并没有发现这两个方法,而在前面的例子中我们又确确实实的使用了values()方法,这是怎么一回事呢?下面是根据前面的Season的class文件反编译的结果,选择其中的一部分:
/**
* 下面是编译器为我们生成的的对应的类
* 类被标记为final,这意味着它不可被继承
* 自动继承Enum<T>,Enum中的所有方法它都可以使用,但是它不能再继承其他类了
*/
public final class com.think.enumeration.Season extends java.lang.Enum<com.think.enumeration.Season> {
//所有的实例都被实现为final的
public static final com.think.enumeration.Season SPRING;
public static final com.think.enumeration.Season SUMMER;
public static final com.think.enumeration.Season FULL;
public static final com.think.enumeration.Season WINTER;
static {};
Code:
0: new #1 // class com/think/enumeration/Season
3: dup
4: ldc #18 // String SPRING
6: iconst_0
7: ldc2_w #19 // double 10.0d
10: ldc2_w #21 // double 20.0d
13: invokespecial #23 // Method "<init>":(Ljava/lang/String;IDD)V
16: putstatic #27 // Field SPRING:Lcom/think/enumeration/Season;
........
76: iconst_4
77: anewarray #1 // class com/think/enumeration/Season
80: dup
81: iconst_0
82: getstatic #27 // Field SPRING:Lcom/think/enumeration/Season;
85: aastore
86: dup
87: iconst_1
........
107: return
//编译器为我们提供的方法
public static com.think.enumeration.Season[] values();
Code:
0: getstatic #44 // Field ENUM$VALUES:[Lcom/think/enumeration/Season;
3: dup
4: astore_0
5: iconst_0
6: aload_0
7: arraylength
8: dup
9: istore_1
10: anewarray #1 // class com/think/enumeration/Season
13: dup
14: astore_2
15: iconst_0
16: iload_1
17: invokestatic #110 // Method java/lang/System.arraycopy:(Ljava/lang/Object;ILjava/lang/Object;II)V
20: aload_2
21: areturn
//编译器为我们提供的方法
public static com.think.enumeration.Season valueOf(java.lang.String);
Code:
0: ldc #1 // class com/think/enumeration/Season
2: aload_0
//实际上是调用Enum.valueOf(Class enumType,String name)方法,只不过第一个参数默认是本枚举类型
3: invokestatic #116 // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
6: checkcast #1 // class com/think/enumeration/Season
9: areturn
}
通过上面的代码,我们可以发现:
(1)编译器自动的把对应的类设置为final,这样它就不能有子类
(2)编译器自动的使对应的类继承子Enum<T>,这样它就不能再继承其他的类,同时可以使用Enum中的方法
(3)Season实例的初始化在一个静态代码块中执行,这一点很重要,这意味这枚举类的实现是在类加载的初始化阶段进行的,一旦把一个枚举类加载到JVM中后,就不能改变枚举类的实例,而且在类加载的初始化阶段,JVM会确保初始化的安全,不会存在多线程的问题。所以在使用单例模式时,把单例类实现成枚举类,其中只有一个实例是一个非常好的手段。
(4)每个实例都是final的
(5)编译器为我们添加了values()方法,它返回此枚举的所有实例;还添加了只有一个参数的valueOf(String name)方法,它其实是调用Enum的valueOf()方法,只不过把该枚举类对应的Class对象传递过去作为第一个参数。也就是如果把一个枚举的实例转换为Enum类型,那么是不可以使用values()方法的,这个时候可以通过Class类的getEnumConstants()方法
Enum e = Season.SPRING;
//不能调用values()方法
//e.values()
//可以使用getEnumConstants()
e.getClass().getEnumConstants()
以上就是枚举的基本用法,枚举是一项很有用的特性,由于它天然的有序性,它可以在很多方面发挥作用,同时使用枚举在构造程序时,结构清晰,语义清晰。
转载请注明出处:喻红叶《Java枚举》