17. Java学习之枚举

enum 的全称为 enumeration, 是 JDK 1.5  中引入的新特性,存放在 java.lang 包中。它是一个看似很小的特性,它使得我们在需要群组并使用枚举类型集时,可以很方便地处理。(note:可选值固定在某个范围时使用)。

一. 枚举的引入

1. 未引入枚举之前

 

假如有以下需求需要实现:

 

需求1:定义一年中的四季和对应的季度

 

需求2:打印当前所在的季节

 

需求3:获取下一个季节

 

需求4:得到各个季节所对应的季度

 

(1)需求1实现

 1 package com.test.a;
 2 
 3 public class Season {
 4 
 5     public static final int SPRING = 1;
 6     public static final int SUMMER = 2;
 7     public static final int AUTUMN = 3;
 8     public static final int WINTER = 4;
 9 
10 }
View Code

         说明:如果想要引用上面的变量,直接类名.变量名就可以了,比如:Season.SPRING

(2)需求2实现

 1 package com.test.a;
 2 
 3 public class Season {
 4 
 5     public static final Season SPRING = new Season();
 6     public static final Season SUMMER = new Season();
 7     public static final Season AUTUMN = new Season();
 8     public static final Season WINTER = new Season();
 9 
10     private Season() {
11 
12     }
13 
14     public static void printSeason(Season seasonNow) {
15         if (seasonNow == SPRING)
16             System.out.println("Now is Spring");
17         else if (seasonNow == SUMMER)
18             System.out.println("Now is Summer");
19         else if (seasonNow == AUTUMN)
20             System.out.println("Now is Autumn");
21         else
22             System.out.println("Now is Winter");
23     }
24 
25 }
View Code

         说明:这次代码做了修改,引入了私有构造器,且创建了四个成员变量对象,代表了四个季节,这样有利于防止输入别的季节,且只能在该类中使用构造函数,外部类是不可以更改的。并且这个打印方法也做了实现,打印除了当前所在的季节。

(3)需求3实现

 1 package com.test.a;
 2 
 3 public class Season {
 4 
 5     public static final Season SPRING = new Season();
 6     public static final Season SUMMER = new Season();
 7     public static final Season AUTUMN = new Season();
 8     public static final Season WINTER = new Season();
 9 
10     private Season() {
11 
12     }
13 
14     public static void printSeason(Season seasonNow) {
15         if (seasonNow == SPRING)
16             System.out.println("Now is Spring");
17         else if (seasonNow == SUMMER)
18             System.out.println("Now is Summer");
19         else if (seasonNow == AUTUMN)
20             System.out.println("Now is Autumn");
21         else
22             System.out.println("Now is Winter");
23     }
24 
25     public static Season getNextSeason(Season seasonNow) {
26         if (seasonNow == SPRING)
27             return SUMMER;
28         else if (seasonNow == SUMMER)
29             return AUTUMN;
30         else if (seasonNow == AUTUMN)
31             return WINTER;
32         else
33             return SPRING;
34 
35     }
36 }
View Code

(4)需求4实现

 1 package com.test.a;
 2 
 3 public class Season {
 4 
 5     public static final Season SPRING = new Season();
 6     public static final Season SUMMER = new Season();
 7     public static final Season AUTUMN = new Season();
 8     public static final Season WINTER = new Season();
 9 
10     private Season() {
11 
12     }
13 
14     public static void printSeason(Season seasonNow) {
15         if (seasonNow == SPRING)
16             System.out.println("Now is Spring");
17         else if (seasonNow == SUMMER)
18             System.out.println("Now is Summer");
19         else if (seasonNow == AUTUMN)
20             System.out.println("Now is Autumn");
21         else
22             System.out.println("Now is Winter");
23     }
24 
25     public static Season getNextSeason(Season seasonNow) {
26         if (seasonNow == SPRING)
27             return SUMMER;
28         else if (seasonNow == SUMMER)
29             return AUTUMN;
30         else if (seasonNow == AUTUMN)
31             return WINTER;
32         else
33             return SPRING;
34 
35     }
36 
37     public static int getQuarter(Season seasonNow) {
38         if (seasonNow == SPRING)
39             return 1;
40         else if (seasonNow == SUMMER)
41             return 2;
42         else if (seasonNow == AUTUMN)
43             return 3;
44         else
45             return 4;
46 
47     }
48 
49 }
View Code

 总结:上面是没有采取枚举前的实现,每当需求变更,就会写很多的代码来适应需求,写了很多的If else,当然还有别的写法。不能简洁的实现需求。

