• 枚举(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("
    -----EnumSet和EnumMap之间的分隔线-----
    ");
     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》中文版 第二版;

    作者:Joe
    努力了的才叫梦想,不努力的就是空想,努力并且坚持下去,毕竟这是我相信的力量
  • 相关阅读:
    Python—模块
    Python之路_Day5
    Python之路_Day4
    Py获取本机指定网卡的ip地址
    Python之路_Day3
    Python之路—Day2作业
    Python之路—Day2
    Python之路—Day1作业
    Python之路—Day1
    Python数据类型
  • 原文地址:https://www.cnblogs.com/Joe-Go/p/9761658.html
Copyright © 2020-2023  润新知