什么是枚举?java中枚举如何定义
枚举是将变量的值一一列举出来,变量的值只限定于列举出来的值,java中的枚举是jdk1.5后引入的,以前通常会在接口中定义静态常量的方式来表示枚举.我们只讨论1.5以后引入的枚举类.下面的例子定义了一个简单的枚举类用于表示四季.定义枚举使用关键字enum
1 public enum Season {
2 SPRING,SUMMER,AUTUMN,WINTER;
3 }
可以看出,定义一个枚举类是非常容易的,只需要将枚举常量的名称一一列举出来,并用逗号分隔如果不添加任何方法,枚举的默认值是从0开始的有序数值,也就是说Season的枚举常量依次是SPRING:0,SUMMER:1,AUTUMN:2,WINTER:3.
使用枚举类
1 public class TestEunm {
2 public static void main(String[] args) {
3 // 0. 不能创建枚举类的对象
4 //Season season = new Season(); //编译报错
5
6
7 // 1.直接使用类名.枚举常量名
8 System.out.println(Season.SPRING); //SPRING
9 System.out.println(Season.SUMMER); //SUMMER
10 System.out.println(Season.AUTUMN); //AUTUMN
11 System.out.println(Season.WINTER); //WINTER
12 //System.out.println(Season.NULL); //不能使用不存在的枚举常量名
13
14 // 2.可以使用==或equals方法比较两个枚举变量的值是否相等,这两种效果是一样的
15 Season spring = Season.SPRING;
16 Season spring2 = Season.SPRING;
17 Season summer = Season.SUMMER;
18 System.out.println(spring == summer); //fasle
19 System.out.println(spring == spring2); //true
20 System.out.println(spring.equals(spring2)); //true
21 System.out.println(spring.equals(summer)); //false
22
23 }
24 }
为什么不能构造枚举类的对象呢?为什么可以使用类名.枚举常量名的方式调用枚举常量呢?使用反编译工具可以看出枚举类都是默认继承java.lang.Enum类的.这是一个泛型的抽象类
我们先分析下Enum类的源码,再来分析我们自己定义的枚举类
Enum类
Enum是java中所有枚举类型的基类.是一个泛型的抽象类,它的类型参数限定为Enum本身或它的子类,同时它还实现了Comparable<E>接口和Serializable接口.(在Enum中其实还有两个私有方法与反序列化有关)
1 public abstract class Enum<E extends Enum<E>> implements Comparable<E>, Serializable {
2
3 // 枚举常量的名称.如Season中声明的SPRING,SUMMER等
4 private final String name;
5
6 // 枚举常量在声明时的位置序号(默认从0开始),这个值通常在EnumSet和EnumMap中使用
7 private final int ordinal;
8
9
10 // 返回此枚举常量的名称
11 public final String name() {
12 return name;
13 }
14
15 // 最终方法,不允许子类重写,返回枚举常量在声明时的序号
16 public final int ordinal() {
17 return ordinal;
18 }
19
20 // 构造器,我们定义的子类将使用这个方法初始化枚举值
21 protected Enum(String name, int ordinal) {
22 this.name = name;
23 this.ordinal = ordinal;
24 }
25 // 返回枚举常量的名称,子类可以重写它的方法,当没有重写时调用效果和调用name()方法效果一样
26 public String toString() {
27 return name;
28 }
29
30 // 如果指定对象等于此对象时返回true
31 public final boolean equals(Object other) {
32 return this == other;
33 }
34
35 // 返回枚举常量的哈希码
36 public final int hashCode() {
37 return super.hashCode();
38 }
39
40 // 获得该枚举对象的一个个拷贝.当调用此方法时,将抛出一个异常,枚举对象应该是单例的,不允许拷贝
41 protected final Object clone() throws CloneNotSupportedException {
42 throw new CloneNotSupportedException();
43 }
44
45 // 比较此枚举常量与指定枚举的顺序,比较的是枚举声明时产生的序号
46 public final int compareTo(E o) {
47 Enum<?> other = (Enum<?>) o;
48 Enum<E> self = this;
49 if (self.getClass() != other.getClass() && self.getDeclaringClass() != other.getDeclaringClass())
50 throw new ClassCastException();
51 return self.ordinal - other.ordinal;
52 }
53
54 // 返回此枚举类型相对应的Class对象
55 @SuppressWarnings("unchecked")
56 public final Class<E> getDeclaringClass() {
57 Class<?> clazz = getClass();
58 Class<?> zuper = clazz.getSuperclass();
59 return (zuper == Enum.class) ? (Class<E>) clazz : (Class<E>) zuper;
60 }
61
62 // 返回带指定名称的枚举常量
63 public static <T extends Enum<T>> T valueOf(Class<T> enumType, String name) {
64 T result = enumType.enumConstantDirectory().get(name);
65 if (result != null)
66 return result;
67 if (name == null) //如果name为空,抛出一个异常
68 throw new NullPointerException("Name is null");
69 // 如果上面的都执行了,抛出一个IllegalArgumentException异常(不合法的参数)
70 throw new IllegalArgumentException("No enum constant " + enumType.getCanonicalName() + "." + name);
71 }
72 // 从Object继承的方法,但是枚举类不应该有该方法
73 protected final void finalize() {
74 }
75
76 }
自定义的Season类
1 // 反编译后的Season类
2 public final class Season extends Enum
3 {
4
5 public static final Season SPRING;
6 public static final Season SUMMER;
7 public static final Season AUTUMN;
8 public static final Season WINTER;
9 private static final Season ENUM$VALUES[];
10
11 private Season(String s, int i)
12 {
13 super(s, i);
14 }
15
16 public static Season[] values()
17 {
18 Season aseason[];
19 int i;
20 Season aseason1[];
21 System.arraycopy(aseason = ENUM$VALUES, 0, aseason1 = new Season[i = aseason.length], 0, i);
22 return aseason1;
23 }
24
25 public static Season valueOf(String s)
26 {
27 return (Season)Enum.valueOf(enums/Season, s);
28 }
29
30 static
31 {
32 SPRING = new Season("SPRING", 0);
33 SUMMER = new Season("SUMMER", 1);
34 AUTUMN = new Season("AUTUMN", 2);
35 WINTER = new Season("WINTER", 3);
36 ENUM$VALUES = (new Season[] {
37 SPRING, SUMMER, AUTUMN, WINTER
38 });
39 }
40 }
通过反编译工具可以看出,枚举也是一个类,但这个类比较特殊,它没有无参构造器,并且构造器都是私有的.我们声明的枚举常量其实就是一个该类的对象,并用static final修饰(这样保证了枚举常量是单例的,且不可修改)因此可以通过类名.枚举常量名的方式来调用.
枚举类内部还维护了一个该类的数组,用于保存我们声明的枚举常量.编译器还会为我们生成一个values方法,它可以返回一个包含全部枚举值的数组.下面使用下我们提到的方法看看效果.
1 public class TestEunm {
2 public static void main(String[] args) {
3 Season spring = Season.SPRING;
4
5 // 0.返回此枚举常量的名称
6 String name = spring.name();
7 // 或
8 String name2 = spring.toString();
9 System.out.println(name); // 输出: SPRING
10 System.out.println(name2); // 输出: SPRING
11
12 // 1.返回包含全部枚举值的数组
13 Season[] values = Season.values();
14 for (Season season : values) {
15
16 // 2.返回枚举常量声明时的位置序号
17 System.out.print(season+"="+season.ordinal()+"; "); // 输出: SPRING=0; SUMMER=1; AUTUMN=2; WINTER=3;
18 }
19
20 // 3.获取该类型相应的Class对象
21 System.out.println(Season.SPRING.getDeclaringClass()); // 输出: class enums.Season
22 System.out.println(Season.SPRING.getClass()); // 输出: class enums.Season
23
24 // 4. 按声明序号比较两个枚举常量
25 System.out.println(Season.SPRING.compareTo(Season.SUMMER)); // -1
26 System.out.println(Season.AUTUMN.compareTo(Season.SUMMER)); // 1
27
28 // 5. 返回指定名称的枚举常量
29 // 通过Enum类
30 Season name3 = Enum.valueOf(Season.class, "SUMMER");
31 System.out.println(name3); // 输出:SUMMER
32 Season name4 = Enum.valueOf(Season.class, "P");
33 System.out.println(name4); // 抛出异常: java.lang.IllegalArgumentException
34 // 通过本类
35 System.out.println(Season.valueOf("WINTER")); //输出: WINTER
36 System.out.println(Season.valueOf("P")); // 抛出异常: java.lang.IllegalArgumentException
37
38
39 // 6. 多态引用
40 Enum<Season> p = Season.SPRING;
41 System.out.println(p.getClass()); // 输出 : class enums.Season
42 System.out.println(p.getDeclaringClass()); // 输出 : class enums.Season
43 }// 输出 : class enums.Season
44 }
注意:在获取一个枚举对象所属的Class对象时,应该使用getDeclaringClass()方法,而不是getClass方法,因为有时候getClass方法返回的类型并不准确
enum Week {
WEEKDAY {
@Override
public void fun() {
System.out.println("工作日");
}
},
WEEKEND {
@Override
public void fun() {
System.out.println("休息日");
}
};
public abstract void fun();
}
public class WeekTest {
public static void main(String[] args) {
String name = Week.WEEKDAY.getClass().getName();
String name2 = Week.WEEKEND.getClass().getName();
System.out.println(name); // enums.Week$1
System.out.println(name2); // enums.Week$2
String name3 = Week.WEEKDAY.getDeclaringClass().getName();
String name4 = Week.WEEKEND.getDeclaringClass().getName();
System.out.println(name3); // enums.Week
System.out.println(name4); // enums.Week
}
}
高级用法
枚举类中也可以定义属性,构造函数,和方法,定义构造函数时比较特殊,必须是私有的.一旦定义了有参的构造函数,声明枚举常量时,就必须使用这个构造函数.
1 public enum Season {
2 SPRING("春天"),SUMMER("夏天"),AUTUMN("秋天"),WINTER("冬天");
3
4 private String name;
5
6 private Season(String name) {
7 this.name = name;
8 }
9 public String getName() {
10 return name;
11 }
12 }
由于自定义的枚举类是final修饰的,所以不能拓展其他的类,但是可以在枚举类中定义抽象方法,这个抽象方法在声明枚举常量时必须被实现
1 public enum Season {
2 SPRING {
3 @Override
4 public void fun() {
5 System.out.println("这是春天");
6 }
7 },SUMMER {
8 @Override
9 public void fun() {
10 System.out.println("这是夏天");
11
12 }
13 },AUTUMN {
14 @Override
15 public void fun() {
16 System.out.println("这是秋天");
17
18 }
19 },WINTER {
20 @Override
21 public void fun() {
22 System.out.println("这是冬天");
23 }
24 };
25
26 public abstract void fun();
27 }
枚举类也可以实现接口
1 interface Maxable<T> {
2 public T getMax();
3 }
4
5 public enum Season implements Maxable<Season> {
6 SPRING, SUMMER, AUTUMN, WINTER;
7
8 @Override
9 public Season getMax() {
10 int maxIndex = Season.values().length - 1;
11 return values()[maxIndex];
12 }
13 }
另一种实现接口的方式
1 enum Sortord implements Comparator<Integer> {
2 BIG_TO_SMALL("从大到小") {
3 @Override
4 public int compare(Integer o1, Integer o2) {
5 return -o1.compareTo(o2);
6 }
7 },
8 SMALL_TO_BIG("从小到大") {
9 @Override
10 public int compare(Integer o1, Integer o2) {
11 return o1.compareTo(o2);
12 }
13 };
14
15 private String way;
16 private Sortord(String way) {
17 this.way = way;
18 }
19 }
20
21 public class TestSortord {
22 public static void main(String[] args) {
23 Integer[] arr = new Integer[10];
24 for (int i = 0; i < arr.length; i++) {
25 arr[i] = new Integer((int) (Math.random() * 20));
26 }
27 System.out.println("原数组为:" + Arrays.toString(arr));
28 // 从大到小排序
29 Arrays.parallelSort(arr, Sortord.BIG_TO_SMALL);
30 System.out.println("从大到小排序为:" + Arrays.toString(arr));
31
32 // 从小到大排序
33 Arrays.parallelSort(arr, Sortord.SMALL_TO_BIG);
34 System.out.println("从小到大排序为:" + Arrays.toString(arr));
35 }
36 }

