注解的概念与用法(举例说明)

注解的理解与使用

注解的释义元数据的含义基础阶段常见的注解注解的作用(包括但不限于)教你读懂注解内部代码内容五种元注解尝试解读简单注解==我当时的疑惑点==

自定义注解自定义注解举例

注解的原理总结

注解的释义

我们都知道注释是拿来给程序员看的,而注解就是给程序(或者说JVM)看的。

注解(Annotation)是代码级别的说明,它是JDK 1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。注解是Java语言中的一种元数据形式,它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明。

元数据的含义

在Java中,元数据的含义是指描述Java类、接口、字段和方法等元素的数据。这些数据提供了关于Java程序元素的额外信息,可以用于反射、注解处理、代码生成等场景。通过元数据,我们可以在运行时获取和操作程序元素的属性、注解等信息,从而实现更灵活和动态的编程能力。

基础阶段常见的注解

我们在基础阶段常见的注解并没有多少,很少用到复杂注解,

注解Value@Override(重写或者覆盖)一般放在方法前面,表示方法在子类中重写了父类中的方法@Deprecated可放在类、方法或字段前面,表示一个类、方法或字段已经过时了,不应该再使用@SuppressWarnings一般放在程序代码靠前位置,抑制编译器产生的警告,这个注解后面有大括号是可以传参的,一般图方便就是写个“all”@Native一般放在方法前面,指示一个方法是native方法,即由本地代码实现的方法@Test一般用于测试,可以不用启动整个项目就能调用部分代码

注解的作用(包括但不限于)

标记检查:

注解可以用来标记一些需要被检查的代码,以便进行静态检查或动态检查。

代码生成:

注解可以被用来生成代码,如JUnit注解可以根据测试方法定义生成测试用例。

生成文档:

特定的注解可以用来生成对应文档,例子是在注释里的注解

拿最后一个举个例子 编写一个演示类

package budiu.budiu;

/**

* 这是一个名字为DocDemo的类

*

* @Author:罗不丢

* @Version:1.0

*/

public class DocDemo {

/**

* @param name

* @return java.lang.String

**/

public String play (String name){

return name+"正在玩";

}

}

注意编码格式别搞错,有中文的把UTF-8换成GBK

用javadoc命令生成文档

就会生成一堆的前端文件,可以当做文档查看,和jdk的开发文档是一个样子的

教你读懂注解内部代码内容

在想要了解注解的内部代码,我们要先了解五种元注解(元注解的意思是注解的最基本组成部分,任何注解都要由它们组成)

五种元注解

@Retention: 它只有一个参数:RetentionPolicy,用于指定注解的生命周期。 这个参数有三个可能的值: RetentionPolicy.SOURCE(注解仅在源代码中存在,编译时会被丢弃), RetentionPolicy.CLASS(注解在编译时会被保留,在JVM中运行时不可见) RetentionPolicy.RUNTIME(注解在运行时还会存在,可以通过反射机制读取它的信息)。

@Target: 它有一个参数:ElementType,用于指定注解可以应用到的Java元素类型。 例如,方法可以被注解,字段可以被注解,甚至注解本身也可以被注解。 ElementType包含多种可能的值,如ElementType.METHOD,ElementType.FIELD,ElementType.ANNOTATION_TYPE等。

@Documented: 这个元注解没有参数。它的作用是表示使用该注解的注解信息能够被javadoc或类似工具文档化。

@Inherited: 这个元注解也没有参数。表示被它修饰的注解具有继承性,即如果一个类声明了被@Inherited修饰的注解,那么它的子类也将具有这个注解。 注意:不是注解继承父注解,而是子类继承父类里的注解。

@Repeatable: 这个元注解是Java 1.8引入的,它表示一个注解可以被重复使用在一个声明上。它有一个参数,这个参数是用来定义该注解的“容器”注解类型,用于存储重复注解的数据。(还没怎么了解过)

尝试解读简单注解

这里以最简单的@Override注解(JDK1.8版本)为引入

/*

* Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved.

* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.

*/

package java.lang;

import java.lang.annotation.*;

