阅读 61

《重学Java系列》之 注解基础

不诗意的女程序媛不是好厨师~ 转载请注明出处,From李诗雨---[blog.csdn.net/cjm24848365…]

按照惯例我们先来看下本篇的思维导图:

在这里插入图片描述

本篇来和大家先学习一下注解基础,后期再一步一步深入学习。

本篇的主要目的就是先对注解有些基本的了解,后期会进行大量的代码实战,这样循序渐进,相信我们就可以很好的掌握注解了。

1.什么是注解?

【注解的定义】:

Java 注解用于为 Java 代码提供元数据。作为元数据,注解不直接影响你的代码执行,但也有一些类型的注解实际上可以用于这一目的。Java 注解是从 Java5 开始添加到 Java 的。

【通俗点理解】:

注解即标签。

如果把代码想象成一个具有生命的个体,注解就是给这些代码的某些个体打标签。

2.自定义注解及使用

  • 注解通过 @interface关键字进行定义。
public @interface Test {
}
复制代码

它的形式跟接口很类似,不过前面多了一个 @ 符号。

上面的代码就创建了一个名字为 Test 的注解。你可以简单理解为创建了一张名字为 Test的标签。

  • 使用注解
@Test
public class TestAnnotation {
}
复制代码

创建一个类 TestAnnotation,然后在类定义的地方加上 @Test就可以用 Test注解这个类了。

你可以简单理解为将 Test 这张标签贴到 TestAnnotation这个类上面。

3.元注解

3.1元注解的理解

元注解:即注解的注解。

元注解是可以注解到注解上的注解,或者说元注解是一种基本注解,但是它能够应用到其它的注解上面。

如果难于理解的话,你可以这样理解。元注解也是一张标签,但是它是一张特殊的标签,它的作用和目的就是给其他普通的标签进行解释说明的。

3.2 常见的5种元注解。

元注解有 @Retention、@Documented、@Target、@Inherited、@Repeatable 5 种。

下面分别一一来说明:

@Retention --- 指定了注解的存活时间

Retention 的英文意为保留期的意思。当 @Retention 应用到一个注解上的时候,它解释说明了这个注解的的存活时间。

它的取值如下:

  1. RetentionPolicy.SOURCE 注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视。 ----生命周期最短的注解
  2. RetentionPolicy.CLASS 注解只被保留到编译进行的时候,它并不会被加载到 JVM 中。
  3. RetentionPolicy.RUNTIME 注解可以保留到程序运行的时候,它会被加载进入到 JVM 中,所以在程序运行时可以获取到它们

我画一张图来帮助大家理解 这三个取值下 注解 所能保留到的时期:

在这里插入图片描述

@Target ---- 指定注解用在什么地方

Target 是目标的意思,@Target 指定了注解运用的地方 你可以这样理解,当一个注解被 @Target 注解时,这个注解就被限定了运用的场景。 类比到标签,原本标签是你想张贴到哪个地方就到哪个地方,但是因为 @Target 的存在,它张贴的地方就非常具体了,比如只能张贴到方法上、类上、方法参数上等等。@Target 有下面的取值

  1. ElementType.ANNOTATION_TYPE 可以给一个注解进行注解
  2. ElementType.CONSTRUCTOR 可以给构造方法进行注解
  3. ElementType.FIELD 可以给属性进行注解
  4. ElementType.LOCAL_VARIABLE 可以给局部变量进行注解
  5. ElementType.METHOD 可以给方法进行注解
  6. ElementType.PACKAGE 可以给一个包进行注解
  7. ElementType.PARAMETER 可以给一个方法内的参数进行注解

@Inherited --- 允许子类继承父类的注解。

Inherited 是继承的意思,但是它并不是说注解本身可以继承,而是说如果一个超类被 @Inherited 注解过的注解进行注解的话,那么如果它的子类没有被任何注解应用的话,那么这个子类就继承了超类的注解。

这么说比较抽象,举个栗子给大家看看:

现在有一个 美女类Beauty,还有一个它的子类 BeautyChild。

那么当Beauty类被打上了“美女”的标签后,她的孩子BeautyChild默认也会拥有“美女标签”。

(PS:也就是美女的孩子还是美女。)

//1.自定义一个使用了@Inherited的标签
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface InheritedTest {
    String value();
}
复制代码
//2.美女类Beauty,并且拥有一个“美女”标签
@InheritedTest("美女")
public class Beauty {
}
复制代码
//3.美女类的孩子类BeautyChild,并没有特地指定任何标签
class BeautyChild extends Beauty {
}
复制代码
//4.测试:看从子类中能否取到标签
public class DemoTest {
    public static void main(String[] args) {
        Class<BeautyChild> beautyChildClass = BeautyChild.class;
        if (beautyChildClass.isAnnotationPresent(InheritedTest.class)) {
         System.out.println(beautyChildClass.getAnnotation(InheritedTest.class).value());
        }
    }
}
复制代码

