Java基础:枚举Enum

在某些情况下,一个类的对象是有限而且固定的,比如季节类,它只有4个对象:春、夏、秋、冬。这种实例有限而且固定的类,在Java里被称为枚举类。

1 手动实现枚举类

示例:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package  com.demo2;
 
public  class  Season {
 
     public  static  final  Season SPRING =  new  Season( "春天" "趁春踏青" );
     public  static  final  Season SUMMER =  new  Season( "夏天" "夏日炎炎" );
     public  static  final  Season FALL =  new  Season( "秋天" "秋高气爽" );
     public  static  final  Season WINTER =  new  Season( "冬天" "围炉观雪" );
 
     public  static  Season getSeason( int  seasonNum) {
 
         switch  (seasonNum) {
         case  1 :
             return  SPRING;
         case  2 :
             return  SUMMER;
         case  3 :
             return  FALL;
         case  4 :
             return  WINTER;
         default :
             return  null ;
         }
 
     }
 
     private  final  String name;
     private  final  String desc;
 
     private  Season(String name, String desc) {
         this .name = name;
         this .desc = desc;
     }
 
     public  String getName() {
         return  name;
     }
 
     public  String getDesc() {
         return  desc;
     }
}
?
1
2
3
4
5
6
7
8
9
10
11
package  com.demo2;
 
public  class  Test2 {
     public  Test2(Season s) {
         System.out.println(s.getName() +  "-----"  + s.getDesc());
     }
 
     public  static  void  main(String[] args) {
         new  Test2(Season.SPRING);
     }
}

如果需要手动实现枚举,可以采用如下设计方式:

  • 通过private把构造器隐藏起来。

  • 把这个类的所有实例都使用public static final修饰的类变量来保存。

  • 如果有必要,提供一些静态方法,允许其他程序根据特定参数来获取与之匹配的实例。

PS:有些程序员喜欢使用简单地定义几个静态常量来表示枚举类。

例如:

?
1
2
3
4
public  static  final  int  SEASON_SPRING= 1 ;
public  static  final  int  SEASON_SUMMER= 2 ;
public  static  final  int  SEASON_FALL= 3 ;
public  static  final  int  SEASON_WINTER= 4 ;

这种方式存在几个问题:

  • 类型不安全。例子中都是整型,所以季节可以进行+,-,*,/等int运算。

  • 没有命名空间。这些静态常量只能靠前缀“SEASON_”来划分所属类型。

  • 打印输出,意义不明确。如果使用System.out.println()打印SEASON_SPRING,输出是1,不是SPRING。


2 枚举类Enum

JDK 5新增了一个关键字Enum,它与class,interface的地位相同,用来定义枚举类。枚举类其实是一个特殊的类,它可以有自己的Field,方法,构造函数,可以实现一个或多个接口。

2.1 定义枚举类

示例:

?
1
2
3
4
5
package  com.demo2;
 
public  enum  SeasonEnum{
     SPRING, SUMMER, FALL, WINTER;
}

编译该段代码,将生成一个SeasonEnum.class文件,说明枚举类是一种特殊的类。

定义枚举类时,需要显式地列出所有的枚举值,例如上面的SPRING, SUMMER, FALL, WINTER;所示,所有的枚举值之间以英文逗号(,)隔开,枚举值列举结束后,以英文分号作为结束。这些枚举值代表了该枚举类的所有可能实例。

2.2 枚举类本质

将2.1中生成的SeasonEnum.class文件反编译:

?
1
javap -c SeasonEnum. class  > SeasonEnum.txt

结果如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
Compiled from  "SeasonEnum.java"
public  final  class  com.demo2.SeasonEnum  extends  java.lang.Enum<com.demo2.SeasonEnum> {
   public  static  final  com.demo2.SeasonEnum SPRING;
 
   public  static  final  com.demo2.SeasonEnum SUMMER;
 
   public  static  final  com.demo2.SeasonEnum FALL;
 
   public  static  final  com.demo2.SeasonEnum WINTER;
 
