第二十章 注解


在这里插入图片描述

0.概述
  1. 注解(也被称为元数据)为我们在代码中添加信息提供了一种形式化的方法,使我们可以在稍后某个时刻非常方便地使用这些数据
  2. 注解使我们能够以将由编译器来测试和验证的格式,存储有关程序的额外信息
  3. 注解的优点:更加干净易读的代码以及编译器类型检查等
  4. @Override,表示当前的方法定义将覆盖超类中的方法。如果你不小心拼写错误,或者方法签名对不上被覆盖的方法,编译器就会发出错误提示
  5. @Deprecated,如果程序员使用了注解为它的元素,那么编译器会发出警告信息
  6. @SuppressWarnings,关闭不当的编译器警告信息
  7. 注解(annotation)是在实际的源码几倍保存所有的信息,而不是某种注释行的文字(comment),这使得代码更整洁且便于维护
1.基本语法
  1. package Chapter20.Example01;
    import org.junit.Test;
    
    public class Testable {
          
          
        public void execute() {
          
          
            System.out.println("Executing...");
        }
        @Test
        void testExecute() {
          
          
            execute();
        }
    }
    
  2. 被注解的方法与其他的方法没有区别

  3. 从语法的角度来看,注解的使用方式几乎与修饰符的使用一模一样

  4. 定义注解
  • (1) 注解的定义看起来很像接口的定义。事实上,与其他任何Java接口一样,注解也会编译成class文件

  • (2)

    //: net/mindview/atunit/Test.java
    // The @Test tag.
    package net.mindview.atunit;
    import java.lang.annotation.*;
    
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Test {
          
          } ///:~
    

    除了@符号以外,@Test的定义很像一个空的接口。定义注解时,会需要一些元注释(meta-annotation),

    • @Target用来定义你的注解将应用于什么地方(例如是一个方法或者一个域)。
    • @Retention用来定义该注解在哪个级别可用,在源代码中(SOURCE)、类文件中(CLASS)或者运行时(RUNTIME)。
  • (3) 没有元素的注解别成为“标记注解(marker annotaion)”例如前面的@Test

  • (4) 注解的元素看起来和接口的方法一样,唯一的区别是你可以为其定义默认值

    package Chapter20.Example02;
    import java.lang.annotation.*;
    
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface UseCase {
          
          
      public int id();
      public String description() default "no description";
    } ///:~
    

    注:篇幅有限,只展示部分代码,其他代码请移步
    description元素有个default值,如果在注解的某个方法时,没有给出的description的值,则注解的处理器就会使用此元素的默认值

  • (5) 使用见:

    public class PasswordUtils {
          
          
    @UseCase(id = 47, description =
    "Passwords must contain at least one numeric")
    public boolean validatePassword(String password) {
          
          
      return (password.matches("\\w*\\d\\w*"));
    }
    @UseCase(id = 48)
    public String encryptPassword(String password) {
          
          
     return new StringBuilder(password).reverse().toString();
    }
    @UseCase(id = 49, description =
    "New passwords can't equal previously used ones")
    public boolean checkForNewPassword(
      List<String> prevPasswords, String password) {
          
          
      return !prevPasswords.contains(password);
    }
    }
    
  1. 元注解
  • (1) Java目前只内置了三种标准注解,以及四种元注解。元注解专职负责注释其他的注解
  • (2) 元注解在这里插入图片描述