枚举类还可以作为内部类使用,也就是说它可以嵌套在类中,接口中,甚至枚举类还可以嵌套在枚举类内.注意枚举类作为内部类都是静态内部类.
由于在接口中的变量和方法都是public修饰的,所以接口中的枚举类都是public修饰的
1 public interface Week{
2 enum WeekDay{
3 Monday, Tuesday, Wednesday, Thursday;
4 }
5 enum WeekEnd{
6 Friday,Saturday;
7 }
8 }
嵌套在类中或枚举类中的枚举类可以使用public,protected,默认修饰符,private修饰
1 public class Week{
2 enum WeekDay{
3 Monday, Tuesday, Wednesday, Thursday;
4 }
5 enum WeekEnd{
6 Friday,Saturday;
7 }
8 }

1 public enum Week{
2 HOLiDAY;
3
4 public enum WeekDay{
5 Monday, Tuesday, Wednesday, Thursday;
6 }
7 protected enum WeekEnd{
8 Friday,Saturday;
9 }
10 }
EnumSet和EnumMap
EnumSet是一个存储同一枚举类型元素的高效集合,它的底层是用位序列实现的,(这里不讨论它的底层实现细节,只是简单的学习一下它的用法(主要是看不懂,哭唧唧)),它的继承结构如图