   static  {};
     Code:
        0 new            # 1      // class com/demo2/SeasonEnum
        3 : dup           
        4 : ldc           # 15     // String SPRING
        6 : iconst_0      
        7 : invokespecial # 16     // Method "<init>":(Ljava/lang/String;I)V
       10 : putstatic     # 20     // Field SPRING:Lcom/demo2/SeasonEnum;
       13 new            # 1      // class com/demo2/SeasonEnum
       16 : dup           
       17 : ldc           # 22     // String SUMMER
       19 : iconst_1      
       20 : invokespecial # 16     // Method "<init>":(Ljava/lang/String;I)V
       23 : putstatic     # 23     // Field SUMMER:Lcom/demo2/SeasonEnum;
       26 new            # 1      // class com/demo2/SeasonEnum
       29 : dup           
       30 : ldc           # 25     // String FALL
       32 : iconst_2      
       33 : invokespecial # 16     // Method "<init>":(Ljava/lang/String;I)V
       36 : putstatic     # 26     // Field FALL:Lcom/demo2/SeasonEnum;
       39 new            # 1      // class com/demo2/SeasonEnum
       42 : dup           
       43 : ldc           # 28     // String WINTER
       45 : iconst_3      
       46 : invokespecial # 16     // Method "<init>":(Ljava/lang/String;I)V
       49 : putstatic     # 29     // Field WINTER:Lcom/demo2/SeasonEnum;
       52 : iconst_4      
       53 : anewarray     # 1      // class com/demo2/SeasonEnum
       56 : dup           
       57 : iconst_0      
       58 : getstatic     # 20     // Field SPRING:Lcom/demo2/SeasonEnum;
       61 : aastore       
       62 : dup           
       63 : iconst_1      
       64 : getstatic     # 23     // Field SUMMER:Lcom/demo2/SeasonEnum;
       67 : aastore       
       68 : dup           
       69 : iconst_2      
       70 : getstatic     # 26     // Field FALL:Lcom/demo2/SeasonEnum;
       73 : aastore       
       74 : dup           
       75 : iconst_3      
       76 : getstatic     # 29     // Field WINTER:Lcom/demo2/SeasonEnum;
       79 : aastore       
       80 : putstatic     # 31     // Field ENUM$VALUES:[Lcom/demo2/SeasonEnum;
       83 return        
 
   public  static  com.demo2.SeasonEnum[] values();
     Code:
        0 : getstatic     # 31     // Field ENUM$VALUES:[Lcom/demo2/SeasonEnum;
        3 : dup           
        4 : astore_0      
        5 : iconst_0      
        6 : aload_0       
        7 : arraylength   
        8 : dup           
        9 : istore_1      
       10 : anewarray     # 1      // class com/demo2/SeasonEnum
       13 : dup           
       14 : astore_2      
       15 : iconst_0      
       16 : iload_1       
       17 : invokestatic  # 39     // Method java/lang/System.arraycopy:(Ljava/lang/Object;ILjava/lang/Object;II)V
       20 : aload_2       
       21 : areturn       
 
   public  static  com.demo2.SeasonEnum valueOf(java.lang.String);
     Code:
        0 : ldc           # 1     // class com/demo2/SeasonEnum
        2 : aload_0       
        3 : invokestatic  # 47    // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
        6 : checkcast     # 1     // class com/demo2/SeasonEnum
        9 : areturn       
}

java.lang.Enum的源码结构如下:


根据字节码和java.lang.Enum源码结构可以得出如下结论:

  • 使用enum定义的枚举类默认直接继承了java.lang.Enum类,而不是直接继承java.lang.Object。其中java.lang.Enum实现了java.lang.Comparable<E>, java.lang.Serializable两个接口。

  • 使用enum定义、非抽象的枚举类默认会使用final修饰,因此非抽象枚举类不能派生子类。

  • 枚举类的构造函数只能使用private访问控制符。如果省略了构造函数的访问控制符,默认会使用private修饰;如果强制指定访问控制符,只能使用private。

  • 枚举类的所有实例必须在枚举类的第一行显示列出,否则这个枚举类永远不都不能产生实例。列出这些实例时,系统会自动添加public statci final修饰符,无须程序员显示添加。

