注解(Annotation),又称作元数据。翻开之前我写过关于Java语言发展史的博客:那些年Java走过的路,找到其中的
章节便可知,它是属于JDK5.0引入的技术。距今已有17年历史了,可见他还是个少年,或许他也曾梦想仗剑走天涯,可惜bug多没去。
我们第一次接触注解,可能是使用子类继承父类的方法(任何对象的父类都是Object),看到重写的注解@Override
:
package annotation;
/**
* @ClassName Son
* @Description 注解演示
* @Author 古阙月
* @Date 2021/4/8 19:00
* @Version 1.0
*/
public class Son {
@Override // 重写的注解
public String toString() {
return super.toString();
}
}
到后来Spring等框架中普遍使用了各种各样的注解。注解的语法格式非常简单:@ + 注解名
,但是偏偏一个小小的注解,却能在框架中实现各种各样的功能,让人不禁觉得灰常奇妙,而且简单优雅!!!
注解(Annotation)主要有两个作用:
@Override
注解,就知道该注解下的方法为重写父类的方法。JDK内置了三个注解,下面来简单介绍一下。
@Override
注解的简单使用在之前的章节已经介绍过了。它位于java.lang
包下,官方解释,看下面的文档截图:表示一个方法声明打算重写超类中的另一个方法声明。如果方法利用此注释类型进行注解但没有重写超类方法,则编译器会生成一条错误消息。 是不是跟我之前介绍的一样?
我们再点进去@Override
来看一下它的源代码:
package java.lang;
import java.lang.annotation.*;
/**
* @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 {
}
感觉似乎很懵,没有关系,之后给你细细讲解。
@Deprecated
注解 同样位于java.lang
包下,官方解释为:用@Deprecated
注释的程序元素,不鼓励程序员使用这样的元素,通常是因为它很危险或存在更好的选择。在使用不被赞成的程序元素或在不被赞成的代码中执行重写时,编译器会发出警告。
此注解用于修饰构造方法(CONSTRUCTOR)、属性(FIELD)、局部变量(LOCAL_VARIABLE)、方法(METHOD)等,如我们用一个方法举例子:
/**
* 废弃的方法
*/
@Deprecated
public static void deprecatedMethod() {
System.out.println("我是一个被废弃的方法...");
}
当我们试图使用deprecatedMethod()
方法时,便会出现下划线废弃警告,表示不建议使用,危险或者有更好的选择。
当然,你要是想强行使用,也是可以的:
package annotation;
import lombok.ToString;
/**
* @ClassName Son
* @Description 注解演示
* @Author 古阙月
* @Date 2021/4/8 19:00
* @Version 1.0
*/
public class Son {
/**
* 废弃的方法
*/
@Deprecated
public static void deprecatedMethod() {
System.out.println("我是一个被废弃的方法...");
}
@Override // 重写的注解
public String toString() {
return super.toString();
}
public static void main(String[] args) {
// 不建议使用的方法,不安全或者有更好的选择
deprecatedMethod();
}
}
package java.lang;
import java.lang.annotation.*;
import static java.lang.annotation.ElementType.*;
/**
* @author Neal Gafter
* @since 1.5
* @jls 9.6.3.6 @Deprecated
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}
如果看不懂,没关系,之后讲解。
@SuppressWarnings
看字面意思很好理解:镇压警告 的意思。和之前的两兄弟@Override
以及@Deprecated
一样,位于java.lang
包下。官方意思是:用于取消注释元素中指定显示的编译器警告,详情可见下图文档截图:
这样看来似乎有些小抽象,没关系,我们来举一个例子:
public void suppressWarningsMethod() {
double num = 3.14;
}
上文是一个方法,如果suppressWarningsMethod()
方法以及num
数值没有被使用过的话,是会被暗化显示的:
我们完全可以用@SuppressWarnings
注解来取消这个编译器警告:
@SuppressWarnings("unused") // 取消未使用便做暗化处理的编译器警告
public void suppressWarningsMethod() {
double num = 3.14;
}
效果如下:
这个时候有细心的小伙伴可能就会发现了:之前使用重写注解@Override
以及废弃注解@Deprecated
时是直接使用的,可使用这个抑制警告注解@SuppressWarnings
时,怎么是这么用的:@SuppressWarnings("unused")
,这个括号里面双引号里面的 unused 是啥意思?
不要急,我们先来看一下
@SuppressWarnings
注解的源码
package java.lang;
import java.lang.annotation.*;
import static java.lang.annotation.ElementType.*;
/**
* @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 {
String[] value();
}
这个时候,我们就会发现@SuppressWarnings
注解跟之前两个注解一个很大的不同,多了一个:
String[] value();
这个value()
像方法又没有方法体,类型还是个String数组,好像让人有些看不懂!
这里提前透露一下,这个
value()
就是这个@SuppressWarnings
的配置参数,value
为这个配置参数的参数名,而value()
是注解中配置参数的声明形式。而String[]
则是这个名为value
的配置参数的类型!!!
所以,当我们使用@SuppressWarnings
注解时,后面需要加上参数。由上文可知,这个参数还可以为多个:
@SuppressWarnings({"unused", "null"}) // 取消编译器警告
public void suppressWarningsMethod() {
double num = 3.14;
}
那么,这个参数值是不是随便加的呢?那倒也不是,不同的参数有不同的含义,负责镇压不同的警告,具体含义如下表所示:
参数值 | 含义 |
---|---|
all | to suppress all warnings(抑制所有警告) |
boxing | to suppress warnings relative to boxing/unboxing operations(要抑制与箱/非装箱操作相关的警告) |
cast | to suppress warnings relative to cast operations(为了抑制与强制转换操作相关的警告)) |
dep-ann | to suppress warnings relative to deprecated annotation(要抑制相对于弃用注释的警告) |
deprecation | to suppress warnings relative to deprecation(要抑制相对于弃用的警告) |
fallthrough | to suppress warnings relative to missing breaks in switch statements(在switch语句中,抑制与缺失中断相关的警告) |
finally | to suppress warnings relative to finally block that don’t return(为了抑制警告,相对于最终阻止不返回的警告) |
hiding | to suppress warnings relative to locals that hide variable(为了抑制本地隐藏变量的警告) |
incomplete-switch | to suppress warnings relative to missing entries in a switch statement (enum case)(为了在switch语句(enum案例)中抑制相对于缺失条目的警告) |
nls | to suppress warnings relative to non-nls string literals(要抑制相对于非nls字符串字面量的警告) |
null | to suppress warnings relative to null analysis(为了抑制与null分析相关的警告) |
rawtypes | to suppress warnings relative to un-specific types when using generics on class params(在类params上使用泛型时,要抑制相对于非特异性类型的警告) |
restriction | to suppress warnings relative to usage of discouraged or forbidden references(禁止使用警告或禁止引用的警告) |
rawtypes | to suppress warnings relative to un-specific types when using generics on class params(在类params上使用泛型时,要抑制相对于非特异性类型的警告) |
serial | to suppress warnings relative to missing serialVersionUID field for a serializable class(为了一个可串行化的类,为了抑制相对于缺失的serialVersionUID字段的警告) |
static-access | o suppress warnings relative to incorrect static access(o抑制与不正确的静态访问相关的警告) |
synthetic-access | to suppress warnings relative to unoptimized access from inner classes(相对于内部类的未优化访问,来抑制警告) |
unchecked | to suppress warnings relative to unchecked operations(相对于不受约束的操作,抑制警告) |
unqualified-field-access | to suppress warnings relative to field access unqualified(为了抑制与现场访问相关的警告) |
unused | oto suppress warnings relative to unused code(抑制没有使用过代码的警告) |
当大家看到最后一个unused
时,是不是瞬间就知道了为什么本博主之前用的是@SuppressWarnings("unused")
了呢?当然了,抑制警告注解@SuppressWarnings
虽然是强迫症的福利,但是平时工作中使用较少,大家了解下就好,也不必深究。
在之前我们已经了解了JDK内置的三个注解重写注解@Override
、废弃注解@Deprecated
以及抑制警告注解@SuppressWarnings
,想必大家已经比较清楚它们的作用了,那么注解的作用仅限于此吗?
当然不是,看看框架里面那些注解,比如
@RestController
、@Autowired
等,就知道注解没那么简单了!!!
当然了,我们也可以自定义属于自己的注解。这么一说,是不是有意思多了呢?
至于怎么自定义属于自己的注解呢?这就不得不说到元注解了。
那么,什么是元注解呢?我们先来看之前重写注解@Override
的源码:
package java.lang;
import java.lang.annotation.*;
/**
* @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 {
}
我们会发现在重写注解@Override
的上方有这样两行代码:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
这个@Target
以及@Retention
就是所谓的元注解了!
元注解就是注解的注解,用于给其他注解作出说明!
在jdk1.5中总共定义了四个元注解供我们使用,他们分别是:
@Target
:用于描述注解的使用范围。@Retention
:用于描述注解的生命周期,指示注解要保留多久。@Documented
:说明该注解被包含在javadoc中。@Inherited
:表示子类可以继承父类的该注解。然后,在jdk1.8中又新定义le两个元注解:
@Native
: 表示被注解的内容是原生(本机)相关的。(说白了就是去调用非Java语言实现的代码,比如C)@Repeatable
:表示该注解可以重复使用。大家看到这里,想必也有些懵了:6个元注解,该怎么用呀?
不必担心,简化思维,我们重点关注前面两个元注解就好了:@Target
以及@Retention
,而且这两个元注解里面的值也是规定好了的枚举值。为什么说这两个注解很重要呢?因为其他注解都可以少,唯独这两个注解必不可少!!!
含义之前已经讲过了:用于描述注解的使用范围。
我们来看一下@Target
的源代码:
package java.lang.annotation;
/**
* @since 1.5
* @jls 9.6.4.1 @Target
* @jls 9.7.4 Where Annotations May Appear
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
ElementType[] value();
}
这样我们就会发现注解@Target
里面的参数值是个ElementType
数组:
ElementType[] value();
点开ElementType
的源代码一看,这是个枚举类型:
package java.lang.annotation;
/**
*
* @author Joshua Bloch
* @since 1.5
* @jls 9.6.4.1 @Target
* @jls 4.1 The Kinds of Types and Values
*/
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 // 使用类型
}
ElementType
内各个值的含义已经写在注释上了,比如如果我们想某个注解能够使用的类(TYPE)、方法(METHOD)以及属性(FIELD)上,则应该在该注解上加上:
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
当然了,如果不使用@Target
注解,该注解可以作用于任何元素上!!!
同样,该注解的含义之前也已经讲过了:用于描述注解的生命周期,指示注解要保留多久。
我们也来看一下它的源码:
package java.lang.annotation;
/**
* Indicates how long annotations with the annotated type are to
* be retained. If no Retention annotation is present on
* an annotation type declaration, the retention policy defaults to
* {@code RetentionPolicy.CLASS}.
*
* A Retention meta-annotation has effect only if the
* meta-annotated type is used directly for annotation. It has no
* effect if the meta-annotated type is used as a member type in
* another annotation type.
*
* @author Joshua Bloch
* @since 1.5
* @jls 9.6.3.2 @Retention
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
/**
* Returns the retention policy.
* @return the retention policy
*/
RetentionPolicy value();
}
发现它的参数值也是一个枚举值RetentionPolicy
:
package java.lang.annotation;
/**
* Annotation retention policy. The constants of this enumerated type
* describe the various policies for retaining annotations. They are used
* in conjunction with the {@link Retention} meta-annotation type to specify
* how long annotations are to be retained.
*
* @author Joshua Bloch
* @since 1.5
*/
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.
* 编译
*/
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
*/
RUNTIME
}
里面有三个值,它们的含义以及范围大小关系为:
(SOURCE(源码) < CLASS(编译) < RUNTIME(运行时)
当然了,我们实际使用过程中 一般用runtime,而如果当我们不使用@Retention
注解时,默认为CLASS,也就是注解保留到编译阶段。
小小的总结一下:
@Target
以及@Retention
注解是我们最常用到的注解,但是你不加上也没关系,因为有默认值,但是我们一般会加上以便加以某种限制。
好了,在上一个章节,我们介绍了元注解,这里我们可以来学着自定义注解了,首先来介绍一下自定义注解的格式:
元注解
修饰符 @interface 注解名 {
配置参数类型 配置参数名(); // 配置参数可以有多个,也可以没有
}
比如@SuppressWarnings
注解:
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
String[] value();
}
当我们声明了一个自定义注解,则自动继承了java.lang.annotation.Annotation
接口,如我们按照之前的语法规则自定义一个名为myAnnotation的注解:
// 该注解保留在运行时
@Retention(RetentionPolicy.RUNTIME)
// 该注解可作用在类、属性、方法上
@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})
@interface myAnnotation {
String[] value();
}
不过需要注意的是:配置参数的类型只能是基本数据类型、String、enum、注解类型,而不能是其他,比如Object、Integer等。
我们来简单使用一下这个注解吧:
package annotation;
import java.lang.annotation.*;
/**
* @ClassName MyAnnotationTest
* @Description 自定义注解测试类
* @Author 古阙月
* @Date 2021/4/11 16:23
* @Version 1.0
*/
@myAnnotation("MyAnnotationTest")
public class MyAnnotationTest {
@myAnnotation("main")
public static void main(String[] args) {
}
}
// 该注解保留在运行时
@Retention(RetentionPolicy.RUNTIME)
// 该注解可作用在类、属性、方法上
@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})
@interface myAnnotation {
String[] value();
}
此处,还有一点细节,如果自定义注解内配置参数仅有一个时并且配置参数名为value,配置参数名可省略:
@myAnnotation("MyAnnotationTest")
public class MyAnnotationTest {
@myAnnotation("main")
public static void main(String[] args) {
}
}
但是如果有多个,或者配置参数名为一个且不为value
,则需要加上参数名,如:
// 该注解保留在运行时
@Retention(RetentionPolicy.RUNTIME)
// 该注解可作用在类、属性、方法上
@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})
@interface myAnnotation {
String[] value();
// 修饰符
String modifier();
}
应当加上参数名:
@myAnnotation(value = "MyAnnotationTest", modifier = "public")
public class MyAnnotationTest {
@myAnnotation(value = "main", modifier = "public")
public static void main(String[] args) {
}
}
但是,在配置参数后面可以加上默认值,语法为:
配置参数类型 配置参数名() default 默认值;
如果有默认值的话,我们在使用注解时,可以不手动加入配置参数值,如:
// 该注解保留在运行时
@Retention(RetentionPolicy.RUNTIME)
// 该注解可作用在类、属性、方法上
@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})
@interface myAnnotation {
String[] value() default "methodName";
// 修饰符
String modifier() default "public";
}
可以不加配置参数值,后面的括号也可有可无:
@myAnnotation()
public class MyAnnotationTest {
@myAnnotation
public static void main(String[] args) {
}
}
总之,得保证一定有值就对了!!!
好了,到这里这篇介绍注解的博客就结束了。如果有一定基础的小伙伴,估计会说:就这?那框架里面怎么使用的注解呀?你这啥功能都没实现,都没有呀!那要这注解有何用?
其实我真的挺冤枉的:博客陆陆续续写了一个多礼拜,也肝了近一万五千字了。只来得及写一些简单的介绍以及基本语法,实在是肝不动了。至于注解如何结合Spring框架的IOC以及AOP思想,实现种种神奇的功能?比如我们常见的事务。
能看到这里,并且看完我的碎碎念的 估计是神人了,我猜是没有多少人看到这里的。创作不易,如果觉得不错的话,顺手给个点赞、评论以及收藏加关注呗!如果有哪里讲的不对的地方,也非常欢迎评论指正哦!