java5 引入的新的枚举类型一直没怎么注意,以为和其他语言一样,就是一个代替常量来保证安全使用的基本数据结构。
今天刚翻完Effective java 2nd,才注意到java 的枚举不一样的地方。

和其他语言不同的地方是,java的枚举本身一个标准的java类,只是不能扩展,构造器也不可以直接访问。

enum 的构造器,可以显示的声明,用来给enum的 instance变量赋值或者做一些操作。自身也可以使用方法
比如下面的代码段。

 IL_DOTS(0);         // dots inlays

    
/** The upcharge for the feature */

    
private float upcharge;


    
private GuitarFeatures(float upcharge) {

    
this.upcharge = upcharge;

    }



      其中IL_DOTS 就是调用了构造器,并给自身的实例变量upcharge 进行了扶植。


这样除了替换一般的常量使用的话,还可以结合java类自身的特点,比如接口,方法等,以更加安全的方式把枚举做类进行使用。


作者给出了多个条款,阐述如何利用枚举类型来实现类型安全的代码编写。下面是条款细节和自己做的一些笔记
 
Item 3: Enforce the singleton property with a private constructor or an enum type 。
使用枚举的特性来实现单件模式。 枚举自身的构造器是私有方式的,可以通过简单的方式就实现单件模式

a single-element enum type is the best way to implement a singleton.

枚举本身的特性使得他非常适合设计成singleton类。下面代码是一个以枚举方式实现的singleton类, 作者认为这种singleton模式实现方式是最好的
参看下面代码中singleton的实现。 INSTANCE代表了枚举的一个实例。

 

public enum Elvis {
        INSTANCE;
        
public void leaveTheBuilding() {
                System.out.println(
"this is a joke");
        }

        
        
public static void main(String[] args) {
                Elvis.INSTANCE.leaveTheBuilding();
        }

}



和使用utility方式的差别是不需要把方法都声明为static的,在语法上会更加灵活.


item 30: Use enums instead of int constants

使用枚举来代替整形常量, 好处除了类型安全以为, 还因为枚举本身也是java类,可以提供方法,有助于实现一些复杂的行为。
缺点是在性能上比int有所下降。

 

Item 31: Use instance fields instead of ordinals
不要直接使用枚举中的求序号(ordinals)方式来获取绑定值,应该使用实例变量来代替。

enum提供一个ordinal方法来返回返回枚举常量的序数(它在枚举声明中的位置,其中初始常量序数为零)。
但是此方法一般只在一些通用的数据结构中使用, 比如EnumSet 和EnumMap。

对此方法的一种误用就是期望用次序号来和常量进行简单的绑定,由此可以得到某常量的一个伪值
比如
public enum Ensemble {
        SOLO, DUET, TRIO, QUARTET, QUINTET, SEXTET, SEPTET, OCTET, NONET, DECTET;
        public int numberOfMusicians() {
                return ordinal() + 1;
        }
}

但是这种做法比较危险,因为常量数字有可能发生变化,既然enum也是类,所以应该使用类的instance field来完成这种值绑定行为。

public enum Ensemble {
        SOLO(1), DUET(2), TRIO(3), QUARTET(4), QUINTET(5), SEXTET(6), SEPTET(7), OCTET(
                        8), DOUBLE_QUARTET(8), NONET(9), DECTET(10), TRIPLE_QUARTET(12);
        private final int numberOfMusicians;

        Ensemble(int size) {
                this.numberOfMusicians = size;
        }

        public int numberOfMusicians() {
                return numberOfMusicians;
        }
}
Item 32: Use EnumSet instead of bit fields
使用EnumSet代替位类型字段。 位类型字段常见于一些需要做异或操作叠加的属性, 比如字体,样式等等。

public class Text {
   public static final int STYLE_BOLD          = 1 << 0; // 1
   public static final int STYLE_ITALIC        = 1 << 1; // 2
   public static final int STYLE_UNDERLINE     = 1 << 2; // 4
   public static final int STYLE_STRIKETHROUGH = 1 << 3; // 8
  // Parameter is bitwise OR of zero or more STYLE_ constants
  public void applyStyles(int styles) { ... }
}

text.applyStyles(STYLE_BOLD | STYLE_ITALIC);

这样使用的样式就是bold和itatlic的交集

正如item30 提到的使用 enum来代替 常量int一样,此处可以使用enum和enumSet结合来完成同样的工作

public class Text {
public enum Style { BOLD, ITALIC, UNDERLINE, STRIKETHROUGH }
// Any Set could be passed in, but EnumSet is clearly best
public void applyStyles(Set<Style> styles) { ... }
}

text.applyStyles(EnumSet.of(Style.BOLD, Style.ITALIC));
EnumSet的of返回的是一个有2个Style的集合,然后再在方法applyStyles中进行处理即可。

这里面要注意的是,实际对旧有的代码无法做此种改造,比如java2d里面的字体和样式设定等等。 此处的推荐更多应该是放在
设计新的框架代码时使用。

我个人觉得这条用处不大,使用非bit方式的编码, 有助于提高代码的可读性,但是一般程序员多少都应该有此概念,就不是大问题了,而既然旧的代码无法改,两种方式混合用,恐怕头更大。