Java实现枚举的本质:

  • java.lang.Enum定义枚举对象的组成成分。一个枚举对象包含两个属性“ordinal”和“name”,一系列实例方法,例如toString、compareTo等等。“ordinal”是索引值,根据枚举声明的位置赋值,从0开始。“name”是枚举对象的名称。例如public enum SeasonEnum{SPRING, SUMMER, FALL, WINTER;}中,SPRING的索引值为0,name为“SPRING”;SUMMER的索引值为1,name为“SUMMER”.........。

  • 编译时,编译器(javac)在自定义枚举类的.class文件中,添加static{}初始化块,初始化块包括生成枚举对象的指令。在示例字节码中,static{}初始化块里依次生成SPRING枚举对象并赋值索引值0,name为“SPRING”,生成SUMMER枚举对象并赋值索引值1,name为“SUMMER”..........最后,在SeasonEnum类中还定义了一个静态成员“ENUM$VALUES”,它是一个SeasonEnum数组,依据索引对象的“ordinal”值按顺序存放索引对象。

2.3 枚举类与switch结构

如果需要使用枚举类的某个实例,可以使用“枚举类.某个实例”的形式,例如SeasonEnum.SPRING。

示例:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package  com.demo2;
 
public  class  Test {
 
     public  static  void  judge(SeasonEnum s) {
 
         switch  (s) {
         case  SPRING:
             System.out.println( "春天,趁春踏青" );
             break ;
         case  SUMMER:
             System.out.println( "夏天,夏日炎炎" );
             break ;
         case  FALL:
             System.out.println( "秋天,秋高气爽" );
             break ;
         case  WINTER:
             System.out.println( "冬天,围炉观雪" );
             break ;
         }
 
     }
 
     public  static  void  main(String[] args) {
         judge(SeasonEnum.FALL);
     }
}

上面程序中的switch表达式中,使用了SeasonEnum对象作为表达式,这是JDK 5增加枚举后对switch的扩展:switch的控制表达式可以是任何枚举类型。不仅如此,当switch控制表达式使用枚举类型时,后面case表达式中的值直接使用枚举值名字,无须添加枚举类作为限定。


3 枚举类与构造函数

枚举类是一种特殊类,它也可以有自己的Field、方法和构造函数。但是从枚举“对象是有限而且固定的”含义层面上,枚举对象应该是状态不可变的对象,即枚举类的所有Field都应该用final修饰。因为Field都使用了final修饰符来修饰,所以必须在构造函数里为这些Field指定初始化值(或者在定义Field时指定默认值,或者在初始化块中指定初始值,但这两种情况并不常见),因此应该为枚举类显式定义带有参数的构造函数。

一旦为枚举类显式定义了带参数的构造函数,列出枚举值时,就必须对应地传入参数。

示例:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package  com.demo2;
 
public  enum  SeasonEnum {
     SPRING( "趁春踏青" ), SUMMER( "夏日炎炎" ), FALL( "秋高气爽" ), WINTER( "围炉观雪" );
     private  final  String desc;
 
     private  SeasonEnum(String desc) {
         this .desc = desc;
     }
 
     public  String getDesc() {
         return  desc;
     }
 
}

不难看出,上面程序中SPRING("趁春踏青"), SUMMER("夏日炎炎"), FALL("秋高气爽"), WINTER("围炉观雪");等同于如下代码:

?
1
2
3
4
private  static  final  SeasonEnum SPRING= new  SeasonEnum( "趁春踏青" );
private  static  final  SeasonEnum SUMMER= new  SeasonEnum( "夏日炎炎" );
private  static  final  SeasonEnum FALL= new  SeasonEnum( "秋高气爽" );
private  static  final  SeasonEnum WINTER= new  SeasonEnum( "围炉观雪" );


4 枚举类与接口

枚举类也可以实现一个或多个接口。与普通类实现一个或多个接口完全一样,枚举类实现一个或多个接口时,也需要实现该接口所包含的方法。

例如:

?
1
2
3
4
5
package  com.demo2;
 
public  interface  SeasonDesc {
     void  info();
}
?
1
2
3
4
5
6
7
8
9
10
11
package  com.demo2;
 
public  enum  SeasonEnum  implements  SeasonDesc {
     SPRING, SUMMER, FALL, WINTER;
 
