Java注解(Annotation)

291 阅读5分钟

0.0 Hello World

先上代码,再加以说明。这样不至于让初学者懵。

public class Dog extends Animal(){
  @Override
  public void call(){
    System.out.println("汪汪...");
  }
}

0.1 Java自带注解 - @Override

方法call()上声明的@Override是Java自带的注解,它的作用是标记这个方法是重写的。当类继承的类或接口没有这个方法,编译时就会报错,因为你声明了这个注解的意图是希望重写某个方法。另外一个作用是在生成javadoc上这个方法也会显示@Override表示这个方法是重写的。

0.2 注解只是为代码提供描述

显然@Override并没有影响你的代码执行。你删注解,代码还是正常运行。这样我们就得出一个结论:Java注解只是为代码的元素(类,方法,方法参数等)提供描述。

0.3 注解处理

看到这里你可能觉得注解不没什么作用,但是想想看,上面讲到的编译时会报错,这是什么在起作用。虽然将注解本身并没有逻辑,但是你标注了注解这有助于其他程序执行。在这里,你标注了@Override,编译期发现你在这个方法标注了注解就会进行判断。

到这里你已经大概了解注解是用来干嘛的了。下面是正文部分,内容包括:

  • 自定义注解
  • Java自带注解
  • 注解处理器

1.0 自定义注解

1.1元注解

我们打开@Override源码看看:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

注解源码非常简单,和接口的定义类似。它使用@interface修饰声明这是一个注解。另外面多了两个注解@Target和@Retention,我们将这些注解叫做元注解,它只用于自定义注解。

@Target

@Target表示你定义的这个注解能用在哪里。包括:

类型描述
ElementType.ANNOTATION_TYPE可以给一个注解进行注解
ElementType.CONSTRUCTOR构造方法
ElementType.FIELD类属性
ElementType.LOCAL_VARIABLE局部变量
ElementType.METHOD方法
ElementType.PACKAGE
ElementType.PARAMETER方法参数
ElementType.TYPE类、接口、枚举等

可填多个,使用数组格式{}括起,并用,隔开。

@Retention

@Retention表示自定义的注解的作用范围。可选

类型描述
RetentionPolicy.SOURCE只在源码时作用
RetentionPolicy.CLASS源码及编译期作用
RetentionPolicy.RUNTIME源码、编译期和运行时都起作用

另外还有两个可选元注解

@Documented

表示生成javadoc时也包括当前自定义注解

@Inherited

表示当前注解能被继承。但不是指继承自定义注解,是指某个类继承了一个使用了这个注解的类时,这个注解也被继承。

1.2 注解属性

接下来,我们来看看一个复杂一点的注解源码,这是Spring的@Bean源码:

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Bean {
    @AliasFor("name")
    String[] value() default {};

    @AliasFor("value")
    String[] name() default {};

    Autowire autowire() default Autowire.NO;

    String initMethod() default "";

    String destroyMethod() default "(inferred)";
}

这个注解和上面最大的区别是注解体内多了很多方法。其实这些不是方法,而是注解的属性。有了这些属性,我们就可以进一步描述代码。

注解属性定义格式为类型 属性名() default 初始值;

  • 注解属性对类型是有限制的,只能是下面的类型:
    • 8个基础类型
    • Class
    • String
    • enum(上面Autowire 就是enum类型)
    • Annotation
    • 或上面类型的数组形式
  • default是一个修饰符,和=类似;
  • 初始值不为null,所以上面看到String类型用空字符串""
  • 另外如果注解只有一个参数时属性名必须时value。

在使用注解时,可以像上面元注解@Target后面()就是填写的属性。

  • 当属性只有一个时,直接填写属性。即注解的value()属性;
  • 如果多个需要用@Target(属性名=属性值,属性名=属性值)的形式填入;

2.0 Java自带注解

Java自带注解非常简单,只有三个。

@Override

用于标记方法是重写的。建议在重写的方法上标记,这有助与阅读和IDE对你的错误做出提示。

