Effective Java学习笔记--39-41条 注解

目录

注解的使用

简单注解使用

带参数的注解使用

多值注解的使用

注解与命名模式相比的优势

坚持使用@Override注解

标记接口与注解的对比


注解本身是一种元数据机制,它提供了一种在代码中提供额外信息的方式,程序可以根据这些注解来执行特定的动作。对于注解的基本介绍在之前的文章里已经详细介绍了(Java注解总结),这里补充一下自定义注解的使用案例,其与命名方式相比的一些优势以及与标记接口的对比。

注解的使用

简单注解使用

考虑自定义一个Test注解来标注测试类中的测试方法。

1、首先定义一个自定义的Test注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Test {
    
}

//@Interface关键字定义注解
//@Retention注解指定注解的保存策略
//@Target注解指定注解的应用范围

2、然后将注解应用在测试类的测试方法上


public class Sample{
    @Test
    public static void m1(){};

    @Test
    public static void m2(){
        throw new RuntimeException("m2 failed");
    };

    @Test
    public void m3(){};

    @Test
    public static int m4(int input){
        return input;
    }
}

//这里Sample类中定义了四个方法:
//m1:公共静态方法
//m2:抛出异常的公共静态方法
//m3:公共非静态方法
//m4:有返回值的公共静态方法

3、通过反射机制调用有Test注解的方法



public class RunTest {

    public static void main(String[] args) throws Exception{
        int test=0;
        int pass=0;
        Class<?> testclass = Class.forName(args[0]);
        for(Method m : testclass.getDeclaredMethods()){
            if(m.isAnnotationPresent(Test.class)){
                test++;
                try{
                    m.invoke(null);
                    pass++;
                }catch(InvocationTargetException wrappedExc){
                    Throwable exc = wrappedExc.getCause();
                    System.out.println(m + " failed: " + exc);
                }catch(Exception exc){
                    System.out.println("Invalid @Test: " + m);
                }
            }
        }
        System.out.printf("Passed: %d, Failed: %d%n", pass, test-pass);
    }
}

要注意这里的反射调用方法没有实例和参数,因此默认调用静态方法。这也是书里为什么强调该注释方法只能用于静态方法的原因。

如果测试方法抛出异常(m2),反射机制会将它封装在InvocationTargetException中。如果抛出其他异常,说明该@Test标注的方法是无效方法(invoke方法无法调用)。

调用结果:

(base) MacBook-Pro:TestSample $ java RunTest Sample
public static void Sample.m2() failed: java.lang.RuntimeException: m2 failed
Invalid @Test: public void Sample.m3()
Invalid @Test: public static int Sample.m4(int)
Passed: 1, Failed: 3

带参数的注解使用

还是上面的例子,如果要求筛选出抛出特定异常的方法:

首先自定义ExceptionTest注解:


@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ExceptionTest {
    Class<? extends Exception> value();
}

注意,这里注解中多了一个value方法,它返回的是一个Exception的子类型。

然后重新将测试类的方法进行标注:在这里通过ExceptionTest筛选出抛出RuntimeException的方法。

public class TestWithInput{
    @ExceptionTest(RuntimeException.class)
    public static void m1(){};

    @ExceptionTest(RuntimeException.class)
    public static void m2(){
        throw new RuntimeException("m2 failed");
    };

    @ExceptionTest(RuntimeException.class)
    public void m3(){};

    @ExceptionTest(RuntimeException.class)
    public static int m4(int input){
        return input;
    }
}

最后就是基于方法反射的检测方法:

public class RunTestwithInput {
    public static void main(String[] args) throws Exception {
        int targetmethod = 0;
        int passed = 0;
        Class<?> claz = Class.forName(args[0]);
        for(Method m : claz.getDeclaredMethods()){
            if(m.isAnnotationPresent(ExceptionTest.class)){
                targetmethod++;
                try{
                    m.invoke(null);
                    System.out.printf("Test %s passed%n", m);
                }catch(InvocationTargetException WrappedEx){
                    Throwable ex = WrappedEx.getCause();
                    Class<? extends Throwable> excType = m.getAnnotation(ExceptionTest.class).value();
                    if(excType.isInstance(ex)){
                        passed++;
                        System.out.printf("Test %s throws targeted Exception: %s%n", m, ex.getClass().getName());
                    }else{
                        System.out.printf("Test %s failed: expected %s, got %s%n", m, excType.getName(), ex.getClass().getName());
                    }
                }catch(Exception ex){
                    System.out.println("INVALID @Test: " + m);
                }

            }
        }
    }

}