     @Override
     public  void  info() {
         System.out.println( "这是一个定义春夏秋冬的枚举类" );
     }
 
}

枚举类实现接口不过如此,与普通类实现接口完全一样:使用implements实现接口,实现接口里包含的抽象方法。

如果由枚举类来实现接口里的方法,则每个枚举类对象在调用该方法时,都有相同的行为方式(因为方法体完全一样)。如果需要每个枚举对象在调用该方法时,呈现出不同的行为方式,则可以让每个枚举对象分别来实现该方法。

示例:

?
1
2
3
4
5
package  com.demo2;
 
public  interface  SeasonDesc {
     void  info();
}
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package  com.demo2;
 
public  enum  SeasonEnum  implements  SeasonDesc {
     SPRING {
         @Override
         public  void  info() {
             System.out.println( "趁春踏青" );
         }
     },
     SUMMER {
         @Override
         public  void  info() {
             System.out.println( "夏日炎炎" );
         }
     },
     FALL {
         @Override
         public  void  info() {
             System.out.println( "秋高气爽" );
         }
     },
     WINTER {
         @Override
         public  void  info() {
             System.out.println( "围炉观雪" );
         }
     };
}

编译该代码,可以看到生成了”SeasonEnum.class“、”SeasonEnum$1.class“、”SeasonEnum$2.class、”SeasonEnum$3.class、”SeasonEnum$4.class“,这几个.class文件证明了结论:SPRING、SUMMER、FALL和WINTER实际上是SeasonEnum匿名子类的实例,而不是SeasonEnum类的实例

反编译SeasonEnum.class:

?
1
javap -c SeasonEnum. class  > se.txt

SeasonEnum.class字节码:

?
1
2
3
public  abstract  class  com.demo2.SeasonEnum  extends  java.lang.Enum<com.demo2.SeasonEnum>  implements  com.demo2.SeasonDesc {
...........
}

反编译SeasonEnum$1.class:

?
1
javap -c  SeasonEnum$ 1 . class  > se1.txt

SeasonEnum$1.class字节码(SeasonEnum$2.class、SeasonEnum$3.class、SeasonEnum$4.class同理):

?
1
2
3
class  com.demo2.SeasonEnum$ 1  extends  com.demo2.SeasonEnum {
..........
}

PS:枚举类不是应该用final修饰的吗?怎么还能派生子类呢?

并不是所有的枚举类都使用了final修饰。非抽象的枚举类才默认使用final修饰。对于一个抽象的枚举类而言(只要它包含了抽象方法,它就是抽象枚举类),系统会默认使用abstract修饰,而不是使用final修饰。

每个枚举对象提供不同的实现方式,从而让不同的枚举对象调用该方法时,具有不同的行为方式。


5 枚举类与抽象方法

如果不使用接口,而是直接在枚举类中定义一个抽象方法,然后再让每个枚举对象提供不同的实现方式。

示例:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package  com.demo2;
 
public  enum  SeasonEnum {
     SPRING {
         @Override
         public  void  info() {
             System.out.println( "趁春踏青" );
         }
     },
     SUMMER {
         @Override
         public  void  info() {
             System.out.println( "夏日炎炎" );
         }
     },
     FALL {
         @Override
         public  void  info() {
             System.out.println( "秋高气爽" );
         }
     },
     WINTER {
         @Override
         public  void  info() {
             System.out.println( "围炉观雪" );
         }
     };
 
     abstract  void  info();
}

编译该代码,可以看到生成了”SeasonEnum.class“、”SeasonEnum$1.class“、”SeasonEnum$2.class“、”SeasonEnum$3.class“、”SeasonEnum$4.class“文件。这跟“4 枚举类与接口”的原理是一样的:SPRING、SUMMER、FALL和WINTER实际上是SeasonEnum匿名子类的实例,而不是SeasonEnum类的实例。

SeasonEnum是一个包含抽象方法的枚举类,但是“public enum SeasonEnum”没有使用abstract修饰,这是因为编译器(javac)会在编译生成.class时,自动添加abstract。同时,因为SeasonEnum包含一个抽象方法,所以它的所有枚举对象都必须实现抽象方法,否则编译器会报错。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值