@Deprecated

用于声明被被标注的代码以过期,不建议使用。在编译时会得到错误导致编译失败。可以在除ANNOTATION_TYPE外的代码中标记。

@SupressWarning

虽然被标注了@Deprecated编译就会错误。当时有时久的程序代码,必须要使用这个类。这是可以使用@SupressWarning标注忽略@Deprecated导致编译错误。

注解处理

在开始我们讲到注解只是为代码提供描述,但是其他程序可以利用这些描述数据来判断如果执行操作。注解处理是使用Java反射来完成的。

注:反射简述。

类,方法,实例属性等本身也是对象,比如讲类定义的是什么修饰符,它有哪些方法,哪些实例属性,哪些注解。所以每个类其实都是Java的Class类型的实例,通过Class可以了解类本身的一切或操作某些特定功能。

比如讲,我们有一些动物类,我们创建了一个@Animal自定义注解表示他们的特性,例如脚数,咬不咬人。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface AnimalTrait{
  int footNum() default 0;
  boolean bitePeople default false;
}

动物类

@AnimalTrait(footNum=4,bitePeople=true)
class Dog extends Call{
  public void call(){
    System.out.println("汪汪...");
  }
}
@AnimalTrait(footNum=4,bitePeople=false)
class Cat extends Animal{
  public void call(){
    System.out.println("喵喵...");
  }
}
@AnimalTrait(footNum=2,bitePeople=false)
class Monkey extends Animal{
  public void call(){
    System.out.println("吖吖...");
  }
}
//...其他动物类

假设我们有一个动物园集合,存放了全部动物的实例。我们现在要写一个类将咬人的动物和不咬人的动物分开到连个集合以便管理它们。

import java.util.ArrayList;

public class Main {

    public static void main(String[] args) {
        ArrayList<Animal> zooAnimal = new ArrayList<>();
        ArrayList<Animal> biteList = new ArrayList<>();
        ArrayList<Animal> notbiteList = new ArrayList<>();

        for(int i=0;i<8;i++){
            zooAnimal.add(new Cat());
            zooAnimal.add(new Dog());
        }

        for(Animal animal:zooAnimal){
          //主要部分,获取实例对应类型的@AnimalTrait注解的bitePeople属性
            if(animal.getClass().getAnnotation(AnimalTrait.class).bitePeople()){
                biteList.add(animal);
            } else {
                notbiteList.add(animal);
            }
        }
        System.out.println("咬人的动物叫声:");
        for(Animal animal:biteList){
            animal.call();
        }
        System.out.println("不咬人的动物叫声:");
        for(Animal animal:notbiteList){
            animal.call();
        }
    }
}

其中主要的是的是获取注解信息的部分,条件中使用getClass获取实例类的Class实例,再用getAnnotation()获取类的@AnimalTrait的Annotation实例,最后通过.bitePeople()获取注解的属性值。

AnnotatedElement接口

所有反射类型Class、Constuctor、Method、Field等都继承了AnnotatedElement接口。接口的主要作用是获取注解的反射类型然后得到注解的属性。

AnnotatedElement接口常用方法

返回值方法签名描述
TgetAnnotation(Class annotationClass)返回指定注解类型
Annotation[]getAnnotations()返回全部注解的数组
default booleanisAnnotationPresent(Class annotationClass)判断是否存在指定类型的注解

Annotation类型

我们获得Annotation类型可以通过.注解属性名()的方式获取注解的属性。

总结

  1. Java注解只是对代码元素提供描述,它并不影响代码执行;
  2. 影响代码执行的是注解处理器,利用反射机制获取注解属性;
  3. 通过@interface定义注解,通过@Target、@Retention、@Documented和@Inherited元注解来设定注解的类型、生命周期、是否生成到Javadoc和是否被继承。
  4. 注解可以有属性,但属性只能是8种基础类型、String、Class、enum、Annotation和以上的数组形式。