/**

* Indicates that a method declaration is intended to override a

* method declaration in a supertype. If a method is annotated with

* this annotation type compilers are required to generate an error

* message unless at least one of the following conditions hold:

* 指示方法声明旨在重写

* 超类型中的方法声明。如果方法被注释

*需要此注释类型编译器才能生成错误

*消息,除非至少满足以下条件之一:

*

*

*

  • * The method does override or implement a method declared in a

    * supertype.

    * 该方法会重写或实现在

    * 超类型。

    *

  • * The method has a signature that is override-equivalent to that of

    * any public method declared in {@linkplain Object}.

    * 该方法具有覆盖等效于

    * 在 {@linkplain Object} 中声明的任何公共方法。

    *

*

* @author Peter von der Ahé

* @author Joshua Bloch

* @jls 9.6.1.4 @Override

* @since 1.5

*/

@Target(ElementType.METHOD)

@Retention(RetentionPolicy.SOURCE)

public @interface Override {

}

除去注释后,代码并不算长,我们只需要了解三行,

@Target(ElementType.METHOD): 这是一个元注解,它用来指明Override注解可以用在什么地方。在这个例子中,它表示Override只能用在方法上。ElementType.METHOD表示这个注解的目标是方法。

@Retention(RetentionPolicy.SOURCE): 这是另一个元注解,用来指明注解的保留策略。这里指定的是RetentionPolicy.SOURCE,意味着这个注解只保留在源代码中,编译成.class文件时它将被丢弃。也就是说,这个注解在运行时是不可见的。

public @interface Override: 这定义了一个名为Override的注解接口。@interface是用来声明一个注解的关键字。这个接口是公开的(public),意味着它可以被任何人访问。

再试着解读一个