//这里通过try...catch进行三种情况的处理:
//1. 测试方法正常执行:显示Test Passed;
//2. 测试方法有效,但是抛出异常,这里就是检测抛出的异常是否是筛选的特定异常;
//3. 测试方法无效,显示Invalid。

执行的结果如下:通过ExceptionTest注解筛选出了有效方法中的 RuntimeException。

(base) MacBook-Pro:TestSample $ java RunTestwithInput TestWithInput
Test public static void TestWithInput.m2() throws targeted Exception: java.lang.RuntimeException
Test public static void TestWithInput.m1() passed
INVALID @Test: public void TestWithInput.m3()
INVALID @Test: public static int TestWithInput.m4(int)

多值注解的使用

多值注解即允许给同一个对象进行多次注释的注解。这可以让对象带上多个注解对象。比如上面的异常检测注解,如果想同时筛选出NullPointerException和IndexOutOfBoundsException,就需要用到多值注解:

首先定义注解ExceptionTest和其对应的容器类ExceptionTestContainer,然后通过@Repeatable注解将两者进行关联。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Repeatable(ExceptionTestContainer.class)
public @interface ExceptionTest {
    Class<? extends Exception> value();
}


@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface ExceptionTestContainer{
    ExceptionTest[] value(); 
}

之后就可以对目标方法进行多值注解:

@ExceptionTest(IndexOutOfBoundsException.class)
@ExceptionTest(NullPointerException.class)
public static void doublyBad(){...}

注解与命名模式相比的优势

了解了注解的使用后,就可以对比一下其与命名模式的优劣。命名模式就是通过在特定对象中留下特定名称来对对象进行标注的一种注释方式,在Junit4之前的单元测试框架中比较常见。注解的优势主要有以下三条:

  1. 命名模式如果拼写出现错误,将会导致失败并且没有特定的提示,而注释名称标记错误会有注解不存在的错误提示。
  2. 命名模式一般只针对方法,如果要编写一个测试类,则要对所有的方法都要进行标注。而注解可以直接标注在类上。
  3. 命名模式无法让注释带有一个参数并使用到程序中,但是注解可以通过带参数注解的形式实现。

坚持使用@Override注解

这一条归根到底两句话,为什么对于父类方法的覆盖要使用Override:

  1. 可以分清楚重载和覆盖。(如果不注意导致两个方法名称一样,但是签名的输入不一样,不使用Override不会报任何错误,因为程序默认作为重载处理)
  2. 对于没有Override,但实际上覆盖了超类方法的情况会出告警,以避免无意识覆盖。

标记接口与注解的对比

标记接口是不包含方法声明的接口,它只是指明一个类实现了具有某种属性的接口,只是对类做的一个筛选。比如Serializable接口,通过这个接口的实现,类可以被允许写入ObjectOutPutStream当中。标记接口相对于注解有几个优势:

1、标记接口可以直接参与到程序的执行当中去,所以在编译时就能够捕捉到相关标注,而注解要到运行时才可以。

public class Person implements Serializable{...}


public class App {
    public static void main(args[]){
        Serializable OutPutperson = new Person(...);
    }

}

2、标记接口可以作用于更精确的范围--特定类或者接口及其实例(而注解只能通过Element.Type.TYPE指定应用到类还是方法等等)。

但是注解有一个无可取代的优势,那就是只要是除了类与接口的注释(比如方法),只能使用注解。

