读书笔记:Effective Java-第6章 枚举和注解

11 篇文章 0 订阅
11 篇文章 0 订阅

目录

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)用一套更强大的注解机制/框架支撑。

如何决策用哪种标记类型:

你需要写一些函数方法,只处理有标记对象类型  --> 标记接口

框架中已大量使用注解 --> 标记注解

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值