Item 33: Use EnumMap instead of ordinal indexing
使用EnumMap代替使用枚举常量的序号进行操作

比如如下代码, 使用序号来完成映射操作,是比较常见的技巧。

public enum Phase { SOLID, LIQUID, GAS;
   public enum Transition { MELT, FREEZE, BOIL, CONDENSE, SUBLIME, DEPOSIT;
           private static final Transition[][] TRANSITIONS = {
              { null,    MELT,     SUBLIME },
              { FREEZE,  null,     BOIL    },
              { DEPOSIT, CONDENSE, null    }
           };
          // Returns the phase transition from one phase to another
          public static Transition from(Phase src, Phase dst) {
             return TRANSITIONS[src.ordinal()][dst.ordinal()];
          }
   }
}


这种做法的问题在于编译器无法准确知道 TRANSITIONS的映射关系,所以无法保证代码的正确性。
而使用enummap,其中的key值是必须在enum中的,就可以静态的保证正确性。
public enum Phase {
        SOLID, LIQUID, GAS;
        public enum Transition {
                MELT(SOLID, LIQUID), FREEZE(LIQUID, SOLID), BOIL(LIQUID, GAS), CONDENSE(
                                GAS, LIQUID), SUBLIME(SOLID, GAS), DEPOSIT(GAS, SOLID);
                final Phase src;
                final Phase dst;

                Transition(Phase src, Phase dst) {
                        this.src = src;
                        this.dst = dst;
                }

                // Initialize the phase transition map
                private static final Map<Phase, Map<Phase, Transition>> m
                        = new EnumMap<Phase, Map<Phase, Transition>>(
                                Phase.class);
                static {
                        for (Phase p : Phase.values())
                                m.put(p, new EnumMap<Phase, Transition>(Phase.class));
                        for (Transition trans : Transition.values())
                                m.get(trans.src).put(trans.dst, trans);
                }

                public static Transition from(Phase src, Phase dst) {
                        return m.get(src).get(dst);
                }
        }
}

不过我个人觉得这种代码只适合写安全性要求较高的框架级别代码,否则写的这么啰嗦,烦死了。使用数组来映射,用UT来保证类型安全也是一样的,代码可读性更好。

Item 34: Emulate extensible enums with interfaces
可以使用使用接口的方式来模拟枚举的扩展,
这是因为枚举类被设计成为final的,原则上无法进行继承方式的扩展。

此处以操作码做列子进行了讲解。
public interface Operation {
        double apply(double x, double y);
}


public enum BasicOperation implements Operation {
        PLUS("+") {
                public double apply(double x, double y) {
                        return x + y;
                }
        },
        MINUS("-") {
                public double apply(double x, double y) {
                        return x - y;
                }
        },
        TIMES("*") {
                public double apply(double x, double y) {
                        return x * y;
                }
        },
        DIVIDE("/") {
                public double apply(double x, double y) {
                        return x / y;
                }
        };
        private final String symbol;

        BasicOperation(String symbol) {
                this.symbol = symbol;
        }

        @Override
        public String toString() {
                return symbol;
        }
}

此处BasicOperation不能被直接扩展,可以依赖接口来实现一个扩展的enum
//Emulated extension enum
public enum ExtendedOperation implements Operation {
    EXP("^") {
        public double apply(double x, double y) {
            return Math.pow(x, y);
        }
    },
    REMAINDER("%") {
        public double apply(double x, double y) {
            return x % y;
        }
    };
    private final String symbol;
    ExtendedOperation(String symbol) {
        this.symbol = symbol;
    }
    @Override public String toString() {
        return symbol;
    }
}

因为两个enum都实现了Operation接口, 那么当然也就可以通过接口来统一操作

        public static void main(String[] args) {
                double x = Double.parseDouble(args[0]);
                double y = Double.parseDouble(args[1]);
                test(Arrays.asList(ExtendedOperation.values()), x, y);
        }

        private static void test(Collection<? extends Operation> opSet, double x,
                        double y) {
                for (Operation op : opSet)
                        System.out.printf("%f %s %f = %f%n", x, op, y, op.apply(x, y));
        }
此处传递ExtendOperation或者BasicOperation都可以, 也可以合并在一起传。


按作者的说法,这种实现的一个主要缺点就是既然不是真正的继承结构,所以会有一些代码冗余,可以通过帮助类来解决。

我觉得作者有点,算了,还是不侮辱我偶像了, 这个例子的前提是enum设计使用了统一的接口,也就是说如果想扩展别人做好的东西,那是不行的, 而自己的东西这么设计,目的可能是为以后留一个扩展,而调用的代码就复杂了不少,这样的需求,我还是倾向于直接使用一个类结构来解决。


作者对enums的使用,整了一些比较复杂的方法,主要是基于java的枚举本身也是一种特殊的类来考虑的,但是个人觉得这些做法反而增加了代码的复杂度,强调编译期安全的问题,其实可以由UT来解决,还有助于培养团队成员的良好工作习惯。

当然了,读完这部分内容,对java枚举会有更好的认识。

感觉新版的Effective java处处都在强调如何做到类型安全, 另外对java 5的内容关注的非常多,介绍了一些比较复杂的技巧和用法,算是长了不少见识,就是有些过于啰嗦了,不知道是不是人年纪太大了。