目录
Item 34: Use enums instead of int constants
Item 35: Use instance fields instead of ordinals
Item 36: Use EnumSet instead of bit fields
Item 37: Use EnumMap instead of ordinal indexing
Item 38: Emulate extensible enums with interfaces
Item 39: Prefer annotations to naming patterns
Item 40: Consistently use the Override annotation
Item 41: Use marker interfaces to define types
Item 34: Use enums instead of int constants
用枚举代替int常量
术语:
int/string enum pattern:int/string枚举模式(enum之前的方式,在类中定义一些列int/string常量)
int constants:int常量,即int enum pattern
constant-specific class body:特定于常量的类主体(每个枚举常量的定义部分)
constant-specific method implementations:特定于常量的方法实现(枚举中定义的抽象方法实现)
优势:
枚举相对于int/string enum pattern来说,可读性强、更安全、功能更强大。
// Month是自定义的枚举
System.out.println(Month.February); // 打印结果:February
System.out.println(Month.February.ordinal()); // 打印结果(第2个元素,序号为1):1
System.out.println(Month.valueOf("February")); // 打印结果:February
使用场景:
当需要一个常量集合,且这个集合的元素在编译时可以确定时使用,如表示月份、8大行星等等集合。
Item 35: Use instance fields instead of ordinals
使用实例域代替序数
不要使用enum中元素的序号(通过ordinal()方法获取),可以通过新增一个实例域来代替这个自带的序号。
enum中的ordinal()方法的设计目的:用于像EnumSet、EnumMap这种基于枚举的通用数据结构。
Item 36: Use EnumSet instead of bit fields
用EnumSet代替位域
位域的缺点:可解释性差、难遍历、难预测位的长度(调整int/long的长度)。
EnumSet是Set的一种实现,相对其他Set的优势:针对enum场景进行了优化,有速度等优势。
// bit fields枚举实现
public class Text {
public static final int BOLD = 1 << 0; // 1
public static final int ITALIC = 1 << 1; // 2
public void applyStyles(int styles) {
// 执行位操作,如交集、并集等
}
}
// EnumSet实现(EnumSet是Set的子类)
public class Text1 {
public enum Style {BOLD, ITALIC}
public void applyStyles(Set<Style> styles) {
// 用集合操作代替位操作,实现相同效果,如交集、并集等
}
}
// 客户端代码
text.applyStyles(EnumSet.of(Sytle.BOLD, Sytle.ITALIC)); // 入参的EnumSet包含两个元素
Item 37: Use EnumMap instead of ordinal indexing
用EnumMap代替序数索引
当统计各enum元素所对于的实体数量时,不要用ordinal()获取的序数来分类,而应该用EnumMap来代替。
EnumMap是Map的一种实现,相对其他Map的优势:针对enum场景进行了优化,有速度快、内存消耗小等优势。
Item 38: Emulate extensible enums with interfaces
用接口模拟可扩展的枚举
// 接口定义
public interface Operation { double apply(double x, double y); }
// 实现接口的enum定义
public enum ExtendOperation implements Operation {...}
// test函数定义,&是与(and)的意思
private static <T extends Enum<T> & Operation> void test(Class<T> opEnumType, double x, double y) {
for (Operation op : opEnumType.getEnumConstants()) {
System.out.printf("%f %s %f = %f%n", x, op, y, op.apply(x, y));
}
}
// 客户端代码
test(ExtendOperation.class, 10, 3);
test函数定义中,<T extends Enum<T> & Operation>表示T是Enum和Operation的子类,&是逻辑与的意思,&后面的类型必须为接口。若写成只写<T extends Operation>也能正常运行,只是运行到opEnumType.getEnumConstants()时报错。
Item 39: Prefer annotations to naming patterns
注解优先于命名模式
术语:
marker annotation:标记注解,比如:@Test。
命名模式指的是通过某种命名格式来表示特殊用途,如junit4以前的测试函数都是以test为前缀命名测试方法,示例:testApply()。这样有诸多弊端,如容易出现拼写错误后难以发现。junit4开始以@Test注解来标识。
下面展示一段使用自定义注解来实现特殊功能的代码:
// 特殊功能处理逻辑:对含注解ExceptionTest的方法的异常进行处理
Class<?> testClass = Class.forName("org.example.six.ExceptionTest");
for (Method m : testClass.getDeclaredMethods()) {
// if (m.isAnnotationPresent(ExceptionTest.class) || m.isAnnotationPresent(ExceptionTestContainer.class) ) { // 进阶代码(替换下一行):使ExceptionTest可以同一个方法上重复使用
if (m.isAnnotationPresent(ExceptionTest.class)) {
try {
m.invoke(null); // 因为调用的方法为static,所以第1个参数写null
System.out.printf("Test %s failed: no exception%n", m);
} catch (InvocationTargetException e) {
Throwable exc = e.getCause();
// ExceptionTest[] excTypeList = m.getAnnotationsByType(ExceptionTest.class); // 进阶代码(需补充数组处理代码):使ExceptionTest可以同一个方法上重复使用
Class<? extends Throwable> excType = m.getAnnotation(ExceptionTest.class).value();
if (!excType.isInstance(exc)) {
System.out.printf("Test %s failed: expected %s, got %s%n", m, excType.getName(), exc);
}
} catch (Exception e) {
System.out.println("Invalid Test: " + m);
}
}
}
// 自定义注解的定义
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
// @Repeatable(ExceptionTestContainer.class) // 进阶代码:使ExceptionTest可以同一个方法上重复使用
public @interface ExceptionTest {
Class<? extends Throwable> value();
}
// 自定义注解的使用
public class SampleSix {
// 符号预期的异常
// @ExceptionTest(NullPointerException.class) // 进阶代码:使ExceptionTest可以同一个方法上重复使用
@ExceptionTest(ArithmeticException.class)
public static void m1() {
int i = 0;
i = i / i;
}
// 预期之外的异常
@ExceptionTest(ArithmeticException.class)
public static void m2() {
int[] a = new int[0];
int i = a[1];
}
// 无异常
@ExceptionTest(ArithmeticException.class)
public static void m3() {
}
// 无异常,带入参
@ExceptionTest(ArithmeticException.class)
public static void m4(String s) {
}
}
// 进阶代码:使ExceptionTest可以同一个方法上重复使用的注解定义
//@Retention(RetentionPolicy.RUNTIME)
//@Target(ElementType.METHOD)
//public @interface ExceptionTestContainer {
// ExceptionTest[] value(); // 需定义ExceptionTest数组
//}
Item 40: Consistently use the Override annotation
坚持使用Override注解
@Override注解的好处:在重写父类方法时,如果出现方法签名(名称、入参类型、返回参数)错误,会通过编译错误来提示。
例外场景(最好还是加上@Override):在“继承抽象类”/“实现无default方法的接口”的非抽象类中,实现抽象/接口方法时可写可不写@Override,因为未实现的抽象方法也会主动报编译错误。
Item 41: Use marker interfaces to define types
用标记接口定义类型
术语:
marker interface:标记接口,指不含方法声明的接口,用来标明一个实现了该接口的类具有某种属性,从而可用来处理特殊需求,类似于marker annotation,如Serializable接口:
public interface Serializable {}
标记接口的应用:
限定一些方法只能处理某种类型的对象,如ObjectOutputStream.writeObject(Object obj) ,入参必须是实现了Serializable的实例,否则抛出NotSerializableException异常,writeObject()的源码解析如下:
// writeObject(Object obj)调用了writeObject0(Object obj, boolean unshared),writeObject0内有如下代码:
if (obj instanceof String) {
...
} else {
if (extendedDebugInfo) {
throw new NotSerializableException(
cl.getName() + "\n" + debugInfoStack.toString());
} else {
throw new NotSerializableException(cl.getName());
}
}
marker interface VS marker annotation:
标记接口好处:
(1)发出异常的阶段方面,标记接口在编译时发现异常(可通过isinstanceof判断),而标记注解只能在运行时发现异常(因为通过反射实现)。
(2)在限制标记对象的类型方面,标记接口更精确,具体到指定一种类型,而标记注解(ElementType.TYPE类型注解)用在各个类/接口上(本人觉得这方面注解也可以达到相同效果)。
标记注解好处:
(1)用一套更强大的注解机制/框架支撑。
如何决策用哪种标记类型:
你需要写一些函数方法,只处理有标记对象类型 --> 标记接口
框架中已大量使用注解 --> 标记注解