【Java基础系列教程】第十七章 Java注解

一、注解 (Annotation) 概述

        从 JDK 5.0 开始, Java 增加了对元数据(MetaData)的支持, 也就是Annotation(注解)。
    
        Annotation 其实就是代码里的特殊标记, 这些标记可以在编译, 类加载, 运行时被读取, 并执行相应的处理。通过使用 Annotation, 程序员可以在不改变原有逻辑的情况下, 在源文件中嵌入一些补充信息。代码分析工具、开发工具和部署工具可以通过这些补充信息进行验证或者进行部署。
    
        Annotation 可以像修饰符一样被使用, 可用于修饰包, 类, 构造器, 方法, 成员变量, 参数, 局部变量的声明, 这些信息被保存在Annotation 的 “name=value” 对中。

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

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

二、常见的Annotation示例

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

示例一:生成文档相关的注解

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

/**
 * @author jimbo
 * @version 1.0
 */
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;
    }
}

示例二:在编译时进行格式检查(JDK内置的三个基本注解)

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

public class AnnotationTest {
    public static void main(String[] args) {
        @SuppressWarnings("unused")
        int a = 10;
        
        @SuppressWarnings({ "unused", "rawtypes" })
        ArrayList list = new ArrayList();
    }

    @Deprecated
    public void print() {
        System.out.println("过时的方法");
    }

    @Override
    public String toString() {
        return "重写的toString方法()";
    }
}

示例三:跟踪代码依赖性,实现替代配置文件功能

        Servlet3.0提供了注解(annotation),使得不再需要在web.xml文件中进行Servlet的部署。

@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);
    } 
}
<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>

        spring框架中关于“事务”的管理。

@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);
}
<!-- 配置事务属性 -->
<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>

三、自定义 Annotation

        定义新的 Annotation 类型使用 @interface 关键字;

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

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

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

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

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

        没有成员定义的 Annotation 称为标记; 包含成员变量的 Annotation 称为元数据 Annotation;

        注意:
                自定义注解必须配上注解的信息处理流程(使用反射)才有意义。
                自定义注解通常都会指明两个元注解:Retention、Target。

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

@Retention(RetentionPolicy.RUNTIME)
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
public @interface MyAnnotation {
    String value() default "hello";
}

// 如果定义的注解含有配置参数,那么使用时必须指定参数值,除非它有默认值。
// @MyAnnotation(value = "hi")
@MyAnnotation
class Person {
    private String name;
    private int age;

    public Person() {
    }

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

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

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

四、JDK 中的元注解

        JDK 的元 Annotation 用于修饰其他 Annotation 定义;

        JDK5.0提供了4个标准的meta-annotation类型,分别是:
                Retention
                Target
                Documented
                Inherited

4.1 @Retention元注解

        @Retention: 只能用于修饰一个 Annotation 定义, 用于指定该 Annotation 的生命周期, @Rentention 包含一个RetentionPolicy 类型的成员变量, 使用@Rentention 时必须为该 value 成员变量指定值:
                RetentionPolicy.SOURCE:在源文件中有效(即源文件保留),编译器直接丢弃这种策略的注释;
                RetentionPolicy.CLASS:在class文件中有效(即class保留),当运行 Java 程序时, JVM 不会保留注解。 这是默认值;
                RetentionPolicy.RUNTIME:在运行时有效(即运行时保留),当运行 Java 程序时, JVM 会保留注释。程序可以通过反射获取该注释。

public enum RetentionPolicy {
    /**
     * Annotations are to be discarded by the compiler.
     * 编译器将丢弃注解。
     */
    SOURCE,

    /**
     * Annotations are to be recorded in the class file by the compiler
     * but need not be retained by the VM at run time.  This is the default
     * behavior.
     * 注解将由编译器记录在类文件中但不需要在运行时由VM保留。这是默认设置行为
     */
    CLASS,

    /**
     * Annotations are to be recorded in the class file by the compiler and
     * retained by the VM at run time, so they may be read reflectively.
     * 
     * @see java.lang.reflect.AnnotatedElement
     * 注解将由编译器记录在类文件中,并且由VM在运行时保留,因此可以反射地读取它们。
     */
    RUNTIME
}

@Retention(RetentionPolicy.SOURCE)
@interface MyAnnotation1{ }

@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation2{ }

4.2 @Target元注解

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

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
}