2.编写注解处理器
  1. package Chapter20.Example02;//: annotations/UseCaseTracker.java
    import java.lang.reflect.*;
    import java.util.*;
    
    public class UseCaseTracker {
          
          
     public static void
     trackUseCases(List<Integer> useCases, Class<?> cl) {
          
          
    for(Method m : cl.getDeclaredMethods()) {
          
          
      UseCase uc = m.getAnnotation(UseCase.class);
      if(uc != null) {
          
          
    	System.out.println("Found Use Case:" + uc.id() +
    	  " " + uc.description());
    	useCases.remove(new Integer(uc.id()));
      }
    }
    for(int i : useCases) {
          
          
      System.out.println("Warning: Missing use case-" + i);
    }
     }
     public static void main(String[] args) {
          
          
    List<Integer> useCases = new ArrayList<Integer>();
    Collections.addAll(useCases, 47, 48, 49, 50);
    trackUseCases(useCases, PasswordUtils.class);
     }
    } /* Output:
    Found Use Case:47 Passwords must contain at least one numeric
    Found Use Case:48 no description
    Found Use Case:49 New passwords can't equal previously used ones
    Warning: Missing use case-50
    *///:~
    

    注:篇幅有限,只展示部分代码其他代码请移步
    getAnnoation()方法返回指定类型的注解对象,这里是UseCase

  2. 注解元素
  • (1) 注解元素可用的类型

    • i.所有基本类型(int,float,boolean等)
    • ii.String
    • iii.Class
    • iv.enum
    • v.Annotation
    • vi.以上类型的数组
  • (2) 不允许使用任何包装类型

    • 元素必须要么具有默认值,要么使用注解时提供元素的值
    • 对于非基本类型的元素,无论是在源代码中声明时,或是在注解接口中的定义默认值时,都不能以null作为其值
    • 为了绕开这不能表示某个元素不存在的约束,我们只能定义一些特殊的值,例如空字符串或负数
        package Chapter20.Example02;
        import java.lang.annotation.*;
        
        @Target(ElementType.METHOD)
        @Retention(RetentionPolicy.RUNTIME)
        public @interface SimulatingNull {
              
              
          public int id() default -1;
          public String description() default "";
        } ///:~
      

    注:篇幅有限,只展示部分代码,其他代码请移步

  1. 生成外部文件
  • 使用多个注解的时候,同一个注解不能重复使用
  1. 注解不支持继承
  • 不能使用关键字extends来继承某个@interface
3.使用apt处理注解
  1. 注解处理工具apt,被设计为操作Java源文件。apt会检查新生成的源文件中注解,然后将所有文件一同编译在
  2. 当注解处理器生成一个新的源文件时,该文件会在新一轮的注解处理中接收检查。该工具会一轮一轮的处理,直到不再有新的源文件产生为止。然后它再编译所有的源文件
4.将观察者模式用于apt
  • 一个访问者会遍历某个数据结构或一个对象的集合,对其中的每一个对象执行一个操作。该数据结构无需有序,而你对每个对象执行的操作,都是特定于此对象的类型。这就将操作与对象解耦,也就是说,你可以添加新的操作,而无需向类的定义中添加方法
5.基于注解的单元测试
  1. 单元测试是对类中的每个方法提供一个或多个测试的一种实践,其目的是为了有规律地测试一个类的各个部分是否具备正确的行为
  2. 在Java中,最著名的单元测试工具就是JUnit
  3. 一个失败的assert或从测试方法中抛出异常,都将被看作一个失败的测试,但是@Unit并不会就在这个失败的测试上打住,它会继续运行,直到所有的测试都运行完毕
  4. 对每一个单元测试而言,@Unit都会用默认的构造器,为该测试所属的类创建出一个新的实例。并在此新创建的对象上运行测试,然后丢弃该对象,以避免对其他测试产生副作用
  5. @TestProperty也可以用来标记那些只在测试中的方法,而它们本身又不是测试方法
  6. 加入@TestObjectCreate注解的方法必须声明未static,且返回一个你正在测试的类型的对象
  7. @TestObjectCleanup方法,当测试结束后,该方法会为你执行清理工作
  8. 将@Unit用于泛型
  • 让测试类继承自泛型类的一个特定版本
  1. 让测试类继承自泛型类的一个特定版本
  • (1) 在JUnit中,程序员必须告诉测试工具你打算测试什么,这就要求用套件来组织测试,以便JUnit能够找到它们,并运行其中包含的测试
  • (2) @Unit只是简单地搜索类文件,检查其是否具有恰当的注解,然后运行@Test
  1. 实现@Unit
  • 所有测试的保留属性必须是RUNTIME,因为@Unit系统必须在编译后的代码中查询这些注解
  1. 移除测试代码
  • 通过Javassist工具类,提供“-r”标志,那么它将删除所有的@Test注解,如果你没有提供该标志,那它则只会打印出@Test注解

猜你喜欢

转载自blog.csdn.net/Tianc666/article/details/108977212