Effective Java : 枚举和注解

时间:2023-02-15 18:38:15

30.使用enum代替int常量

以前的方案

在枚举出现前,都是 使用常量的方式,如

public static final int APPLE_FUJI = 0;  
public static final int ORANGE_NAVEL = 0;

这种方称为枚举常量,其弊端有:

  • 如果与枚举常量关联的 int 发生变化,则必须重新编译
  • 如果将枚举常量翻译成可打印的字符串,只能见到一个数字,没有太大的用处.

枚举方式

java 中的枚举本质上是 int值.

public enum Apple{FUJI}
public enum Apple{NAVEL}
  1. 枚举类基本想法 : 通过公有的 final域为 每个枚举常量 导出实例的类.
  2. 因为没有可访问的构造器, 因此枚举类型都是 final
  3. 因为 客户端 无法创建 枚举实例,也不能对其 进行扩展,因此枚举是 实例受控的,单例的泛型化
  4. 编译时类型安全,声明后取值一定是 枚举中的有效值 之一
  5. 通过复写 toString,可以将 枚举的值打印出来.
  6. 枚举可以添加任意的方法和域,并实现任意的接口

枚举的高级用法

  • switch,枚举中可以通过 switch(this)来根据不同的 做不同的操作,

示例 : Operation.java

  • 当然,上面种方式并不好,建议使用 特定于常量的方法实现(onstant-specific method implementation) Operation1.java

  • 可以通过构造函数传递参数,例如,示例:Operation.java, op 打印是调用toString打印出了+,-,*,/;

  • 利用策略枚举,可以用在更加安全,灵活的场景.如:书中的加班场景,每添加一种枚举常量就强制添加一种策略,示例代码:PayrollDay.java

总结

  1. switch 枚举 适合于 给外部的 枚举类型 增加特定于 常量的行为.
  2. 一般来说,枚举会 优先使用 comparable类型.而非 int类型
  3. 需要一组固定常量的时候就可以使用枚举.
  4. 枚举 装载和初始化的时候会有 空间和时间 的成本.

31.用实例域代替序数

简介

枚举的 ordinal()方法会返回枚举常量在类型中的数字位置,
但是尽量不要使用它,因为当重新排序后,会对客户端造成破坏.
正确的做法是,将他保存在 一个 实例域 中.

示例

 public enum Ensemble {
SOLO(1), DUET(2), TRIO(3), QUARTET(4);

private final int numberOf;

Ensemble(int _i) {
this.numberOf = _i;
}

public int getNumberOf() {
return numberOf;
}
}

32.使用enumset代替位域

int 枚举模式

public class Text {
public static final int STYLE_BOLD = 1 << 0;
public static final int STYLE_ITALIC = 1 << 1;
public static final int STYLE_UNDERLINE = 1 << 2;
public static final int STYLE_STRIKETHROUGH = 1 << 3;

public void applyStyles(int styles) {
//...
}
}

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) {
// Body goes here
}
}
  1. EnumSet 实现了 Set 接口,提供了丰富的功能,类型安全.可以从其他任何Set中得到互换性.
  2. 整个 EnumSet 就是用 单个 long 来表示的,性能上比得上 位运算的性能.
  3. 总而言之因为枚举类型要用在集合(Set)中,所以没有理由用位域来表示.

33.用enummap代替序数索引

简介

这里的总体原则 和上一个 一致, 就是 尽量不要使用 ordinal() 方法.
以枚举 序数 作为 数组索引 总不是那么精确.

建议使用 EmumMap来索引数组,如果是 多维的 ,可以使用 Enum<...,Enum<?>>

书中 示例代码: Herb.java

34.用接口模拟可伸缩的枚举

简介

  1. 如果让一个 枚举类型 去扩展另一个 枚举类型,利用语言的特性,几乎是不可能的/
  2. 枚举的可扩展性,到最后都证明不是一个好点子.

接口模拟枚举的伸缩性

鉴于如上两点,我们可以利用枚举定义接口操作码类型来扩展枚举,使枚举具有可扩展性. 示例代码: Operation.java

一.定义如下接口

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;
}
},
//...
}

三. 扩展实现类型

public enum ExtendedOperation implements Operation {
EXP("^") {
public double apply(double x, double y) {
return Math.pow(x, y);
}
},
//...
}

总结

  1. 从示例中可以学到 泛型继承的更深层次用法,如
// test parameter is a bounded type token (Item 29)
private static <T extends Enum<T> & Operation> void test(Class<T> opSet,
double x, double y) {
for (Operation op : opSet.getEnumConstants())
System.out.printf("%f %s %f = %f%n", x, op, y, op.apply(x, y));
}

确保了Class 既是枚举类型,又是Operation的子类型

  1. 虽然无法编写可扩展的枚举类型,却可以通过接口来模拟其伸缩性.
  2. 这里唯一不足,如上的 保存和获取 与某项操作相关联的符号的逻辑代码,仍无法复用,最好建立辅助类helper来操作.

35.注解优先于命名模式

简介

1.5之前,Java使用的 是命名模式,如JUnit,这种模式有以下缺点:

  1. 拼写错误不能及时发现
  2. 无法保证命名只在正确的场景使用
  3. 没有值/类型信息,编译器无法提前发现问题

使用 注解 可以很好的解决 如上问题,通过元注解进行约定

  1. @Retention : 限定保留时期
  2. @Target: 限定其应用的程序元素
  3. 还有很多注解,如 @IntDef,@ViewDebug
  4. 注解接收的参数如果是数组,为其赋值一个单独的元素也是合法的,如下
@ExceptionTest({
IndexOutOfBoundsException.class,NullPointerException.class
})

36.坚持使用override属性

简介

@override 注解表明,被注解的方法声明覆盖了超类中的方法声明.
如果使用此注解,而不是 超类中的方法声明,就会报编译时错误

所以,最好是在 每个想要覆盖超类的方法 声明上添加 @override 注解

37.用标记接口定义类型

简介

标记接口就是 不包含方法声明的接口.指明一个实现类 具有 某种属性
Serializable表明实现类 可以被写到 ObjectOutputStream

第 35item 中了解过 标记注解 ,在这里所说的是标记接口.

标记接口 相当于 标记注解 有如下特点

  1. 标记接口定义的类型是由 被标记的类的实例 实现的,标记注解 则没有定义这样的类型
  2. 他们 可以被 更加精确的进行锁定,比如
    如果注解类型利用 @Target(ElementType.TYPE) 标记,则它可以被应用到任何类或者接口上

标记注解 胜过 标记接口 的地方

  1. 它可以通过 默认的方式 添加 一个或者多个注解类型元素,给 已被使用的注解类型添加更多的信息.随着时间的推移,简单的标记注解类型可以演变成更加丰富的注解类型.
  2. 他们是更大的注解机制的一部分.

何时使用注解,何时使用接口

  1. 如果标记是应用到任何程序元素而不是类或者接口,就必须使用注解. 因为只有 类和接口可以用来实现或者扩展接口
  2. 如果标记只应用给类和接口,就应该 优先使用标记接口而非注解

总而言之

  1. 如果想要定义 一个任何新方法都不会与之关联的类型,标记接口是最好的选择
  2. 如果想要标记程序元素而非类和接口,考虑到未来可能要给标记添加更多信息,标记注解是更好的选择.