2. 引入枚举

 1 package com.test.a;
 2 
 3 public enum Season {
 4     SPRING(1), SUMMER(2), AUTUMN(3), WINTER(4);
 5     private int quater;
 6 
 7     private Season(int quater) {
 8         this.quater = quater;
 9     }
10 
11     public static Season getNextSeason(Season seasonNow) {
12         int nextSeasonQuater;
13         if (seasonNow.quater == 4) {
14             nextSeasonQuater = 1;
15         }
16         nextSeasonQuater = seasonNow.quater + 1;
17         return getSeasonByQuater(nextSeasonQuater);
18     }
19 
20     // 得到季度对应的季节,采用枚举
21     private static Season getSeasonByQuater(int quater) {
22         for (Season season : Season.values()) {
23             if (season.quater == quater) {
24                 return season;
25             }
26         }
27         return null;
28     }
29 
30 }
View Code

    说明:采用了枚举以后,if else减少,定义静态常量也非常简洁,并且我们可以直接调用values方法类获取对应的每个枚举实例值。整体能够支持的功能更加的多样化,且代码更加的简洁。枚举类也自定义了很多的final变量和方法,下面将会详细讲解的。

3. 为何引入枚举

根据上面的例子我们知道,在没有枚举类的时候,我们要定义一个有限的序列,春夏秋冬,一般会通过上面那种静态变量的形式,但是使用那样的形式如果需要一些其他的功能,就会出现很多风格不同的代码,且包含大量的if else。所以,枚举类的出现,就是为了简化这种操作。

 二. Enum的源码分析

1. 类定义

public abstract class Enum<E extends Enum<E>> implements Comparable<E>, Serializable

说明:这是一个抽象类,因此不能通过new来创建它的对象

翻阅thining in java,Enum类无法被继承,为什么呢?

1 public enum Season<E> extends Enum<Enum<E>>{    
2 }
3 
4 public class Season extends Enum<Enum<E>>{
5 }
6 
7 public class Season extends Enum{
8 }
View Code

      说明:上面三种方式都试图来继承Enum,但是编译器都报错了。

2. 反编译

 为了解释这种现象,通过javap命令来反编译Season类:

说明:1)上面是反编译的结果,发现上面的Season枚举类实际上是一个java类,且是final类型的,这就说明了这个枚举类型是不可以被继承的。并且Season继承了Enum类,因为java是单继承的,因此就意味着Season枚举类不可以继承其它的类了。(既不能再继承别人,又不能拥有子类了)。这个动作必须由编译器来完成,直接手动写成继承Enum编译报错。

           2)其中定义的四个季节类型的枚举对象都被反编译成了public static final类型常量,说明可以直接访问它们。

           3)上面还发现了多了一个Private类型的构造器,说明枚举类型是不允许被实例化的,即我们不可以用new来创建对象。并且,这个构造器再内部实际上调用了父类Enum的Protected类型的构造器protected Enum(String name, int ordinal) 。

           倘若我们自定义了构造函数,编译器自动添加的构造函数也是不会冲突的,它将以有参构造函数的形式展示:private com.test.a.Season(int);

 1 package com.test.a;
 2 
 3 public enum Season {
 4     SPRING(1), SUMMER(2), AUTUMN(3), WINTER(4);
 5     private int a;
 6 
 7     Season(int a) {
 8         this.a = a;
 9     }
10 }
View Code

     4)编译的时候还自动添加了static静态代码块,它用来初始化所有的枚举对象,并添加到自动生成的一个数组常量$VALUES中存储起来。

     5)编译时还会自动生成values()方法,它用来返回所有枚举对象的数组。

     6)编译时还会自动生成valueOf(String),它实际上调用了Enum.valueOf方法

      7)注意点:values方法是编译器生成的,在父类Enum中没有;valueOf方法也是编译器生成的,它只有一个参数,但是父类中的valueOf是两个参数,但是最终还是调用了Enum类的valueOf方法。对比举例:

 1 package com.test.a;
 2 
 3 import java.util.Arrays;
 4 
 5 public class Test {
 6     public static void main(String args[]) {
 7         Season seasons[] = Season.values();// Season类在编译时编译器默认提供了静态方法values。
 8         System.out.println(Arrays.toString(seasons));
 9         Season season = Season.valueOf("SPRING");
10         System.out.println(season);
11 
12     }
13 }
14 
15 
16 [SPRING, SUMMER, AUTUMN, WINTER]
17 SPRING
View Code

        说明:values()方法的作用就是获取枚举类中的所有变量,并且作为数组返回;valueOf(String name)同父类方法,只是它更简便。倘若将枚举实例向上转型成Enum,这两个方法都不可以被调用。比如:

 1 package com.test.a;
 2 
 3 import java.util.Arrays;
 4 
 5 public class Test {
 6     public static void main(String args[]) {
 7         Enum enum1 = Season.SPRING;
 8         enum1.valueOf("SPRING");// 只支持两个参数调用,编译错误
 9         enum1.values();// 编译错误,没有这个values方法
10 
11     }
12 }
View Code
   总结枚举类型它是一种特殊的数据类型,是因为它既是一种类(class)类型却又比类类型多了些特殊的约束,但是这些约束的存在也让枚举变得相对简单和安全。

 三. 基本用法

 1. 变量

    (1)name