结果是:从子类中确实取到了父类所拥有的标签。

在这里插入图片描述

@Repeatable --- 注解的值可以同时取多个

Repeatable 是可重复的意思。@Repeatable 是 Java 1.8 才加进来的,所以算是一个新的特性。

什么样的注解会多次应用呢?通常是注解的值可以同时取多个。

还是来举个栗子来方便大家理解吧:

比如说我吧,在工作上,我拥有“程序媛”的标签;在朋友眼中,我拥有“疯一样的女子”的标签;在陌生人眼中,我又拥有“淑女”的标签。根据不同的场合,我可以拥有很多不同的标签,这完全没有问题。

//1.先声明一个Persons类用来包含所有的身份
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Persons {
    Person[] value();//由于会有多个身份,所以此处是数组
}
复制代码
//2.定义一个Person注解,它使用到了@Repeatable。
//@Repeatable括号内的就相当于用来保存该注解内容的容器。
@Repeatable(Persons.class)
public @interface Person {
    String role() default "";
}
复制代码
//3.声明一个Shiyu类,给该类加上一些身份。
@Person(role = "程序媛")
@Person(role = "疯一样的女子")
@Person(role = "淑女")
public class Shiyu {
}
复制代码
//4.测试看看能否获取这多个标签
public class DemoTest2 {
    public static void main(String[] args) {
        if (Shiyu.class.isAnnotationPresent(Persons.class)) {
            Persons p2 = Shiyu.class.getAnnotation(Persons.class);
            for (Person t : p2.value()) {
                System.out.println(t.role());
            }
        }
    }
}
复制代码

结果标签全部可以打印出来,则正确:

在这里插入图片描述

【注意事项】:

大家在测试@repeatable的代码时,别忘了配置gradle哈。要加上下边这个配置才可以:

在这里插入图片描述

即要加上:

compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
}
复制代码

如果不加的话,编译时会报错:

在这里插入图片描述

所以,切记这些小细节哈~

@Documented---用到的很少

顾名思义,这个元注解肯定是和文档有关。它的作用是能够将注解中的元素包含到 Javadoc 中去。ElementType.TYPE 可以给一个类型进行注解,比如类、接口、枚举

4.注解的属性

注解的属性也叫做成员变量。

注解只有成员变量,没有方法。

【需要注意】的是:

  • 在注解中定义属性时它的类型是有要求的。

    只能用8种基本数据类型( byte,short,char,int,long,float,double,boolean),外加 类(Class),接口(interface),注解(annotation), String,以及这一些类的数组。

  • 注解中属性可以有默认值,默认值需要用 default 关键值指定

    如:

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Test{
        int id() default -1;
        String msg() default "Hello";
    }
    复制代码

    上面代码定义了 TestAnnotation 这个注解中拥有 id 和 msg 两个属性。

    在使用的时候,我们应该给它们进行赋值。

    赋值的方式是在注解的括号内以 value="" 形式,多个属性之前用 ,隔开:

    @Test(id=1,msg="hello annotation")
    public class TestAnnotation {
    }
    复制代码

5.注解的提取

1.运行期注解(RunTime)---利用反射去获取信息。 2.编译期(Compile time)注解,以及处理编译期注解的手段是APT和Javapoet,@Retention(RetentionPolicy.CLASS)。

今天我们主要讲一下运行期注解的提取:

首先可以通过 Class 对象的 isAnnotationPresent() 方法判断它是否应用了某个注解

public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {}
复制代码

然后通过 getAnnotation() 方法来获取 Annotation 对象。

 public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {}
复制代码

或者是 getAnnotations() 方法。

public Annotation[] getAnnotations() {}
复制代码

前一种方法返回指定类型的注解,后一种方法返回注解到这个元素上的所有注解。

如果获取到的 Annotation 如果不为 null,则就可以调用它们的属性方法了。比如

public class TestDemo {
    public static void main(String[] args) {
        boolean hasAnnotation = TestDemo.class.isAnnotationPresent(Test.class);
        if (hasAnnotation) {
            TestAnnotation testAnnotation = TestDemo.class.getAnnotation(Test.class);
            System.out.println("id:" + testAnnotation.id());
            System.out.println("msg:" + testAnnotation.msg());
        }
    }
}
复制代码

6.注解的使用场景

  • 提供信息给编译器: 编译器可以利用注解来探测错误和警告信息
  • 编译阶段时的处理: 软件工具可以用来利用注解信息来生成代码、Html文档或者做其它相应处理。
  • 运行时的处理: 某些注解可以在程序运行的时候接受代码的提取 值得注意的是,注解不是代码本身的一部分。

积累点滴,做好自己~