/**

* 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.

* 表示应在

* 带注释的元素(以及包含在带注释的

* 元素)。 请注意,给定元素中禁止显示的警告集是

* 在所有包含元素中禁止显示的警告的超集。 为

* 例如,如果您对一个类进行注释以禁止显示一个警告并注释一个警告

* 方法来抑制另一个,两个警告都会在该方法中被禁止。

*

*

*

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 not 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.

*

*

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();

}

这个注解是Java的@SuppressWarnings注解,它的定义解释了它的功能和用法。现在,我们来详细解读一下这个注解:

@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE}): 这是@SuppressWarnings注解的元注解,指明了它可以用于什么类型的Java元素。具体来说,它可以用于类、字段、方法、参数、构造器和局部变量。它的作用范围非常广泛。

@Retention(RetentionPolicy.SOURCE): 这个元注解表示@SuppressWarnings注解的保留策略是SOURCE,也就是说它仅在源代码中存在,编译时会被丢弃。因此,这个注解不会影响运行时的行为。

public @interface SuppressWarnings: 这定义了名为SuppressWarnings的注解。当开发者想要抑制编译器产生的某些警告时,可以使用这个注解。

String[] value();: 这是@SuppressWarnings注解的唯一 一个元素。它是一个字符串数组,表示要抑制的警告的名称。例如,如果你想要抑制未经检查的转换警告,你可以使用"unchecked"字符串。编译器会忽略这些特定的警告。需要注意的是,这里提到编译器应该忽略它们不认识的警告名称,但是也可以自由选择在遇到不识别的警告名称时发出警告。

我当时的疑惑点

在解读SuppressWarnings注解时,为什么最后一段的大括号里就定义了一个字符串数组,一点逻辑都看不出来,要按照之前的思维来说,它怎么识别到字符串并判断抑制特定警告的,不应该有一些内部方法来完成吗?

其实在Java编译器中,当解析和处理Java代码时,会检查代码上的注解。当编译器遇到@SuppressWarnings注解时,它会查看注解中的value数组,这些值表示需要抑制的警告类型。

编译器内部会有一个警告名称与警告类型的映射逻辑。当编译器遇到特定的代码模式或情况时,它会生成相应的警告。当存在@SuppressWarnings注解时,编译器会检查生成的警告名称是否匹配注解中指定的名称,如果匹配,则不会将该警告显示给开发者。

需要注意的是,这个匹配和抑制逻辑是由编译器实现的,而不是由注解本身实现的。不同的编译器可能会对@SuppressWarnings注解中的警告名称有不同的处理方式,但通常都会遵循Java语言规范中定义的行为。

虽然@SuppressWarnings注解本身只是一个标记,但通过编译器的逻辑处理,它能够实现警告的抑制功能。

所以我们不太需要关注注解的逻辑,就单纯把它理解成一种给编译器识别的标识符号就好。

自定义注解

除了官方提供的注解,我们也可以自定义注解,并使用自定义注解。 自定义注解的一些规范: 注解定义:

注解使用@interface关键字进行定义。

元注解:

为你的自定义注解提供元注解,特别是@Target和@Retention,以明确你的注解的用途和生命周期。

成员变量:

注解中的成员变量代表注解的参数。这些成员变量只能是基本类型、String、Class、枚举类型、注解类型,或者这些类型的数组。

默认值:

为注解的成员变量提供一个默认值。

命名规范:

注解的名称应该是描述性的,并且应该使用名词。同时,为了避免混淆,建议为注解名称加上一个特定的前缀。

自定义注解举例

自定义注解的例子——@RepeatableHint。这个注解的目的是给开发者提供一个可重复的提示信息。

首先,定义 Hint 注解:

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME) //会保留到运行时阶段

@Target(ElementType.TYPE) //限制使用范围是类,枚举,接口

public @interface Hint {

String value();

}

接着,定义 RepeatableHint 注解:

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME) //会保留到运行时阶段

@Target(ElementType.TYPE) //限制使用范围是类,枚举,接口

public @interface RepeatableHint {

Hint[] hints() default {}; //算是个嵌套吧,但底层仍是字符串数组

}

注意这里 RepeatableHint 注解中定义了一个名为 hints 的成员,它的类型是 Hint[],默认值为空数组。这样就可以在使用 RepeatableHint 注解时,重复使用 Hint 注解。

然后在需要的类或者接口或枚举类上使用这些注解:

@RepeatableHint(hints={

@Hint("这是一个测试类的提示1"),

@Hint("提示2")

})

public class TestClass {

public static void main(String[] args) {

// 获取TestClass的Class对象

Class testClass = TestClass.class;

// 判断TestClass是否带有RepeatableHint注解

if (testClass.isAnnotationPresent(RepeatableHint.class)) {

// 获取RepeatableHint注解对象

RepeatableHint repeatableHint = testClass.getAnnotation(RepeatableHint.class);

// 获取所有的Hint注解对象

Hint[] hints = repeatableHint.hints();

// 遍历并打印所有的提示信息

for (Hint hint : hints) {

System.out.println(hint.value());

}

}

}

}

在处理这些注解时,可以通过反射获取到所有的 Hint 注解,并对其进行处理。例如,可以在程序启动时,通过解析这些注解,将提示信息打印到日志中或者进行其他处理。

注解的原理

注解的原理基于Java的反射机制。

注解本质: 注解本质上是一个接口,它继承了java.lang.annotation.Annotation接口。当我们定义一个注解时,实际上定义了一个新的接口。

动态代理: 当我们通过反射获取注解时,返回的对象实际上是Java运行时生成的动态代理对象。这意味着我们调用注解的方法时,实际上是在调用动态代理对象上的方法。

AnnotationInvocationHandler: 这是Java内部用于处理注解调用的一个类。当我们通过动态代理对象调用注解的方法时,最终会调用AnnotationInvocationHandler的invoke方法。这个方法会从一个叫做memberValues的Map中索引出对应的值。

memberValues的来源: memberValues中的数据来源于Java的常量池。这就是为什么我们可以在注解中定义一些常量,然后这些常量的值会被存储在Java的常量池中。

元注解的角色: 元注解如@Retention, @Target等,它们的作用是描述注解的特性。例如,@Retention定义了注解的生命周期,@Target定义了注解可以应用的目标。

运行时处理: 大部分注解的处理都是在运行时完成的。例如,框架可能会在运行时扫描某个特定的注解,并根据该注解的属性修改框架的行为。

总的来说,注解提供了一种元编程的方式。我们可以在不改变原有代码逻辑的情况下,通过添加注解来改变代码在编译后或者运行时的行为。而这一切都是基于Java的反射机制以及动态代理技术实现的。

总结

虽然注解在Java SE阶段用的很少,但在框架学习中,比如SpringBoot中将会成为常客,我们也会从面向对象编程逐渐转化为面向注解编程🤭🤭🤭🤭🤭。