// 我们移除MyAnnotation @Target中的TYPE,如果再在类上定义该注解,则报错
@Target({FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE, TYPE_PARAMETER, TYPE_USE})
public @interface MyAnnotation {
    String value() default "hello";
}


// 报错信息:'@MyAnnotation' not applicable to type

4.3 @Documented元注解

        @Documented: 用于指定被该元 Annotation 修饰的 Annotation 类将被javadoc工具提取成文档。默认情况下,javadoc是不包括注解的。
        定义为Documented的注解必须设置Retention值为RUNTIME。

        注:详情见Date API;

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
    
}

4.4 @Inherited元注解

        @Inherited: 被它修饰的 Annotation 将具有继承性。如果某个类使用了被@Inherited 修饰的 Annotation, 则其子类将自动具有该注解。
                比如:如果把标有@Inherited注解的自定义的注解标注在类级别上,子类则可以继承父类类级别的注解。
                实际应用中,使用较少。

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

@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
public @interface MyAnnotation {
    String value() default "hello";
}
@MyAnnotation(value="hi")
class Person{
    private String name;
    private int age;

    public Person() {
    }
    @MyAnnotation
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    @MyAnnotation
    public void walk(){
        System.out.println("人走路");
    }
    public void eat(){
        System.out.println("人吃饭");
    }
}

class Student extends Person{

}

class Test{
    public static void main(String[] args) {
        // 利用反射测试子类是否继承父类的注解
        Class clazz = Student.class;
        Annotation[] annotations = clazz.getAnnotations();
        for(int i = 0;i < annotations.length;i++){
            System.out.println(annotations[i]);
        }
    }
}

五、利用反射获取注解信息

        JDK 5.0 在 java.lang.reflect 包下新增了 AnnotatedElement 接口, 该接口代表程序中可以接受注解的程序元素。
    
        当一个 Annotation 类型被定义为运行时 Annotation 后, 该注解才是运行时可见, 当 class 文件被载入时保存在 class 文件中的 Annotation 才会被虚拟机读取。
    
        程序可以调用 AnnotatedElement对象的如下方法来访问 Annotation 信息。

        注:下一章反射讲解;

六、JDK8中注解的新特性

 6.1 可重复的注解

JDK8之前实现重复注解:

        如果我们重复定义两个注解,那么就会报如下错误:
                Duplicate annotation. The declaration of 'MyAnnotation' does not have a valid java.lang.annotation.Repeatable annotation

        JDK8之前必须借助另外一个注解类来实现。

public @interface MyAnnotations {
    MyAnnotation[] value();
}

@MyAnnotations({@MyAnnotation(value="hi"),@MyAnnotation(value="hello")})
class Person{}

JDK8之后可重复注解示例:

        注意:
                在MyAnnotation上声明@Repeatable,成员值为MyAnnotations.class;
                MyAnnotation的Target和Retention等元注解与MyAnnotations相同。

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

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


@Inherited
@Repeatable(MyAnnotations.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
public @interface MyAnnotation {
    String value() default "hello";
}

@MyAnnotation(value="hi")
@MyAnnotation(value="abc")
class Person{}

6.2 类型注解

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

        在Java 8之前,注解只能是在声明的地方所使用,Java8开始,注解可以应用在任何地方。
                ElementType.TYPE_PARAMETER 表示该注解能写在类型变量的声明语句中(如:泛型声明)。
                ElementType.TYPE_USE 表示该注解能写在使用类型的任何语句中。

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

    String value() default "hello";
}

public class Generic<@MyAnnotation T>{

