Java高级编程—注解

文章目录

  • 1.注解的概述
  • 2.常见的Annotation示例
    • [2.1 生成文档相关的注解](#2.1 生成文档相关的注解)
    • [2.2 在编译时进行格式检查的注解](#2.2 在编译时进行格式检查的注解)
    • [2.3 跟踪代码依赖性,实现替代配置文件功能的注解](#2.3 跟踪代码依赖性,实现替代配置文件功能的注解)
  • 3.自定义Annotation
  • 4.JDK中的元注解
    • [4.1 Retention](#4.1 Retention)
    • [4.2 Target](#4.2 Target)
    • [4.3 Documented & Inherited](#4.3 Documented & Inherited)
  • [5. JDK8中注解的新特性](#5. JDK8中注解的新特性)
    • [5.1 可重复注解](#5.1 可重复注解)
    • [5.2 类型注解](#5.2 类型注解)

1.注解的概述

Annotation(注解)是 JDK 5.0时加的新特性。 从 JDK 5.0 开始, Java 增加了对元数据(MetaData) 的支持, 也就是 Annotation(注解) 。

Annotation 其实就是代码里的特殊标记 , 这些标记可以在编译, 类加载, 运行时被读取, 并执行相应的处理。通过使用Annotation, 程序员可以在不改变原有逻辑的情况下, 在源文件中嵌入一些补充信息。代码分析工具、开发工具和部署工具可以通过这些补充信息进行验证或者进行部署。

Annotation 可以像修饰符一样被使用, 可用于修饰包,类, 构造器, 方法, 成员变量, 参数, 局部变量的声明, 这些信息被保存在 Annotation 的 "name=value" 对中。

在JavaSE中,注解的使用目的比较简单,例如标记过时的功能, 忽略警告等。在JavaEE/Android中注解占据了更重要的角色,例如 用来配置应用程序的任何切面,代替JavaEE旧版中所遗留的繁冗代码和XML配置等。

未来的开发模式都是基于注解的,JPA是基于注解的,Spring2.5以 上都是基于注解的,Hibernate3.x以后也是基于注解的,现在的 Struts2有一部分也是基于注解的了,注解是一种趋势,一定程度上 可以说:框架 = 注解 + 反射 + 设计模式。

2.常见的Annotation示例

使用 Annotation 时要在其前面增加 @ 符号, 并把该 Annotation 当成 一个修饰符使用。用于修饰它支持的程序元素。

2.1 生成文档相关的注解

注解名 作用 格式要求
@author 标明开发该类模块的作者,多个作者之间使用,分割 ---------
@version 标明该类模块的版本 ---------
@see 参考转向,也就是相关主题 ---------
@since 从哪个版本开始增加的 ---------
@param 对方法中某参数的说明,如果没有参数就不能写 @param 形参名 形参类型 形参说明
@return 对方法返回值的说明,如果方法的返回值类型是void就不能写 @return 返回值类型 返回值说明
@exception 对方法可能抛出的异常进行说明 ,如果方法没有用throws显式抛出的异常就不能写 @exception 异常类型 异常说明

其中: 1. @param @return 和 @exception 这三个标记都是只用于方法的。

  1. @param和@exception可以并列多个。

下面给出一个实例体会一下:

java 复制代码
package com.annotation.javadoc;
    /**
    * @author shkstart
    * @version 1.0
    * @see Math.java
    */
    public class JavadocTest {
    /**
    * 程序的主方法,程序的入口
    * @param args String[] 命令行参数
    */
    public static void main(String[] args) {
    }
    /**
    * 求圆面积的方法
    * @param radius double 半径值
    * @return double 圆的面积
    */
    public static double getArea(double radius){
    return Math.PI * radius * radius;
    }
} 

在上述的例子当中,除了表格中的 @since 和 @exception 两个没有用到外,其余的生成文档相关的注解都已经使用过了。

2.2 在编译时进行格式检查的注解

在这一部分中,JDK内置了三个基本的注解,分别为: @Override 、@Deprecated 和 @SuppressWarnings 。

@Override 限定重写父类方法, 该注解只能用于方法
@Deprecated 用于表示所修饰的元素(类, 方法等)已过时。通常是因为 所修饰的结构危险或存在更好的选择
@SuppressWarnings 抑制编译器警告

下面分别给出它们的使用:

  1. @Override的使用
java 复制代码
class Person{
    private String name;
    private int id;

    public Person() {
    }

    public Person(String name, int id) {
        this.name = name;
        this.id = id;
    }

    public void walk(){
        System.out.println("人走路");
    }

    public void eat(){
        System.out.println("人吃饭");
    }

}

class Student extends Person{

    @Override        //在编译时检查一下这个walk方法是不是重写父类的(如果不是,就会给出报错的提示)
    public void walk() {
        System.out.println("学生走路");
    }
    
}

在上述代码块中,我们先是定义了一个Person类,在声明了两个属性,提供了构造器后提供了两个方法walk()和eat(); 接着定义了一个Student类去继承Person类,并重写了walk方法。

下面我们给出测试:

java 复制代码
public class AnnotationTest {
    public static void main(String[] args) {
        Person p = new Student();
        p.walk();
    }
    
}

测试结果如下:

使用 @Override 的好处是它会在编译时检查一下这个walk方法是不是重写父类的,如果重写walk方法写的是对的,那么不使用 @Override 也没有什么影响,编译器只是不会帮你检查了,但效果是一样的。如果写错了,它就会给出报错的提示。比如walk拼写错误,把字母l写成了数字1,那么在编译时就过不去。如下图所示:

2.@Deprecated的使用

加上了@Deprecated 修饰的结构表示时已经过时了,但是过时了的东西还是可以用的,只是不建议使用。

java 复制代码
public class AnnotationTest {
    public static void main(String[] args) {
        
//      这个构造器就是已经过时了,但是过时了的东西还是可以用的
        Date date = new Date(2020, 10, 11);
        System.out.println(date);
    }
}

上述代码块中的Date类的构造器就已经过时了,API文档的解释如下:

3.@SuppressWarnings的使用

@SuppressWarnings的作用是抑制编译器的警告,比如我们在程序中定义了一个变量,但是目前还没有使用过,此时编译器就会报一个 'unused' 的警告。这个在eclipse中表现的更加明显。如下所示:

在main方法中我们定义了一个整型变量num,但未使用,此时就会在函数的左边出现黄色的警告。

在这个变量上添加 @SuppressWarnings("unused") 的注解后,左边的警告就消失了。

需要注意的是:@SuppressWarnings() 的参数部分可以写多个值,也就是可以抑制多个警告。这是因为它的成员变量是一个字符串数组。API 如下图所示:

2.3 跟踪代码依赖性,实现替代配置文件功能的注解

举出如下的两个例子来说明一下:
Servlet3.0提供了注解(annotation),使得不再需要在web.xml文件中进行Servlet的部署。

java 复制代码
public class LoginServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws
    ServletException, IOException { }
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws
    ServletException, IOException {
    doGet(request, response);
    }
}

要想在客户端输入/login时执行这个Servlet 内的业务逻辑,需要写配置文件作映射,如下所示:

xml 复制代码
<servlet>
<servlet-name>LoginServlet</servlet-name>
<servlet-class>com.servlet.LoginServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>LoginServlet</servlet-name>
<url-pattern>/login</url-pattern>
</servlet-mapping>

而有了注解后只需为该Servlet添加一个@WebServlet即可:

java 复制代码
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws
    ServletException, IOException { }
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws
    ServletException, IOException {
    doGet(request, response);
    }
}

Spring框架关于事务的管理:

在原来需要使用配置文件指明事务的管理情况,现在可以把这些信息体现为注解,不需要再去写xml文件了。

java 复制代码
@Transactional(propagation=Propagation.REQUIRES_NEW,
    isolation=Isolation.READ_COMMITTED,readOnly=false,timeout=3)
    public void buyBook(String username, String isbn) {
    //1.查询书的单价
    int price = bookShopDao.findBookPriceByIsbn(isbn);
    //2. 更新库存
    bookShopDao.updateBookStock(isbn);
    //3. 更新用户的余额
    bookShopDao.updateUserAccount(username, price);
}
xml 复制代码
<!-- 配置事务属性 -->
<tx:advice transaction-manager="dataSourceTransactionManager" id="txAdvice">
<tx:attributes>
<!-- 配置每个方法使用的事务属性 -->
<tx:method name="buyBook" propagation="REQUIRES_NEW"
isolation="READ_COMMITTED" read-only="false" timeout="3" />
</tx:attributes>
</tx:advice>

3.自定义Annotation

注解也可以自定义,就像自定义类、接口、异常一样。如果现有的注解不能够满足我们的需求,那么我们也可以自定义注解。
如何自定义注解?

我们仿照 SuppressWarnings 自定义一个注解,先来看一下 SuppressWarnings 的结构:

java 复制代码
package java.lang;
import java.lang.annotation.*;
import static java.lang.annotation.ElementType.*;

/**
 * Indicates that the named compiler warnings should be suppressed in the
 * annotated element (and in all program elements contained in the annotated
 * element).  Note that the set of warnings suppressed in a given element is
 * a superset of the warnings suppressed in all containing elements.  For
 * example, if you annotate a class to suppress one warning and annotate a
 * method to suppress another, both warnings will be suppressed in the method.
 *
 * <p>As a matter of style, programmers should always use this annotation
 * on the most deeply nested element where it is effective.  If you want to
 * suppress a warning in a particular method, you should annotate that
 * method rather than its class.
 *
 * @author Josh Bloch
 * @since 1.5
 * @jls 4.8 Raw Types
 * @jls 4.12.2 Variables of Reference Type
 * @jls 5.1.9 Unchecked Conversion
 * @jls 5.5.2 Checked Casts and Unchecked Casts
 * @jls 9.6.3.5 @SuppressWarnings
 */
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
    /**
     * The set of warnings that are to be suppressed by the compiler in the
     * annotated element.  Duplicate names are permitted.  The second and
     * successive occurrences of a name are ignored.  The presence of
     * unrecognized warning names is <i>not</i> an error: Compilers must
     * ignore any warning names they do not recognize.  They are, however,
     * free to emit a warning if an annotation contains an unrecognized
     * warning name.
     *
     * <p> The string {@code "unchecked"} is used to suppress
     * unchecked warnings. Compiler vendors should document the
     * additional warning names they support in conjunction with this
     * annotation type. They are encouraged to cooperate to ensure
     * that the same names work across multiple compilers.
     * @return the set of warnings to be suppressed
     */
    String[] value();
}

从 Annotation 的定义中可以看出:

1.注解声明为 @interface ,即自定义注解需要使用这个关键字。

2.自定义注解自动继承了 java.lang.annotation.Annotation 接口。

  1. Annotation 的成员变量在 Annotation 定义中以无参数方法的形式来声明。其方法名和返回值定义了该成员的名字和类型。我们称为配置参数。类型只能是八种基本数据类型、String类型、Class类型、enum类型、Annotation类型、 以上所有类型的数组。

(在 SuppressWarnings 中声明的是一个String类型的数组,因为该注解有多个成员变量)

  1. 可以在定义 Annotation 的成员变量时为其指定初始值, 指定成员变量的初始值使用 default 关键字。

  2. 如果只有一个参数成员,建议使用参数名为 value。

  3. 如果定义的注解含有配置参数,那么使用时必须指定参数值,除非它有默认 值。格式是"参数名 = 参数值" ,如果只有一个参数成员,且名称为value, 可以省略"value="。

7.如果自定义的注解没有成员,表明是一个标识作用。可以类比一下 io 流中的Serializable 接口(一个没有抽象方法的接口,只是起到一个标志的作用)。比如:@override这个注解里就是没有成员的。

下面我们自定义一个名为 MyAnnotation 的注解:

java 复制代码
/*
* 注:@interface 和 interface 没有任何的关系
* */
public @interface MyAnnotation {

    String value();    //若有多个成员变量时可以指定成数组

}

然后我们就可以使用这个注解了,这里我们把它添加到Person类上。如下所示:

复制代码
@MyAnnotation(value = "kaixuan")   
class Person{
    private String name;
    private int id;

    public Person() {
    }
    
    public Person(String name, int id) {
        this.name = name;
        this.id = id;
    }

    public void walk(){
        System.out.println("人走路");
    }

    public void eat(){
        System.out.println("人吃饭");
    }

}

有几点需要注意:

1.value 是MyAnnotation 的成员变量的值,必须要给它指定了,不然会value丢失的错。

2.如果不想在使用的地方指定,也可以在声明 MyAnnotation 时为 value 指定一个默认的值。

java 复制代码
public @interface MyAnnotation {
    
    String value() default "dabing";    //若有多个成员变量时可以指定成数组
}

当然默认指定的优先级一定是低于添加注解时指定的value值的。

3.自定义注解必须配上注解的信息处理流程(使用反射)才有意义。

4.JDK中的元注解

JDK 的元注解用于修饰其他 Annotation 定义。也就是说:元注解是对现有的注解进行解释说明的注解。

以"元数据"的解读来类比一下,比如现在有一个字符串类型的数据如下:

java 复制代码
String name = "kaixuan";

显然,这里的数据是"kaixuan",是程序中要使用到的数据。而其余两部分"String"和"name"是用来修饰这个数据的,"String"指明了这个数据的类型是字符串;"name"是给这个数据起了个名字。所以这两个修饰"kaixuan"的数据String和name 就被称为"元数据"。理解了元数据,那么元注解也就不难理解了吧。

JDK5.0提供了4个标准的meta-annotation类型,分别是:Retention、Target、Documented 和 Inherited。各自的作用如下表所示:

| Retention | 指定所修饰的Annotation的生命周期:SOURCE / CLASS(默认行为) / RUNTIME

(只有声明为RUNTIME生命周期的注解,才能通过反射获取。) |

| --- | --- |

| Target | 用于指定被修饰的Annotation能用于修饰哪些程序元素 |

| Documented | 用于指定被该元 Annotation 修饰的注解在被javadoc解析时,保留下来。 |

| Inherited | 被它修饰的Annotation将具有继承性。如果某个类使用了@Inherited修饰的注解,则其子类将自动具有该注解。 |

4.1 Retention

该注解只能用于修饰一个 Annotation 定义, 用于指定该 Annotation 的生命周期。
如何实现指定Annotation 生命周期?

@Rentention 包含一个 RetentionPolicy 类型的成员变量, 而这个RetentionPolicy本身又是枚举类型的,其中包含三个值:SOURCE,CLASS, RUNTIME。

Retention注解源码如下:

RetentionPolicy 枚举类如下:
下面说一下这三个值的作用:

RetentionPolicy.SOURCE 在源文件中有效(即源文件保留),编译器直接丢弃这种策略的注释。也就是说在执行javac命令后,生成的.class文件中不会保留注解的信息
RetentionPolicy.CLASS 在class文件中有效(即class保留) , 当运行 Java 程序时, JVM 不会保留注解。 这是默认值
RetentionPolicy.RUNTIME 在运行时有效(即运行时保留),当运行 Java 程序时, JVM 会 保留注释。程序可以通过反射获取该注释。
(会被保存在.class文件里面,且解释运行的时候还会加载进内存里,所以是可以通过反射去读取的。)

所以只有声明为 RUNTIME 生命周期的注解,才能通过反射获取。

下面我们给刚才自定义的注解MyAnnotation 加上Retention,并把内部的RetentionPolicy 成员的值指定为 RUNTIME 来做一下测试:

java 复制代码
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {

    String value() default "dabing";    //若有多个成员变量时可以指定成数组

}

这样设计后就可以在运行时获取到这个注解信息了。把MyAnnotation添加到Person类上,由于MyAnnotation里已经给出了默认的value值就不再指定。

java 复制代码
@MyAnnotation()                                 
class Person{
    private String name;
    private int id;

    public Person() {
    }

    public Person(String name, int id) {
        this.name = name;
        this.id = id;
    }

    public void walk(){
        System.out.println("人走路");
    }

    public void eat(){
        System.out.println("人吃饭");
    }

}

然后我们通过反射来获取一下Person类上的注解信息,给出如下的测试:

java 复制代码
@Test
public void test1() {
    Class clazz = Person.class;
    Annotation[] annotations = clazz.getAnnotations();
    for (Annotation a : annotations) {
        System.out.println(a);
    }
}

测试结果如下:

4.2 Target

用于修饰 Annotation 定义, 用于指定被修饰的 Annotation 能用于修饰哪些程序元素。 @Target 也包含一个名为 value 的成员变量。

查看一下Target的源码:

java 复制代码
package java.lang.annotation;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    /**
     * Returns an array of the kinds of elements an annotation type
     * can be applied to.
     * @return an array of the kinds of elements an annotation type
     * can be applied to
     */
    ElementType[] value();
}

可以看到,它里面的value是一个ElementType类型的数组,深入查看 value 数组如下:

java 复制代码
package java.lang.annotation;

public enum ElementType {
    /** Class, interface (including annotation type), or enum declaration */
    TYPE,

    /** Field declaration (includes enum constants) */
    FIELD,

    /** Method declaration */
    METHOD,

    /** Formal parameter declaration */
    PARAMETER,

    /** Constructor declaration */
    CONSTRUCTOR,

    /** Local variable declaration */
    LOCAL_VARIABLE,

    /** Annotation type declaration */
    ANNOTATION_TYPE,

    /** Package declaration */
    PACKAGE,

    /**
     * Type parameter declaration
     *
     * @since 1.8
     */
    TYPE_PARAMETER,

    /**
     * Use of a type
     *
     * @since 1.8
     */
    TYPE_USE
}

可以看到,这也是一个枚举类,内部定义了十个常量,分别代表什么注释中已经写的很清楚了。

下面再看一下 Target 对 SuppressWarnings 的修饰:

很显然,Target 指定了SuppressWarnings 可以作用的六种类型,也就是说只有在这几类结构上面是可以添加 SuppressWarnings 注解的,而其他的结构不行。我们也可以为刚才自定义的MyAnnotation添加元注解Target,指明MyAnnotation 可以修饰哪些结构。

java 复制代码
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.LOCAL_VARIABLE;

@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {

    String value() default "dabing";    //若有多个成员变量时可以指定成数组

}

自定义注解通常都会指明两个元注解:Retentino、Target。

4.3 Documented & Inherited

Documented : 用于指定被该元 Annotation 修饰的注解在被javadoc解析时,保留下来。默认情况下,javadoc是不包括注解的。( 定义为Documented的注解必须设置Retention值为RUNTIME。 )

Inherited :它修饰的Annotation将具有继承性。如果某个类使用了@Inherited 修饰的注解,则其子类将自动具有该注解。

下面给出Inherited注解的测试:

首先为MyAnnotation注解添加Inherited 元注解:

java 复制代码
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.LOCAL_VARIABLE;

@Inherited
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {

    String value() default "dabing";    //若有多个成员变量时可以指定成数组

}

在Person类上使用MyAnnotation注解,并指定其中的value值为"kaixuan",Student为Person的子类,不添加注解:

java 复制代码
import org.junit.Test;
import java.lang.annotation.Annotation;

@MyAnnotation("kaixuan")                                    //value是MyAnnotation的成员变量的值,必须要给它指定了,不然会报错。也可以在声明注解时给出value的默认值
class Person{
    private String name;
    private int id;

    public Person() {
    }

    public Person(String name, int id) {
        this.name = name;
        this.id = id;
    }

    public void walk(){
        System.out.println("人走路");
    }

    public void eat(){
        System.out.println("人吃饭");
    }

}

interface Info{
    void show();
}

class Student extends Person implements Info{

    @Override        //在编译时检查一下这个walk方法是不是重写父类的(如果不是,就会给出报错的提示)
    public void walk() {
        System.out.println("学生走路");
    }

    @Override      //实现接口中的抽象方法
    public void show() {

    }
}

给出测试程序,验证 Student 类上是否有MyAnnotation注解:

java 复制代码
@Test
    public void testGetAnnotation(){
        Class clazz = Student.class;
        Annotation[] annotations = clazz.getAnnotations();
        for(Annotation a : annotations){
            System.out.println(a);
        }
    }

测试结果如下:

可以看到,Student类上的MyAnnotation和其父类中的是一样的。

5. JDK8中注解的新特性

Java 8 对注解处理提供了两点改进:可重复的注解及可用于类型的注解。

5.1 可重复注解

如果希望一个注解可以在一个结构(类、接口、属性、方法...)上同时使用多次,那么这个注解就被称为"重复注解"。

比如说现在想要把我们自定义的 MyAnnotation 注解在Person类上使用两次,如下所示:

java 复制代码
@MyAnnotation("kaixuan")
@MyAnnotation("runze")
class Person{
    private String name;
    private int id;

    public Person() {
    }

    public Person(String name, int id) {
        this.name = name;
        this.id = id;
    }

    public void walk(){
        System.out.println("人走路");
    }

    public void eat(){
        System.out.println("人吃饭");
    }
}

但是这个时候肯定是会报错的,如下图所示:

这是因为MyAnnotation 只能赋一次值,使用之后就没了不能再用了。要想实现这个功能,只能再声明一个value值为 MyAnnotation类型的数组 的新的注解,使用这个注解完成这个功能。比如声明为MyAnnotations,设计如下:

java 复制代码
public @interface MyAnnotations {
    MyAnnotation[] value();
}

接着在Person类上添加MyAnnotations 注解,形式如下:

上述的做法是jdk 8之前的写法,那么在jdk 8 引入了可重复注解之后,可以怎么实现呢?

首先我们需要为 MyAnnotation 注解添加一个元注解 Repeatable,并传入一个Class实例,也就是一个运行时类,这里我们传入MyAnnotations.class。Repeatable注解的源码如下:

可见,Repeatable注解的value值类型为Class的实例。

接着我们把MyAnnotations 和 MyAnnotation 的生命周期Retention、所作用的结构Target 以及Inherited都保持一致。

最终的MyAnnotation设计如下:

java 复制代码
import java.lang.annotation.*;

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.LOCAL_VARIABLE;

//在MyAnnotation中使用MyAnnotations,所以要求MyAnnotations的生命周期不能小于MyAnnotation,避免出现当要使用;
//的时候,MyAnnotations不可用;而作用的对象也是一样的道理,将它们的作用对象保持一致即可。
@Inherited
@Repeatable(MyAnnotations.class)
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {

    String value() default "dabing";    //若有多个成员变量时可以指定成数组
}

MyAnnotations设计如下:

java 复制代码
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.LOCAL_VARIABLE;

@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
public @interface MyAnnotations {
    MyAnnotation[] value();
}

这个时候就可以实现在Person类上多次使用MyAnnotation了。

可以看到,写成最开始的需求也不会报错了。

5.2 类型注解

JDK 8之后,关于元注解@Target的参数类型ElementType枚举值多了两个: TYPE_PARAMETER,TYPE_USE。

在Java 8之前,注解只能是在声明的地方所使用,Java8开始,注解可以应用 在任何地方。

ElementType.TYPE_PARAMETER 表示该注解能写在类型变量的声明语 句中(如:泛型声明)。
ElementType.TYPE_USE 表示该注解能写在使用类型的任何语句中。

下面给出测试:

java 复制代码
//使用注解修饰泛型的类型
class Generic<@MyAnnotation T>{
    public void show()  throws @MyAnnotation RuntimeException {
        //使用注解修饰别的任何语句
        ArrayList<@MyAnnotation String> list = new ArrayList<>();

        int num = (@MyAnnotation int) 10L;
    }
}

同时需要为MyAnnotation注解的Target元注解添加TYPE_PARAMETER和TYPE_USE。

java 复制代码
@Inherited
@Repeatable(MyAnnotations.class)
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE, TYPE_PARAMETER,TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
    String value() default "dabing";    //若有多个成员变量时可以指定成数组
}
相关推荐
Scott9999HH8 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
腻害兔8 小时前
【若依项目-产品经理视角】深度拆解 RuoYi-Vue-Pro 商城模块:从商品管理到交易引擎,50 张表撑起一整套电商系统
java·大数据·vue.js·产品经理·ai编程
码智社9 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海9 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖10 小时前
JDK 26 新特性详解
java·开发语言
马优晨11 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
人邮异步社区12 小时前
怎么把C语言学到精通?
c语言·开发语言
心平气和量大福大13 小时前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf
ttwuai13 小时前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang
维天说13 小时前
CLI-Switch 2026年3月版历史设计:Hook、TTY 隔离与 JSON 状态
java·服务器·json