Java 中的注解到底是什么?注解有什么作用?

Java 注解的理解和使用

什么是注解

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

通俗解释:注解可以直观的理解为注释,只不过这个注释在任何时候都就可以用,包括编译期和运行期,主要是用作补充作用。也可以将其理解成标签,可以给方法,类等地方做标注。同时我们还能够在任何时间或地点获得这种标注。从而用来提供一些补充信息和配置。

可能到这还不是很清楚,那么我们先继续向下看。

注解的定义

首先来看注解的使用,注解通过 @interface 关键字进行定义。

public @interface TestAnnotation {
}

@interface就类似于Java 中的class, interface关键字,都是一种类型。

上述定义就可以理解为创建了一个标签或者标注。

什么是元注解

什么是元注解呢?当我们定义了一个注解之后,该注解还是无法正常工作的,我们还需要给注解做一些配置,比如该注解的生命周期,该注解覆盖的范围。

其实也比较好理解,当我们创建一个标签的时候,我们要知道该标签可以用在什么地方或者一些其他的配置。

此时就有了元注解,顾名思义,元注解就是应用到注解上的注解,常见的元注解有五种:

  • @Target:指定注解可以应用的元素类型。它的取值可以是ElementType.TYPE(类、接口、枚举)、ElementType.FIELD(字段)、ElementType.METHOD(方法)、ElementType.PARAMETER(参数)等。
  • @Retention:指定注解的生命周期。它的取值可以是RetentionPolicy.SOURCE(编译期可见)、RetentionPolicy.CLASS(类加载期可见)或RetentionPolicy.RUNTIME(运行期可见)。
  • @Documented:指定注解是否会包含在文档中。
  • @Inherited:指定注解是否可以被继承。如果一个注解被标记为@Inherited,则子类会继承父类的注解。
  • @Repeatable:指定注解是否可重复。Java 8引入了可重复注解的概念,允许在同一个元素上多次使用相同的注解类型。通常是一个注解的值可以有多个。

这些元注解可以用于定义自定义注解,并对自定义注解的行为进行约束和配置。例如,通过在自定义注解上使用@Target(ElementType.METHOD),可以限制该注解只能应用于方法上;通过使用@Retention(RetentionPolicy.RUNTIME),可以指定该注解在运行时可见。

注解的属性

注解的属性也叫做成员变量。注解只有成员变量,没有方法。注解的成员变量在注解的定义中以“无形参的方法”形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
    int id();
    String msg();
}

需要注意的是,在注解中定义属性时它的类型必须是 8 种基本数据类型外加 类、接口、注解及它们的数组。

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

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
    public int id() default -1;
    public String msg() default "Hi";
}

注解的使用

上面说明了Java 中注解的一些相关概念,那么注解到底如何使用呢?我们平时在哪些地方都用到了注解呢?

我们知道在运行时可以使用反射来获取一些Class的信息,那么当一个注解的生命周期设置为运行时的时候,我们依然可以通过反射来获取注解的一些信息。

所以注解一般是配合反射来使用的,由于注解的特性,我们可以对注解的声明周期,覆盖范围,属性做一些配置,由于Java反射的存在,我们可以在运行时来提取注解,从而获取注解的一些属性,从而我们能够做一些自己的处理。

此时又有疑问了,我们获取这些注解的信息有什么作用呢?

其实当我们使用注解修饰了类和方法等成员之后,注解自己是不会生效的,必须由开发者提供相应的代码来提取并处理注解信息,这些处理注解的代码称为 APT(Annotation Processing Tool)。这也就是为什么官方解释注解并不会对源代码有什么影响了,注解只是一个标签工具,再打上这个标签之后,开发者可以使用相应的代码来获取,从而做一些处理工作。

那么一般都是什么处理工作呢?非常好理解,我们自己定义一个注解然后进行处理。

举例:

我们可以写一个检测代码是否出错的注解,如果出错我们记录一下错误信息。

首先写一段代码:

package demo;

/**
 * @ClassName NoBug
 * @Author cuisenghe
 * @Date DATE
 */
public class NoBug {
    @Check
    public void add(){
        System.out.println("1+1=" + 1+1);
    }
    @Check
    public void minus(){
        System.out.println("1-1=" + (1-1));
    }
    @Check
    public void multi(){
        System.out.println("1*1=" + 1*1);
    }
    @Check
    public void divide(){
        System.out.println("1/0=" + 1/0);
    }
}

check注解:

package demo;


import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

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

}

检查工具类:

package demo;

import java.lang.reflect.Method;

/**
 * @ClassName CheckTest
 * @Author cuisenghe
 * @Date DATE
 */
public class CheckTest {
    public static void main(String[] args) {
        // 创建对象
        NoBug noBug = new NoBug();
        // 获取对象的Class
        Class<? extends NoBug> aClass = noBug.getClass();
        // 根据class 获取所有的方法
        Method[] declaredMethods = aClass.getDeclaredMethods();
        //用来记录测试产生的 log 信息
        StringBuilder log = new StringBuilder();
        // 记录异常的次数
        int errornum = 0;
        for (Method method : declaredMethods) {
            // 判断方法上是否由@Check注解
            if (method.isAnnotationPresent(Check.class)) {
                try {
                    // 有则执行该方法
                    method.setAccessible(true);
                    method.invoke(noBug, null);
                } catch (Exception e) {
                    // 捕获异常
                    // 记录到log中
                    errornum++;
                    log.append(method.getName());
                    log.append(" ");
                    log.append("has error:");
                    log.append("\n\r  caused by ");
                    // 记录异常的次数
                    //记录测试过程中,发生的异常的名称
                    log.append(e.getCause().getClass().getSimpleName());
                    log.append("\n\r");
                    //记录测试过程中,发生的异常的具体信息
                    log.append(e.getCause().getMessage());
                    log.append("\n\r");
                }

            }
        }
        log.append(aClass.getSimpleName());
        log.append(" has  ");
        log.append(errornum);
        log.append(" error.");
        // 生成测试报告
        System.out.println(log.toString());
    }
}

运行结果:

在这里插入图片描述

可以发现,注解帮我们检测出了有方法执行时发生了错误,这就是为什么官方说明,注解并不会自动生效,而是需要开发者们自己编写处理逻辑。

那有人又有疑问了?为什么我平时使用的注解都是自动生效的?

其实是当我们使用一些框架的时候,开发者们已经写好了关于这些注解的逻辑,当我们使用这些注解,并执行程序的时候,框架会自动帮我们运行这些处理逻辑,从而使得这些注解生效。

常见的一些官方注解有:

@Deprecated:这个元素是用来标记过时的元素,想必大家在日常开发中经常碰到。编译器在编译阶段遇到这个注解时会发出提醒警告,告诉开发者正在调用一个过时的元素比如过时的方法、过时的类、过时的成员变量。

这个注解是编译期的注解,这个注解的处理逻辑是编译器完成的,其实也是编译器的开发者完成的。

@Override:提示子类要复写父类中被 @Override 修饰的方法。

在我们使用spring框架进行开发的时候,也会有一些注解,比如@Autowired注解,@Resource注解等等,这些注解都是配合spring框架完成一些工作,这些注解的处理逻辑都是框架帮我们完成的,我们只需要按照要求使用这些注解就可以了。

参考:java注解-最通俗易懂的讲解 - 知乎 (zhihu.com)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

小崔同学24

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

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

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

打赏作者

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

抵扣说明:

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

余额充值