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