JumboEnumSet使用long数组实现,RegularEnumSet使用long实现.从图中我们可以看出,EnumSet是一个抽象类,无法通过new创建实例,而JumboEnumSet和RegularEnumSet两个类都是包可见的.我们也不能使用.但是可以通过EnumSet的静态工厂方法来得到一个EnumSet的实现类对象,我们并不关心具体的类型是属于JumboEnumSet还是属于RegularEnumSet类型的.下面我们来使用一下EnumSet
1 public class EnumSetDemo {
2 public static void main(String[] args) {
3 // 0. 使用枚举类创建一个EnumSet
4 EnumSet<Season> allOf = EnumSet.allOf(Season.class);
5 System.out.println(allOf); // [SPRING, SUMMER, AUTUMN, WINTER]
6
7 // 1. 创建指定枚举类型的空EnumSet
8 EnumSet<Season> noneOf = EnumSet.noneOf(Season.class);
9 System.out.println(noneOf); // []
10 // 添加元素
11 noneOf.add(Season.SPRING);
12 noneOf.add(Season.SUMMER);
13 noneOf.add(Season.AUTUMN);
14 System.out.println(noneOf); // [SPRING, SUMMER, AUTUMN]
15 // 移除元素
16 noneOf.remove(Season.SPRING);
17 System.out.println(noneOf); // [SUMMER, AUTUMN]
18
19 // 2. 创建一个包含指定元素类型,使用EnumSet.of方法,这个方法有六个重载方法
20 EnumSet<Season> of = EnumSet.of(Season.SPRING,Season.AUTUMN,Season.SUMMER);
21 System.out.println(of); // [SPRING, SUMMER, AUTUMN]
22
23 // 3. 创建一个从指定枚举常量到指定枚举常量范围内所有元素的EnumSet
24 EnumSet<Season> range = EnumSet.range(Season.SUMMER, Season.WINTER);
25 System.out.println(range); // [SUMMER, AUTUMN, WINTER]
26
27 // 4. 创建一个和其他EnumSet具有相同枚举类型的EnumSet
28 EnumSet<Season> copyOf = EnumSet.copyOf(range);
29 EnumSet<Season> complementOf = EnumSet.complementOf(noneOf);
30 System.out.println(complementOf); // [SPRING, WINTER]
31 System.out.println(copyOf); //[SUMMER, AUTUMN, WINTER]
32 }
33 }
EnumMap是一个键类型为同一枚举类型的映射,底层使用值数组高效实现的.枚举映射根据键的自然顺序(即枚举声明时的顺序)来维护的.下面来使用一下EnumMap
1 public class EnumMapDemo {
2 public static void main(String[] args) {
3 // 0. 使用指定枚举类型作为键创建一个空EnumMap
4 EnumMap<Season, String> enumMap = new EnumMap<Season, String>(Season.class);
5 System.out.println(enumMap); // {}
6
7 // 1.添加
8 enumMap.put(Season.SPRING, "春天");
9 enumMap.put(Season.SUMMER, "夏天");
10 enumMap.put(Season.AUTUMN, "秋天");
11 enumMap.put(Season.WINTER, "冬天");
12
13 // 2. 获取键的Set视图
14 Set<Season> keySet = enumMap.keySet();
15
16 // 3.获取值的Set视图
17 Collection<String> values = enumMap.values();
18
19 // 4. 获取映射中包含的映射关系的Set视图
20 Set<Entry<Season, String>> entrySet = enumMap.entrySet();
21 for (Entry<Season, String> entry : entrySet) {
22 System.out.print(entry.getKey()+"="+entry.getValue()+", ");
23 // SPRING=春天, SUMMER=夏天, AUTUMN=秋天, WINTER=冬天,
24 }
25
26 // 5.移除键指定的映射关系
27 enumMap.remove(Season.SPRING);
28 System.out.println(enumMap); //{SUMMER=夏天, AUTUMN=秋天, WINTER=冬天}
29
30 // 6. 创建一个和指定EnumMap键类型相同的EnumMap,它包含指定EnumMap中的映射关系
31 EnumMap<Season, String> enumMap2 = new EnumMap<Season, String>(enumMap);
32 System.out.println(enumMap2); // {SUMMER=夏天, AUTUMN=秋天, WINTER=冬天}
33
34
35 Map<Season, String> map = new HashMap<Season, String>();
36 map.put(Season.SPRING,"哈哈");
37 // 7.使用指定的Map创建并初始化一个EnumMap,指定Map的键类型必须是枚举类型
38 EnumMap<Season, String> enumMap3 = new EnumMap<Season, String>(map);
39 System.out.println(enumMap3); // {SPRING=哈哈}
40 }
41 }