第六章、枚举和注解

Java 1.5发行版本新增了两个引用类型家族:枚举类型(Enumerate类)和注解类型(Annotation接口)。

第三十条、用enum代替int常量

  1. 枚举类型是指由一组固定的常量组成合法值的类型。替代之前的具名的int常量。
    public static final int APPLE_FUJI = 0;

    这种方式称作int枚举模式,存在诸多的不足:
    程序十分脆弱,因为int枚举是编译时常量,被编译到使用它们的客户端中。如果与枚举常量关联的int发生了变化,客户端就必须重新编译,否则,程序行为会变得不确定性。(还有个变体是String枚举模式)

  2. java的枚举类型是功能十分齐全的类,本质上是int值。
    基本想法是:通过公有的静态final域为每个枚举常量导出实例的类。枚举类型是实例受控的,它们是单例的泛型化,本质上是单元素的枚举

    枚举提供了编译时的类型安全,如果声明一个参数的类型为Apple,就可以保证,被传到该参数上的任何非null的对象引用一定属于三个有效的Apple值之一。试图传递类型错误的值时,会导致编译时错误。
    public enum Apple{FUJI,PIPPIN,GERNNY_SMITH}
    包含同名变量的多个枚举类型可以在一个系统中和平共处,因为每个类型都有自己的命名空间。

  3. 除了弥补int枚举常量的不足,枚举类型还允许添加任意的方法和域(近似于类),并实现任意的接口。它们提供了所有Object方法的高级实现,实现了Comparable和Serializable接口。我们为啥要将方法或者域加入到枚举类型中?将数据与它的常量关联起来。可以用任何适当的方法来增强枚举类型。

     /**
      * Created by laneruan on 2017/7/10.
      * 一个枚举类型的例子
      * 为了将数据与枚举常量关联起来,得声明实例域,并编写一个带有数据并将数据保存在域中的构造器。
      */
     public class EnumPlanet {
         public enum Planet{
             MERCURY(3.302e+23,2.439e6),
             VENUS(4.8693+24,6.052e6),
             EARTH(5.975e+24,6.378e6),
             MARS(6.419e+23,3.393e6),
             JUPITER(1.899e+27,7.149e7),
             SATURN(5.685e+26,6.027e7),
             URANUS(8.683e+25,2.556e7),
             NEPTUNE(1.024e+26,2.477e7);
             private final double mass;
             private final double radius;
             private final double surfaceGravity;
             private static final double G = 6.67300E-11;
     
             Planet(double mass,double radius){
                 this.mass = mass;
                 this.radius = radius;
                 surfaceGravity = G * mass/(radius * radius);
             }
             public double mass(){return mass;}
             public double radius(){return radius;}
             public double surfaceGravity(){return surfaceGravity;}
     
             public double surfaceWeight(double mass){
                 return mass * surfaceGravity;
             }
         }
         public static void main(String[] args){
             double earthWeight = Double.parseDouble(args[0]);
             double mass = earthWeight/Planet.EARTH.surfaceGravity();
             for (Planet p : Planet.values()){
                 System.out.println("Weight on " + p +" is "+p.surfaceWeight(mass));
             }
         }
     }
    

    与枚举常量关联的有些行为,可能只需要用在定义了枚举的类或者包中,这种行为最好被实现成私有的或者包级私有的方法。
    如果一个枚举具有普遍适用性,它就应该成为一个顶层类。如果它是被用在一个特定的顶层类中,它就应该成为该顶层类的一个成员类。

  4. 特定于常量的方法实现:将不同的行为与每个枚举常量关联起来

     public enum Operation{
         PLUS{
             @Override
             double apply(double x, double y) {
                 return x+y;
             }
         },
         MINUS{
             @Override
             double apply(double x, double y) {
                 return x-y;
             }
         },
         TIMES{
             @Override
             double apply(double x, double y) {
                 return x*y;
             }
         },
         DIVIDE{
             @Override
             double apply(double x, double y) {
                 return x/y;
             }
         };
         abstract double apply(double x,double y),
     }
    
  5. 什么时候应该使用枚举类型?
    每当需要一组固定常量的时候。包括:天然的枚举类型;在编译的时候就知道其所有可能值的集合。

  6. 总结:与int常量相比,枚举类型的优势不言而喻:易读,更加安全。功能更加强大。许多枚举都不需要显式的构造器或者成员,但许多其他枚举类型则受益于“每个常量与属性的关联”以及“提供行为受这个属性影响的方法”。


第三十一条、用实例域代替序数

许多枚举天生就与一个单独的int值相关联,所有的枚举都有一个ordinal方法,它返回每个枚举常量在类型中的数字位置。可以试着从序数中得到关联的int值。最好避免使用ordinal方法。

永远不要根据枚举的序数导出与它想关联的值,而是要将它保存在一个实例域中:

    public enum Ensemble{
        SOLO(1),DUET(2),TRIO(3),QUARTET(4),QUINTET(5),
        SETET(6),SEPET(7),OCTET(8),NONET(9),DECTET(10);
        private final int numberOfMusicians;
        Ensemble(int size){
            this.numberOfMusicians = size;
        }
        public int numberOfMusicians(){return numberOfMusicians;}
    //   public int numberOfMusicians(){return ordinal()+1;}
    }

第三十二条、用EnumSet代替位域

需要传递多组常量集时,java.util包提供了EnumSet类来有效地表示从单个枚举类型中提取多个值的多个集合,这个类实现了Set接口,提供了丰富的功能。

    class Text{
        public enum Style {BOLD,ITALIC,UNDERLINE,STRIKETHROUGH}
        public void applyStyles(Set