Effective Java读书笔记十二(Java Tips.Day.12)

TIP 35 注解优先于命名模式

命名模式的问题


  1. 文字拼写错误会导致失败,且没有任何提示。
  2. 无法确保它们只用于相应的程序元素上。
  3. 它们没有提供将参数值与程序元素关联起来的好方法。

注解可以很好地解决这些问题。假设想要定义个注解类型来指定简单的测试,它们自动运行,并在抛出异常时失败:

/**
 * 只能用于无参的静态方法.
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Test {
}

Test注解类型的声明就是它自身通过Retention和Target注解进行了注解。注解类型声明中的这种注解被成为元注解(meta-annotation)

@Retention(RetentionPolicy.RUNTIME) 表明Test注解应该在运行时保留。如果没有保留,测试工具就无法知道Test注解。
@Target(ElementType.METHOD) 表明,Test注解只在方法声明中才是合法的。

注释说明这个注解只能用于无参的静态方法。但是很遗憾,编译器不能强制这一限制。

下面就是现实应用中的Test注解:

public class Sample {
    @Test
    public static void m1(){}
    public static void m2(){}
    @Test
    public static void m3(){
        throw new RuntimeException("Boom");
    }
    public static void m4(){}
    @Test
    public void m5(){}
    public static void m6(){}
    @Test
    public static void m7(){
        throw new RuntimeException("Crash");
    }
    public static void m8(){}
}

测试类:

public class RunTest {
    public static void main(String args[]) throws Exception {
        int tests = 0;
        int passed = 0;
        //请注意这里forName的参数,必须写上类的全名,包含包名
        Class testClass = Class.forName("douvril.effect.tip34.Sample");
        for (Method m : testClass.getDeclaredMethods()) {
            if (m.isAnnotationPresent(Test.class)){
                tests++;
                try{
                    m.invoke(null);
                    passed++;
                }catch (InvocationTargetException wrappedExc){
                    //当反射调用的方法内部抛出异常时,最终将抛出这个异常
                    Throwable exc = wrappedExc.getCause();
                    System.out.println(m+" failed: "+exc);
                }catch (Exception e){
                    System.out.println("INVALID @Test: "+m);
                }
            }
        }
        System.out.println("Passed:"+passed+" , Failed:"+(tests - passed));
    }
}

运行结果:

public static void douvril.effect.tip34.Sample.m7() failed: java.lang.RuntimeException: Crash
public static void douvril.effect.tip34.Sample.m3() failed: java.lang.RuntimeException: Boom
INVALID @Test: public void douvril.effect.tip34.Sample.m5()
Passed:1 , Failed:3

可以看到只有一个方法通过了测试,就是m1方法。而Boom和Crash的则是m3和m7方法。
至于非法调用的m5,则是在调用时捕获到了Exception ,而非 InvocationTargetException —— 这表明方法内部并没有抛出异常。如果在捕获Exception的字句中,打印该异常信息则会发现其实是java.lang.NullPointerException
因为m5不是静态方法,所以调用时必须有一个Sample实例作为参数,而我们传入的是null ——– 而这正式Test声明的注释内容所要求的。


现在我们要针对只在抛出特殊异常时才成功的测试添加支持,为此声明一个新的注解类型:

/**
 * 必须抛出一个指定的异常才算成功
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ExceptionTest {
    Class<? extends Exception> value();
}

public class Sample2 {
    @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(){} //测试会失败,因为根本不会抛出任何异常
}

看看运行结果:

Test public static void douvril.effect.tip34.Sample2.m3() failed: no exception.
Test public static void douvril.effect.tip34.Sample2.m2() failed: expected java.lang.ArithmeticException , got java.lang.ArrayIndexOutOfBoundsException: 1
Passed:1 , Failed:2

代码工作正常。只有抛出@ExceptionTest(ArithmeticException.class) 指定的这个ArithmeticException 异常, 才算测试成功。


总结。
请仔细回味,最好亲自上手运行本条中的所有代码。它们清楚的展示了注解之余命名模式的优越性。

既然有了注解,就完全没有必要使用命名模式了。

除非是工具铁匠(toolsmiths – 特定的程序员) , 否则大多数程序员都不必定义注解类型。但是所有的程序员都应该使用Java平台所提供的预定义注解类型(参考TIP 36 和 TIP 24)。

还要考虑使用IDE或静态分析工具提供的任何注解。这种注解可以提升由这些工具所提供的诊断信息的质量。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值