在实际编程中,存在着这样的“数据集”,它们的数值在程序中是稳定的并且个数是有限的。例如春、夏、秋、冬四个数据元素组成了四季的“数据集”,一月到十二月组成了十二个月份的“数据集”,周一到周五及周六周日组成了每周七天的“数据集”。在Java中可以使用枚举类型来表示这些数据。
在Java5之前,没有枚举类型,需要使用静态常量。如下:
public class Week {
static final int SUNDAY = 0;
static final int MONDAY = 1;
static final int TUESDAY = 2;
static final int WEDNESDAY = 3;
static final int THURSDAY = 4;
static final int FRIDAY = 5;
static final int SATURDAY = 6;
}
要使用上面这些模拟的枚举值时,直接使用Week类名调用静态常量即可。不过这种方式可能会出现一个问题,当声明一个方法中接受int类型的参数时,也可以使用上面定义的静态常量作为实参传入,编译器不会报错,但是会影响代码的阅读性。
Java5开始新增了枚举类型,避免了上面的问题。定义枚举类型,使用enum关键字,声明格式如下:
[修饰符] enum 枚举名 {
枚举成员
}
修饰符:public、private、internal。
枚举名:符合Java规范的标识符。
枚举成员:任意枚举成员之间不能有相同的名字,多个枚举成员之间使用逗号隔开,最后一个成员后面可以有分号,也可以没有。
下面代码定义了一个枚举类型Week,用该类型声明的变量只能存储枚举类型定义中给出的某个枚举值,或者null值。
public enum Week {
Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday;
}
可以使用swith语句很方便地判断枚举的值,如下:
class WeekTest {
static void UseWeek(Week day) { // 使用Week枚举类型声明变量day
switch (day) {
case Sunday:
System.out.println("星期天");
break;
case Monday:
System.out.println("星期一");
break;
case Tuesday:
System.out.println("星期二");
break;
case Wednesday:
System.out.println("星期三");
break;
case Thursday:
System.out.println("星期四");
break;
case Friday:
System.out.println("星期五");
break;
case Saturday:
System.out.println("星期六");
break;
}
}
public static void main(String[] args) {
WeekTest.UseWeek(Week.Monday); // 输出“星期一”
}
}
枚举类型最常用的地方就是在switch语句中使用。上面的代码在UseWeek()方法中定义了一个形参day,其类型是Week枚举类型,将变量day作为swith语句的表达式,通过对day的值来配符case语句中的常量的值,若相等执行相应的case语句。
另外:case表达式中可以直接写入枚举值(写也可以),不用添加枚举类作为限定。
枚举的本质是类,只不过是使用enum关键字替代class,它所创建的类型都是java.lang.Enum类的子类,而java.lang.Enum是一个抽象类。枚举屏蔽了枚举值的类型信息,不像在用public static final 三个修饰符修饰的常量时必须指定类型。枚举是用来构建常量数据结构的模板,而且这个模板也可以扩展(添加属性、方法、构造函数)。
所以上面使用enum关键字创建的Week枚举,其源文件名是Week.java,编译后的文件名是Week.class,和类一样。
下面代码是使用JDK自带的javap工具来反编译Week.class文件,会输出如下的结果:
//PS D:\MyJavaLearn\> javap Week.class
//Compiled from "Week.java"
public final class com.test.Week extends java.lang.Enum<Week> {
public static final Week Sunday;
public static final Week Monday;
public static final Week Tuesday;
public static final Week Wednesday;
public static final Week Thursday;
public static final Week Friday;
public static final Week Saturday;
public static Week[] values();
public static Week valueOf(java.lang.String);
static {};
从上面反编译的代码可以看出,枚举类型实际上还是一个类,枚举值还是静态常量,使用public static final 三个修饰符。但是,这些常量的类型就是类本身。
从反编译的代码中还可以看到Week类增加了两个静态方法values()和valueOf()。
在Java语言中,每一个枚举类型成员都可以当做一个Enum类的实例。而且枚举成员会默认被public static final修饰,所以可以直接使用枚举名调用它。
java.lang.Enum类是一个抽象类,所有创建的枚举类型自动实现了它,故所有枚举实例都可以调用枚举类定义的方法。常用的如下:
方法名 | 说明 |
compareTo | 比较枚举与指定对象的定义顺序 |
valueOf(Class<T>enumType,String name) | 返回带指定名称的指定枚举类型的枚举常量 |
values() | 以数组的形式返回枚举类型的所有成员 |
ordinal() | 返回枚举常量的索引位置(它在枚举声明中的位置,其中初始常量序数为零 |
toString() | 返回枚举常量的名称 |
下面代码定义了一个颜色枚举类型,使用了如上方法的调用。
public class EnumMethod {
// 定义颜色枚举类
public enum Color {
red,yellow,green,blue,pink,brown,purple
}
public static void main(String[] args) {
// ordinal()方法的使用:获取指定枚举实例的索引
for (int i=0;i<Color.values().length;i++) {
// 循环输出枚举类中所有枚举常量的索引值
System.out.print(Color.values()[i] + "的索引:" + Color.values()[i].ordinal() + " ");
}
System.out.println();
// toString()方法的使用,返回枚举常量的名称
System.out.println("toString()方法的使用:" + Color.blue.toString());
// compareTo()方法的使用
System.out.println("compareTo()方法的使用:" + Color.blue.compareTo(Color.red));
// valueOf()方法使用
System.out.println("valueOf方法使用:" + Color.valueOf("green"));
}
}
输出的结果如下:
为枚举添加属性和方法
枚举类型出来Java提供的方法,还可以定义自己的方法。但需要注意,这时需要在枚举实例的最后一个成员后面添加分号,并且必须先定义枚举实例。如下代码:
package com.test;
public enum EnumProperty {
// 枚举成员定义,且必须以分好结尾
Jan("January"),Feb("February"),Mar("March"),Apr("April"),May("May"),Jun("June"),
Jul("July"),Aug("August"),Sep("September"),Oct("October"),Nov("November"),Dec("December");
// 定义枚举类型的private属性
private final String month;
// 定义私有构造函数(也可以是public)
private EnumProperty(String month) {
this.month = month;
}
// 定义枚举类的public方法
public String getMonth(){
return month;
}
}
class EnumPropertyTest {
public static void main(String[] args) {
// 使用for循环遍历枚举类型,并输出
for(EnumProperty en :EnumProperty.values()) {
System.out.println(en + " : " + en.getMonth());
}
}
}
上面代码定义了EnumPropery枚举,声明了它的私有属性month和私有构造方法EnumPropery(),及public方法getMonth()。构造方法EnumPropery()作用是为私有属性month赋值,getMonth()方法是获取私有属性month的值。在上面的测试类中,通过枚举的values()方法获得枚举的所有成员,再通过for循环遍历成员,其中调用自定义的方法getMonth()获取枚举的成员。
请注意:在定义枚举成员时的Jan("January"),其实是调用了构造函数,并传入“January”进行初始化枚举的私有属性month,给其进行赋值“January”。
枚举值的比较
如果只是简单地比较两个枚举类型的值是否相等,那么直接使用比较运算符“==”即可。如果需要进一步地比较,则可以使用枚举值的compareTo方法。
枚举的对象有一个ordinal()方法可以获取枚举值的一个数值表示,compareTo()方法实际是比较两个值的ordinal的值。比如如下代码:
System.out.println(Week.Monday.compareTo(Week.Thursday));
上面代码运行的结果是:-3
使用compareTo()方法比较两个枚举值,当结果小于0时,compareTo左边的枚举值小于右边的枚举值;当结果为0时,则说明两个枚举值相等;当结果大于0时,则说明compareTo左边的枚举值大。
注意:Java中,不允许使用“>”和“<”比较运算符比较两个枚举值。
另外:自定义的比较方法不能取名为compareTo,因为所有枚举类型生产的类都是从Enum类继承的,而Enum类中的compareTo方法不能被覆盖。
EnumSet和EnumMap类
为了更高效地操作枚举类型,java.util中有EnumSet和EnumMap两个类。
1)EnumMap类
EnumMap类是为枚举类型专门量身定做的Map实现。EnumMap类只能接收同一枚举类型的实例作为键值,并且由于枚举类型实例的数量相对固定且有限,所以EnumMap类使用数组来存放与枚举类型相关的值,这使得EnumMap的效率非常高。
2)EnumSet类
EnumSet类是枚举类型的高性能Set实现。EnumSet要求放入它的枚举常量必须属于同一枚举类型,它提供了许多工厂方法以便于初始化。
方法 | 说明 |
---|---|
allOf(Class<E>elementType) | 创建一个包含指定枚举类型的所有枚举成员的EnumSet对象 |
complementOf(EnumSet<E> s) | 创建一个与指定枚举类型对象s相同的EnumSet对象,包含指定s中不包含的枚举成员 |
copyOf(EnumSet<E> s) | 创建一个与指定枚举类型对象s相同的EnumSet对象,包含与s中相同的枚举成员 |
noneOf(Class<E>elementType) | 创建一个具有指定枚举类型的空EnumSet对象 |
of(E first,E... rest) | 创建一个包含指定枚举成员的EnumSet对象 |
range(E from,E to) | 创建一个包含从from到to的所有枚举成员的EnumSet对象 |
package com.test;
import java.util.*;
import java.util.Map.Entry;
enum EnumMonth {
Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Noe,Dec
}
public class EnumTest {
public static void main(String[] args) {
// EnumSet的使用
EnumSet<EnumMonth> monthSet = EnumSet.allOf(EnumMonth.class);
for (EnumMonth month : monthSet) {
System.out.print(month + " ");
}
System.out.println();
// EnumMap的使用
EnumMap<EnumMonth,String> monthMap = new EnumMap(EnumMonth.class);
monthMap.put(EnumMonth.Jan,"一月份");
monthMap.put(EnumMonth.Feb,"二月份");
monthMap.put(EnumMonth.Mar,"三月份");
monthMap.put(EnumMonth.Apr,"四月份");
monthMap.put(EnumMonth.May,"五月份");
monthMap.put(EnumMonth.Jun,"六月份");
// 省略了7-12月份
for (Iterator<Entry<EnumMonth,String>> ite=monthMap.entrySet().iterator();ite.hasNext();) {
Entry<EnumMonth,String> entry = ite.next();
System.out.print(entry.getKey().name() + ":" + entry.getValue() + " ");
}
}
}
运行的结果如下:
上面代码,通过EnumSet的allOf()方法获得枚举类EnumMonth的所有枚举成员,并通过for循环打印输出所有的枚举成员。
对于EnumMap的使用,是通过其构造方法返回一个指定枚举类型的空的枚举映射monthMap,再通过它的put()方法将枚举成员依次添加到这个空枚举映射monthMap变量中。然后通过迭代器Itertor和for循环依次将EnumMap的键-值打印出来。