    public void show() throws @MyAnnotation RuntimeException{

        ArrayList<@MyAnnotation String> list = new ArrayList<>();

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

}

七、面试题

1、枚举类的理解?
    类的对象只有有限个,确定的。
    当需要定义一组常量时,强烈建议使用枚举类。
    若枚举只有一个对象,则可以作为一种单例模式的实现方式。

2、枚举类的创建方式?
    JDK1.5之前需要自定义枚举类;
    JDK1.5新增的 enum 关键字用于定义枚举类;

3、枚举类的特征?
    1、私有化类的构造器,保证不能在类的外部创建其对象;
    2、在类的内部创建枚举类的实例。声明为:public static final;
    3、对象如果有实例变量,应该声明为private final,并在构造器中初始化;
    注:enum声明的枚举类和class声明的枚举类,基本类似,唯独声明枚举类对象的语法简洁;

4、enum关键字的作用?
    首先,enum声明实际上定义了一个类。因此我们可以通过定义的enum调用其方法。
    其次,Java编译器会自动在enum类型中插入一些方法,其中就包括values()——所以我们的程序在没编译的时候,自然没法查看values()方法的源码了。

5、使用enum关键字定义的枚举类实现接口?
    若每个枚举值在调用实现的接口方法呈现相同的行为方式,则只要统一实现该方法即可。
    若需要每个枚举值在调用实现的接口方法呈现出不同的行为方式, 则可以让每个枚举值分别来实现该方法。

6、什么是注解?以及它的作用?
    从 JDK 5.0 开始, Java 增加了对元数据(MetaData) 的支持, 也就是Annotation(注解)。
    Annotation 其实就是代码里的特殊标记, 这些标记可以在编译, 类加载, 运行时被读取, 并执行相应的处理。
    Annotation 可以像修饰符一样被使用, 可用于修饰包, 类, 构造器, 方法, 成员变量, 参数, 局部变量的声明, 这些信息被保存在Annotation 的 “name=value” 对中。

7、JAVASE常用的注解?
    @Override: 限定重写父类方法, 该注解只能用于方法;在编译期检测是否是重写的方法;
    @Deprecated: 用于表示所修饰的元素(类, 方法等)已过时。通常是因为所修饰的结构危险或存在更好的选择;
    @SuppressWarnings: 抑制编译器警告;
    
8、怎么自定义注解?
    1、定义新的 Annotation 类型使用 @interface 关键字,自定义注解自动继承了java.lang.annotation.Annotation接口。
    2、Annotation 的成员变量在 Annotation 定义中以无参数方法的形式来声明。其方法名和返回值定义了该成员的名字和类型。我们称为配置参数。类型只能是八种基本数据类型、String类型、Class类型、enum类型、Annotation类型、以及所有类型的数组。
    3、我们自定义的注解,如果有成员变量,那么在使用的时候必须赋值;如果自定义的注解的成员变量,赋予了默认值,那么我们使用的时候可以赋值,也可以不赋值;
        我们需要default来给注解的成员变量赋值
    4、如果只有一个参数成员,建议使用参数名为value;
    5、没有成员定义的 Annotation 称为标记; 包含成员变量的 Annotation 称为元数据 Annotation;
        我们都可以称之为注解;

9、什么元注解,元注解有哪些,作用是什么?
    JDK 的元 Annotation 用于修饰其他 Annotation 定义;
    @Retention: 只能用于修饰一个 Annotation 定义, 用于指定该 Annotation 的生命周期, @Rentention 包含一个RetentionPolicy 类型的成员变量, 使用@Rentention 时必须为该 value 成员变量指定值:
        RetentionPolicy.SOURCE:在源文件中有效(即源文件保留),编译器直接丢弃这种策略的注释;
        RetentionPolicy.CLASS:在class文件中有效(即class保留),当运行 Java 程序时, JVM 不会保留注解。 这是默认值;
        RetentionPolicy.RUNTIME:在运行时有效(即运行时保留),当运行 Java 程序时, JVM 会保留注释。程序可以通过反射获取该注释。
    @Target: 用于修饰 Annotation 定义, 用于指定被修饰的 Annotation 能用于修饰哪些程序元素。 @Target 也包含一个名为 value 的成员变量。
    @Documented: 用于指定被该元 Annotation 修饰的 Annotation 类将被javadoc 工具提取成文档。默认情况下,javadoc是不包括注解的。
    @Inherited: 被它修饰的 Annotation 将具有继承性。

10、JDK8注解的新特性?
    Java 8对注解处理提供了两点改进:可重复的注解及可用于类型的注解。

11、重复注解的实现?
    jdk8之前:
        在jdk8之前如果想要实现重复注解,比如注解MyAnnotation想要重复定义多次;那么定义一个MyAnnotations注解;
        MyAnnotations里面的成员变量类型是MyAnnotation[] value();
        注解的使用:@MyAnnotations({@MyAnnotation(value = "myAnnotation"),@MyAnnotation})
    jdk8之后:
        在jdk8之后如果先要实现重复注解,前两步一样;
        在MyAnnotation上声明@Repeatable,成员值为MyAnnotations.class;
        MyAnnotation的Target和Retention等元注解与MyAnnotations相同。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: The `@Retention` annotation is a type of meta-annotation in Java, which is used to specify the retention policy of an annotation type. The retention policy determines the lifespan of an annotation, i.e., how long the annotation will be kept in the Java class file and available to be retrieved by the Java runtime environment. The `@Retention` annotation can have one of the following three values for its retention policy: 1. `RetentionPolicy.SOURCE`: The annotation will be retained only in the source code and will be ignored by the compiler. 2. `RetentionPolicy.CLASS`: The annotation will be retained in the class file, but will not be available at runtime. This is the default retention policy. 3. `RetentionPolicy.RUNTIME`: The annotation will be retained in the class file and will be available at runtime, so it can be obtained via reflection. Here is an example of how to use the `@Retention` annotation: ``` import java.lang.annotation.*; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface MyAnnotation { String value(); } ``` ### 回答2: @Retention 是 Java 语言中的一个修饰符,用于指定注解的保留策略。保留策略指的是注解在被编译成字节码之后的保留方式。 @Retention 注解有三个可选的保留策略: 1. RetentionPolicy.SOURCE:源代码级别保留策略,即注解仅在源代码中存在,编译成字节码后被丢弃。 2. RetentionPolicy.CLASS:字节码级别保留策略,即注解被包含在字节码中,但在运行时被丢弃。这是默认的保留策略。 3. RetentionPolicy.RUNTIME:运行时级别保留策略,即注解被保留在字节码中,并且可通过反射机制在运行时获取到。这种保留策略可以使得注解在程序运行时动态地被解析和使用。 一般来说,我们常用的注解(例如:@Override)采用的是 RetentionPolicy.SOURCE 或 RetentionPolicy.CLASS 保留策略,因为它们的作用在编译期间就能发挥,对于运行时不需要访问的注解,没有必要保留在字节码中。而对于需要在运行时进行解析的注解,例如 Spring 中的 @Autowired 和 JUnit 中的 @Test,我们需要使用 RetentionPolicy.RUNTIME 保留策略。 需要注意的是,@Retention 注解本身需要被 RetentionPolicy.RUNTIME 所保留,才能在运行时获取到注解的保留策略。这是因为 RetentionPolicy 是一个元注解(即注解注解),用于修饰其他注解,所以需要在运行时才能被解析。 ### 回答3: @Retention 是一个注解元素,用来指定注解的生命周期。生命周期包括三个级别:source、class 和 runtime。 1. @Retention(RetentionPolicy.SOURCE):表示该注解只在源代码阶段保留,编译时就会被丢弃。这种注解对于编译器和构建工具来说很有用,例如用于检查代码风格或生成一些辅助类等。 2. @Retention(RetentionPolicy.CLASS):表示该注解会被保留到编译阶段,但不会被加载到运行时环境中。这种注解对于需要在编译期做一些处理的场景很有用,比如自动生成代码或字节码增强等。 3. @Retention(RetentionPolicy.RUNTIME):表示该注解会被保留到运行时环境中,可以在运行期间通过反射机制获取到注解信息。这种注解通常用于运行时动态处理,比如权限检查、日志记录、事件监听等。 使用 @Retention 注解元素是为了告诉编译器或运行时环境如何对待注解,并决定了注解的可见性与有效性范围。根据实际需求,我们可以选择适合的生命周期来应用注解,以实现所需的功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我是波哩个波

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值