Contents

Effective Java 2 Enum Annotation

第30条:用enum代替int常量

Java枚举背后思想:它们通过公有的静态final域为每个枚举常量导出实例的类。因为它们没有可以访问的构造器,枚举类型是真正的final。 因为客户端既不能创建枚举类型的实例,也不能对它进行扩展,因为很可能没有实例,而只有声明过的枚举常量。 换句话说,枚举类型是实例受控的。它们是单例的泛型化,本质上是单元素枚举。

1、枚举类型还允许添加任意的方法和域,并实现任意的接口。 目的:增强枚举类型。

如:为了将数据和枚举常量关联起来,得声明实例域,并编写一个带有数据并将数据保存在域中的构造器。

1
2
3
4
5
6
7
8
public enum Demo{
	ONE(1),TWO(2);
	private final val;
	Demo(int val){this.val = val;}
	public void getVal(){
		return val;
	}
}

2、如果一个枚举具有普遍适用性,它就应该是以顶层类。如果是被用在一个特定的顶层类中,它就应该顶层类的一个成员类。

3、特定于常量的方法实现

将不同行为与每个枚举类型关联起来方法:在枚举类型中声明一个抽象的apply方法,并在特定于常量的类主体中,用具体的方法覆盖每个常量的抽象apply方法,这种方法称作特定于常量的方法实现。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public enmu Operation{
	PLUS("+")	{int apply(int x, int y){return x + y;}},
	MINUS("-")	{int apply(int x, int y){return x - y;}},
	TIMES("*")	{int apply(int x, int y){return x * y;}},
	DIVIDE("/")	{int apply(int x, int y){return x / y;};
	private final String symbol;
	Operation(String symbol){this.symbol = symbol;}	
	@Override 
	public String toString(){return symbol;}	
	abstract int apply(int x, int y);
}

注意:枚举类型有一个自动产生的valueOf(String)方法,它将常量的名字转变成常量本身。如果枚举类型中覆盖了toString方法,就要编写一个fromString方法,将指定字符串表示法变回相应的枚举。如下添加部分:

1
2
3
4
public static final Map<String,Operation> stringToEnum = new HashMap<String,Operation>(4);
static{
	for(Operation op : ... 
}

4、策略枚举

 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
//The strategy enum pattern
enum PayrollDay{
	MONDAY(PayType.WEEKDAY),TUESDAY(PayType.WEEKDAY),
	WEDNESDAY(PayType.WEEKDAY),THURSDAY(PayType.WEEKDAY),
	FRIDAY(PayType.WEEKDAY),
	SATURDAY(PayType.WEEKEND),SUNDAY(PayType.WEEKEND);
	
	private final PayType payType;
	PayrollDay(PayType payType) {this.payType = payType;}	
  	double pay(double hoursWorked, double payRate){
		return payType.pay(hoursWorked, payRate);
	}
	
	//The strategy enum type
	private enum PayType{
		WEEKDAY{
			double overtimePay(double hours, double payRate){
				return hours <= BASE_HOURS ? 0 : (hours - BASE_HOURS) * payRate / 2;
			}
		},
		WEEKEND{
			double overtimePay(double hours, double payRate){
				return hours * payRate / 2;
			}
		};
		private static final int BASE_HOURS = 8;
		abstract double overtimePay(double hours, double payRate);
		double pay(double hoursWorked, double payRate){
			double basePay = hoursWorked * payRate;
			return basePay + overtimePay();
		}
	}
}

枚举中的switch并不是在枚举中实现特定于常量行为的一种很好选择,可以采用抽象函数+策略枚举的形式

5、使用场景

  • 当需要一组固定常量时,如菜单的选项、操作代码、命令标记等。
  • 当每个枚举常量都有特定的行为,考虑特定于常量的方法实现,即使用abstract
  • 当多个枚举常量同时共享相同的行为,考虑策略枚举

第31条:用实例域代替序数

序数:许多枚举天生就与一个单独的int值相关联,即所有枚举都有一个ordinal方法,它返回每个枚举常量在类型中的数字位置(默认从0开始)。 永远不要根据枚举的序数导出与它关联的值,而是要将它保存在一个实例域中。如下

1
2
3
4
5
6
7
8
public enum Demo{
	ONE(1),TWO(2);
	private final val;
	Demo(int val){this.val = val;}
	public void getVal(){
		return val;
	}
}

第32条:用EnumSet代替位域

位域:让你利用OR位运算将几个常量合并到一个集合中。如下

1
2
3
4
private static int ONE = 1 << 0;	// 1
private static int TWO = 1 << 0;	// 2
private static int THREE = 1 << 0;	// 4
private static int FOUR = 1 << 0;	// 8

当需要传递多组常量集时,通过java.util包里的EnumSet类来返回一个Set类型的常量集。如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import java.util.*;
public class demo{
	public static enum Style {RED,BLUE,BLACK};
	public static void appStyles(Set<Style> style){
		for(Style s : style)
			System.out.println(s);
	}
	public static void main(String[] args){
		appStyles(EnumSet.of(Style.RED,Style.BLUE,Style.BLACK));
	}
}

总而言之,正因为枚举类型要用在集合(Set)中,所以没有理由用位域来表示它。

第33条:用EnumMap代替序数索引

EnumMap是专门为枚举类型量身定做的Map实现。虽然使用其它的Map实现(如HashMap)也能完成枚举类型实例到值得映射,但是使用EnumMap会更加高效:

它只能接收同一枚举类型的实例作为键值,并且由于枚举类型实例的数量相对固定并且有限,所以EnumMap使用数组来存放与枚举类型对应的值。这使得EnumMap的效率非常高。

1
2
3
4
5
Map<Herb.Type, Set<Herb>> h = new EnumMap<Herb.Type, Set<Herb>>(Herb.Type.class);
for (Herb.Type t : Herb.Type.values())
    h.put(t,new HashSet<Herb>());
for (Herb m : garden)
	h.get(m.type).add(m);

以上代码实现了,Herb类型和具体的Herb的映射。

 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
public enum  Phase {
    SOLID, LIQUID, GAS;

    public enum Transition{
        MELT(SOLID, LIQUID), FREEZE(LIQUID, SOLID),
        BOIL(LIQUID, GAS);

        private final Phase src;
        private final Phase dst;

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

    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);
    }
}

以上代码,采用了2次索引实现了Map(起始阶段,Map(目标阶段,阶段过渡))形式。

使用场景: 需要一个枚举类型映射一个对象时,使用EnumMap

总之最好不要用序数来索引数组,而要使用EnumMap,如果所表示的关系式多维的,就使用EnumMap<…, EnumMap<…»

第34条:用接口模拟可伸缩的枚举

总而言之,虽然无法编写可扩展的枚举类型,却可以通过编写接口以及实现该接口的基础枚举类型,对它进行模拟。

第35条:注解优先于命名模式

命名模式:表示有些程序元素需要通过某种工具或者框架进行特殊处理。(如JUnit需要用户用test作为测试方法名称的开通)。这样做存在很多缺陷,这里就不列举了。

注解: 注解基本概念 自定义注解入门 注解处理器

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Test {
    Class<? extends Throwable> expected() default Test.None.class;

    long timeout() default 0L;

    public static class None extends Throwable {
        private static final long serialVersionUID = 1L;

        private None() {
        }
    }
}

第36条:坚持使用Override注解

@Override只能用在方法声明中,表示被注解的方法声明覆盖了超类型中的一个声明。 告诉编译器该方法是复写的,让编译器检查是否复写正确。

第37条:用标记接口定义类型

标记接口:没有包含方法声明的接口,而只是指明一个类实现了具有某种属性的接口。 (如:Serializable接口,通过实现它表明它的实例可以被写到ObjectOutputStream)

注意: 标记接口定义的类型是由被标记类的实例实现的;标记注解则没定义这样的类型。 如果想要定义一个任何新方法都不会与之关联的类型,使用标记接口。 如果想要标记程序元素而非类和接口,考虑未来可能要给标记添加更多信息,使用标记注解。