相关的源码实现:

1 //对应的源码方法
2  public final String name() {
3         return name;
4    }
View Code
1 public String toString() {
2         return name;
3     }
View Code

     说明:name()方法和toString()方法是一样的,唯一的区别是,你可以重写toString方法。name变量就是枚举变量的字符串形式。举例:

 1 package com.test.a;
 2 
 3 public class Test {
 4     public static void main(String args[]) {
 5         Season season = Season.SPRING;
 6         String name = season.name();
 7         String name2 = season.toString();
 8         System.out.println(name);
 9         System.out.println(name2);
10 
11     }
12 }
View Code

输出:

SPRING

SPRING

    (2)ordinal

1  //对应的源码方法
2 public final int ordinal() {
3         return ordinal;
4     }
View Code

     说明:默认情况下,枚举类会给所有的枚举变量一个默认的次序,该次序从0开始,用ordinal变量表示。比如:

 1 package com.test.a;
 2 
 3 public class Test {
 4     public static void main(String args[]) {
 5         Season season = Season.SPRING;
 6         int ordinal = season.ordinal();
 7         System.out.println(ordinal);
 8 
 9     }
10 }
11 
12 输出:
13 0
View Code

 2. 方法

 就介绍一些上面没有提到过的:

(1)compareTo

       实际上就是比较的oridinal,如果为负,就代表前者小于后者。例如:

 1 package com.test.a;
 2 
 3 public class Test {
 4     public static void main(String args[]) {
 5         int i = Season.SPRING.compareTo(Season.WINTER);
 6         System.out.println(i);
 7 
 8     }
 9 }
10 
11 -3
View Code

(2)getDeclaringClass

       说明:该方法是获取枚举实例变量的方法。针对上面向上转型以后是不可以调用values方法和valueOf方法的,就可以通过这个getDeclaringClass和反射来获取枚举实例。

       在Class类中,有以下两个方法和枚举有关:

返回类型方法名称方法说明
T[] getEnumConstants() 返回该枚举类型的所有元素,如果Class对象不是枚举类型,则返回null。
boolean isEnum() 当且仅当该类声明为enum类型时返回true

       简单例子:

 1 package com.test.a;
 2 
 3 import java.util.Arrays;
 4 
 5 public enum Season {
 6     SPRING, SUMMER, AUTUMN, WINTER;
 7     public static void main(String args[]) {
 8         System.out.println(Season.SPRING.getDeclaringClass());
 9         System.out.println(Season.class.isEnum());
10         Season seasons[] = Season.class.getEnumConstants();
11         System.out.println(Arrays.toString(seasons));
12     }
13 }
14 
15 
16 class com.test.a.Season
17 true
18 [SPRING, SUMMER, AUTUMN, WINTER]
View Code

(3)getClass   和 getDeclaringClass的区别

        Note:上面例子中的getClass可以提到getDeclaringClass。会得到相同的结果。但是并不是总是能够得到相同的结果。比如以下情况就不可以:

 1 package com.test.a;
 2 
 3 public enum Season {
 4 
 5     SPRING {
 6         int getOrdinal() {
 7             return 0;
 8         }
 9     },
10     SUMMER {
11         int getOrdinal() {
12             return 1;
13         }
14     };
15 
16     public static void main(String args[]) {
17         System.out.println(Season.SPRING.getClass());
18         System.out.println(Season.SPRING.getDeclaringClass());
19     }
20 
21     abstract int getOrdinal();
22 
23 }
24 
25 class com.test.a.Season$1
26 class com.test.a.Season
View Code

        说明:上面的打印结果就不相同。SPRING和SUMMER相当于Season的内部类,并研究getDeclaringClass源码如下:

