枚举类

99封情书 提交于 2019-11-27 21:58:36

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

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!