注解的理解与使用
- 注解的释义
- 元数据的含义
- 基础阶段常见的注解
- 注解的作用(包括但不限于)
- 教你读懂注解内部代码内容
- 五种元注解
- 尝试解读简单注解
- ==我当时的疑惑点==
- 自定义注解
- 自定义注解举例
- 注解的原理
- 总结
注解的释义
我们都知道注释是拿来给程序员看的,而注解就是给程序(或者说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命令生成文档
就会生成一堆的前端文件,可以当做文档查看
教你读懂注解内部代码内容
在想要了解注解的内部代码,我们要先了解五种元注解(元注解的意思是注解的最基本组成部分,任何注解都要由它们组成)
五种元注解
@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:
* 指示方法声明旨在重写
* 超类型中的方法声明。如果方法被注释
*需要此注释类型编译器才能生成错误
*消息,除非至少满足以下条件之一:
*
*
* <ul><li>
* The method does override or implement a method declared in a
* supertype.
* 该方法会重写或实现在
* 超类型。
* </li><li>
* The method has a signature that is override-equivalent to that of
* any public method declared in {@linkplain Object}.
* 该方法具有覆盖等效于
* 在 {@linkplain Object} 中声明的任何公共方法。
* </li></ul>
*
* @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.
* 表示应在
* 带注释的元素(以及包含在带注释的
* 元素)。 请注意,给定元素中禁止显示的警告集是
* 在所有包含元素中禁止显示的警告的超集。 为
* 例如,如果您对一个类进行注释以禁止显示一个警告并注释一个警告
* 方法来抑制另一个,两个警告都会在该方法中被禁止。
*
*
* <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();
}
这个注解是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 = 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中将会成为常客,我们也会从面向对象编程逐渐转化为面向注解编程🤭🤭🤭🤭🤭。