1 @SuppressWarnings("unchecked")
2     public final Class<E> getDeclaringClass() {
3         Class<?> clazz = getClass();
4         Class<?> zuper = clazz.getSuperclass();
5         return (zuper == Enum.class) ? (Class<E>)clazz : (Class<E>)zuper;
6     }
View Code

       根据源码逻辑,我们知道父类是不是Enum,决定了最终返回的是何种类型,分类更加的清晰。因此,判断枚举类型时,使用getDeclaringClass更有保障。

四. 高级用法

1. 向enum类中添加变量和方法

       enum其实和常规类的用法差不多,可以添加变量,方法,甚至是main方法。

 1 package com.test.a;
 2 
 3 public enum Season {
 4 
 5     SPRING("春天"), SUMMER("夏天"), AUTUMN("秋天"), WINTER("冬天");
 6 
 7     private String name;
 8 
 9     private Season(String name) {
10         this.name = name;
11     }
12 
13     public String getName() {
14         return name;
15     }
16 
17     public static void main(String[] args) {
18         for (Season day : Season.values()) {
19             System.out.println("name:" + day.name() + ",name:" + day.getName());
20         }
21     }
22 
23 }
View Code
1 name:SPRING,name:春天
2 name:SUMMER,name:夏天
3 name:AUTUMN,name:秋天
4 name:WINTER,name:冬天
View Code

      说明:根据上面代码示例可以知道,enum和常规类用法差不多。但是enum中方法或者变量的定义必须要在enum实例对象定义之后,否则编译器会报错;不能手动创建enum类的对象,因为这必须且只能编译器自己做。

2. 复写父类中的方法

    只支持toString方法的重写

3. enum中定义抽象方法

    在enum中可以定义抽象方法,并且在每个枚举实例中重写这个抽象方法,这样就可以让不同的枚举实例拥有不同的行为。比如:

 1 package com.test.a;
 2 
 3 public enum Season {
 4     SPRING {
 5 
 6         @Override
 7         public void f() {
 8             System.out.println("this is spring");
 9 
10         }
11 
12     },
13     WINTER {
14         @Override
15         public void f() {
16             System.out.println("this is winter");
17 
18         }
19     };
20 
21     public static void main(String[] args) {
22         Season.SPRING.f();
23         Season.WINTER.f();
24     }
25 
26     public abstract void f();
27 
28 }
View Code
1 this is spring
2 this is winter
View Code

   说明:enum类的实例似乎表现出了多态的特性,但是枚举类型的实例终究不能作为类型传递使用,一旦使用,编译器就会报错,比如

 public void text(Season.SPRING instance){ }  //编译错误,因为SEASON.SPRING是个实例对象
4. enum可以实现多个接口
1 public interface Base {    
2     public void f();
3 }
4 public interface Base2 {
5     public void g();
6 
7 }
View Code
 1 package com.test.a;
 2 
 3 public enum Season implements Base, Base2 {
 4     SPRING, WINTER;
 5 
 6     public static void main(String[] args) {
 7         Season.SPRING.f();
 8         Season.SPRING.g();
 9     }
10 
11     @Override
12     public void g() {
13         System.out.println("ggggg");
14 
15     }
16 
17     @Override
18     public void f() {
19         System.out.println("fffffff");
20     }
21 
22 }
23 
24 fffffff
25 ggggg
View Code

5. switch中可以支持枚举

六. EnumMap

EnumMap是专门针对枚举类型而创建的类。它的键依照ordinal的值作为put顺序。简单例子:

 1 public enum Season{
 2     WINTER,AUTUMN,SPRING;
 3 }
 4 
 5 public class Test {
 6     public static void main(String args[]) {
 7         EnumMap enumMap=new EnumMap(Season.class);
 8         enumMap.put(Season.AUTUMN, "秋天");
 9         enumMap.put(Season.WINTER, "冬天");
10         enumMap.put(Season.SPRING, "春天");
11         System.out.println(enumMap);
12     }
13 }
14 
15 {WINTER=冬天, AUTUMN=秋天, SPRING=春天}
View Code

   说明:根据输出结果可以知道和enum类定义中的ordinal顺序一致。

    知识点:EnumMap的基本实现原理,内部有两个数组,长度相同,一个表示所有可能的键,一个表示对应的值,值为null表示没有该键值对,键都有一个对应的索引,根据索引可直接访问和操作其键和值,效率很高。(知识点摘自:https://www.cnblogs.com/swiftma/p/6044672.html)

 

note:更多关于enumMap和EnumSet的用法后面再补充。还有关于枚举单例模式等,后面补充。

 

posted @ 2018-10-08 20:38  Hermioner  阅读(263)  评论(0编辑  收藏  举报