什么是枚举?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 }