所以,首先要看注解的对象,如果是类或者接口的注解要再考虑一下这个注解的类或者接口是要具体标记某些个特定实例还是整体的类,如果是前者,那就需要通过标记接口来实现。

  • 15
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 《Effective Java第三版》是由Joshua Bloch所著的一本Java编程指南。这本书是基于第二版的更新版本,目的是给Java程序员提供一些最佳实践和经验,以编写高效、可维护和可靠的Java代码。 这本书共分为15个章节,每个章节都讲解了一个与Java开发有关的重要主题。比如,章节一讲述了使用静态工厂方法代替构造器的优点,章节二则介绍了如何用Builder模式来构建复杂的对象。此外,书中还提及了Java对象的等价性、覆盖equals方法和hashCode方法、避免创建不必要的对象、使用泛型、枚举、lambda表达式等等。 《Effective Java第三版》通过具体的代码示例和清晰的解释来说明每个主题的关键概念,使读者能够更好地理解和应用。此外,书中还提供了一些实用的技巧和技术,例如避免使用原始类型、尽量使用接口而非类来定义类型等。 总的来说,这本书提供了很多实用的建议和技巧,可以帮助Java开发者写出高质量的代码。无论是初学者还是有经验的开发者,都可以从中受益匪浅。无论你是打算从头开始学习Java编程,还是已经有一定经验的开发者,这本书都是值得推荐的读物。 ### 回答2: 《Effective Java 第三版》是由Joshua Bloch 所著的一本Java编程指南,是Java程序员必读的经典之作。该书共包含90个目,涵盖了各种Java编程的最佳实践和常见问题的解决方法。 本书分为多个部分,每个部分都侧重于一个特定的主题。作者探讨了Java编程中的各种问题和挑战,并提供了解决方案和建议。这些建议包括如何选择和使用合适的数据结构和算法,如何设计高效的类和接口,如何处理异常和错误,以及如何编写可读性强的代码等等。 《Effective Java 第三版》还关注了Java编程中的性能优化和安全性问题。作者强调了遵循Java语言规范、使用标准库、防范常见安全漏洞等重要原则。此外,本书还介绍了Java 8及其后续版本的新特性和用法,如Lambda表达式、流式编程和Optional类等。 这本书的特点之一是每个目都独立于其他目,可以单独阅读和理解。每个目开头都有一个简洁的总结,让读者能够快速掌握主要观点。此外,书中还有大量的示例代码和解释,帮助读者更好地理解和运用所学知识。 总的来说,《Effective Java 第三版》是一本非常实用和全面的Java编程指南。它适用于各个层次的Java程序员,无论是初学者还是有经验的开发人员,都可以从中获得宝贵的经验和知识。无论是编写高质量的代码、优化性能还是确保安全性,这本书都是一本不可或缺的参考书籍。 ### 回答3: 《Effective Java 第3版(中文版)》是由 Joshua Bloch 所著的一本关于使用 Java 编程语言的指南书。该书是对 Java 语言的最佳实践的详尽描述,为中高级 Java 开发人员提供了许多实用的建议和技巧。 该书的主要内容包括Java 语言的优雅编程风格、类和接口的设计、Lambda 表达式和流的使用、泛型、异常和并发编程等方面的最佳实践。 在《Effective Java 第3版(中文版)》中,许多传统的 Java 开发中的陷阱、常见错误和不良习惯都得到了深入的剖析和解答。它不仅提供了可供开发人员参考的示例代码,还解释了为什么某种方式是有问题的,以及如何更好地进行改进。 该书的深度和广度非常适合正在努力提高 Java 编程技能的开发人员。它涵盖了多个关键领域,为读者提供了在实际项目中解决常见问题的方法和思路。 此外,《Effective Java 第3版(中文版)》还介绍了最新版本的一些特性和改进。例如,它详细说明了如何正确地使用 Java 8 中新增的 Lambda 表达式和流,以及如何充分利用 Java 9、10 和 11 中的新功能。 总之,这本书是 Java 开发人员必备的指南之一。通过深入理解和应用书中的实践建议,读者可以更加高效地编写、优化和维护 Java 代码。无论是想提升职业技能还是在项目中减少错误和问题,这本《Effective Java 第3版(中文版)》都是一本非常有帮助的参考书。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值