枚举(enum)

枚举类型是指由一组固定的常量组成的合法值的类型。例如一年中的季节,太阳系的行星或者一副牌的花色等,在还没有引入枚举类型之前,表示枚举类型的常用模式是声明一组具名的int常量,一个类型成员一个常量,如下所示:

1     public static final int APPLE_FUJI = 0;
2     public static final int APPLE_PIPPIN = 1;
3     
4     public static final int ORANGE_NAVEL = 0;
5     public static final int ORANGE_TEMPLE = 1;

这样的方式叫做int枚举模式,这种模式有很多缺点:

  ⑴ 在类型的安全性和使用的方便性来说,对coder来说没有任何帮助;

  ⑵ 可以将不同代表性的值互相引用,并且编译器也不会出现警告(你可以将apple传到orange的方法中),这样即使程序不会出错,但对于代码的阅读者来说,很有可能会弄错;

  ⑶ 采用这种模式的程序十分脆弱,因为int枚举是编译时常量,被编译到使用它们的类中,如果与枚举常量关联的int发生了变化,那么这个类就必须重新编译,如果不重新编译,程序还是可以运行,但是运行的结果就不确定了(静态final常量在类编译的时候就确定了);

  ⑷ 遍历或者获得int枚举组的大小,没有很可靠的方法,调试或者打印出来的信息没有太大的用处,同时也很不方便;

 枚举(enum)

  可以替代这种模式,在避免了int枚举模式的缺点,同时提供了许多额外的好处。

  从Java1.5开始,我们就可以使用枚举来代替上述的模式了,下面简单将上述例子修改为枚举类型:

    public enum Apple{FUJI,PIPPIN}
    public enum Orange{NAVEL,TEMPLE}

   Java枚举类型背后的基本想法非常简单:它们就是通过公有的静态final域为每个枚举常量导出实例的类。对Java来说,Java的枚举本质上是int值。

枚举具有很多特点:

  ⑴ 因为没有可以访问的构造器,枚举类型是真正的final。因为使用枚举的类既不能创建枚举的实例,也不能对其进行扩展,因此很有可能没有实例,而只有声明过的枚举常量。换句话说就是,枚举类型是实例受控的。它们是单例的泛型化,本质上是单元素的枚举;

  ⑵ 枚举提供了编译时的类型安全;如果声明一个参数的类型为Apple,就可以保证被传到该参数上的任何非null的对象引用一定属于两个有效的Apple值之一。试图传递类型错误的值时,会导致编译时错误,就像试图将某种枚举类型的表达式赋给另一种枚举常量的变量,或者试图利用==来比较不同枚举类型一样(避免了上述int枚举模式的第二种缺点)。

  ⑶ 包含同名常量的多个枚举类型可以在一个系统中和平共处,因为每个类型都有自己的命名空间(你可以在Apple里面定义一个WEIGHT,同时在Orange里面定义一个WEIGHT)。

  ⑷ enum的常量值并没有被编译到使用它们的类中,而是在int枚举模式中。这样在枚举类型和使用它们的类中提供了一个隔离层。你可以增加或者重新排列枚举类型的常量,无需重新编译使用它们的类的代码。而且,你还可以通过toString方法,将枚举类型转换成打印的字符串。

  ⑸ 枚举类型允许添加任意的方法和域,并且还可以实现任意的接口。提供了所有的Object方法的高级实现,实现了Comparable和Serializable接口,并且还对枚举类型的可任意改变性设计了序列化的方式。

  ⑹ 枚举与int常量相比,枚举有个小小的性能缺点,即在装载和初始化枚举时会有空间和时间的成本(除了资源受限的设备,例如手机和烤面包机外,在实际中不必太在意这个问题)。

前面4个特点比较简单,说说第五个特点,看个例子:

 1 public enum Planet {
 2     MERCURY(3.302e+23,2.439e6),
 3     VENUS(4.869e+24,6.052e6),
 4     EARTH(5.975e+24,6.378e6),
 5     MARS(6.419e+23,3.393e6),
 6     JUPITER(1.889e+27,7.149e7);
 7 
 8     private final double mass;
 9     private final double radius;
10     private final double surfaceGravity;
11 
12     Planet(double mass,double radius){
13         this.mass = mass;
14         this.radius = radius;
15         this.surfaceGravity = 10 * mass / (radius * radius);
16     }
17 
18     public double mass(){
19         return mass;
20     }
21 
22     public double radius(){
23         return radius;
24     }
25 
26     public double surfaceGravity(){
27         return surfaceGravity;
28     }
29 
30     public double surfaceWeight(double mass){
31         return mass * surfaceGravity;
32     }
33 }

   编写一个像这样的Plant枚举类并不难。为了将数据与枚举常量联系起来,得声明实例域,并编写一个带有数据并将数据保存在域中的构造器。枚举天生就是不可变的,因此所有的域都应该是final的,这些域可以是公有的,但最好是私有的,并提供公有的访问方法。虽然这个枚举很简单,但是功能却很强大,如下所示:

1 public static void main(String[] args){
2         double earthWeight = 175d;
3         double mass = earthWeight / Planet.EARTH.surfaceGravity();
4         for (Planet p : Planet.values()){
5             System.out.printf("Weight on %s is %f%n ",p,p.surfaceWeight(mass));
6         }
7     }

输出结果:

1  Weight on MERCURY is 66.133672
2  Weight on VENUS is 158.383926
3  Weight on EARTH is 175.000000
4  Weight on MARS is 66.430699
5  Weight on JUPITER is 440.362707

  可以看出通过一段简单的代码就可以实现很多的功能,并且对打印出来的信息一目了然。

EnumSet和EnumMap

enumSet和enumMap分别是枚举类型的set和map,看一下它们的用法:

 1 public static void main(String[] args){
 2         EnumSet<Planet> es = EnumSet.allOf(Planet.class);
 3         for (Planet ed : es)
 4             System.out.println(ed.name() + ":" + ed.ordinal());
 5 
 6         System.out.println("\n-----EnumSet和EnumMap之间的分隔线-----\n");
 7 
 8         EnumMap<Planet, String> em = new EnumMap<Planet, String>(Planet.class);
 9         em.put(Planet.MERCURY, "水星");
10         em.put(Planet.VENUS, "金星");
11         em.put(Planet.EARTH, "地球");
12         em.put(Planet.MARS, "火星");
13         em.put(Planet.JUPITER, "木星");
14 
15         Iterator<Entry<Planet, String>> iterator = em.entrySet().iterator();
16         while (iterator.hasNext()){
17             Entry<Planet, String> entry = iterator.next();
18             System.out.println(entry.getKey().name() + ":" + entry.getValue());
19         }
20     }

输出结果:

MERCURY:0
VENUS:1
EARTH:2
MARS:3
JUPITER:4

-----EnumSet和EnumMap之间的分隔线-----

MERCURY:水星
VENUS:金星
EARTH:地球
MARS:火星
JUPITER:木星

注意:

  enumSet和enumMap都是非线程安全的。

 

  总而言之,与int常量相比,枚举类型的优势是不言而喻的。枚举要以读得多,也更加安全,功能更加强大。每当需要一组固定常量的时候,就应该使用枚举(行星,一周的天数,棋子的数目等)。

参考:《Effective Java》中文版 第二版;

posted on 2018-10-10 14:43  AoTuDeMan  阅读(586)  评论(0编辑  收藏  举报

导航