前言
本文主要讲解 JDK 中的注解如何使用及如何自定义注解,内容相对简单。希望通过阅读本文能够帮助对注解存在疑惑你。
一、什么是注解
如果要问什么是注解,可能有很同学不知道该如何描述,像经常看到的@Override、@Controller ... 等等,都是注解,但是他们有什么用呢?这里先通过一个大家比较熟悉的注释来简单说明一下。
1. 注释
注释,不用多说,经常用,就是给某段程序加一段描述,让人可以快速了解这段代码是做了个什么事情。通常写法有以下几种:
//
/**
*
*/
那么简单概述一下,即
注释,是用文字描述程序,给程序员看的
2. 注解
回过头来,再来看注解。先看一下官方的定义:
注解(Annotation),也叫元数据,一种代码级别的说明,它是 JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。
相信还没有弄明白注解的同学,看完后依然是一脸懵逼,那么再来用我们自己的话来概述:
注解:是用来说明程序的,给计算机看的注释,没错,它也是起到了注释的作用
二、注解的作用
了解了注解的定义,看一下它的作用
作用的分类:
1. 编写文档:通过代码里的标识的注解生成文档【生成 doc 文档】
2. 代码分析:通过代码里标识的注解对代码进行分析【使用反射】
3. 编译检查:通过代码里标识的注解让编译器能够实现基本的编译检查【Override】
1. 编译检查
这个比较简单,举个例子看一下,它是如何进行编译检查的。
首先创建一个 Java 类User.java
public class User {
private String id;
private String name;
@Override
public String toString() {
return "User{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
'}';
}
}
很简单,主要看一下 toString方法,它上面有一个@Override注解,重写了 Object类的 toString 方法。当我们将方法名字稍微改一下,如下图:
可以看到,编译报错了,因为父类并没有toString1 这个方法。这就是它的编译检查作用。
2. JavaDoc 文档演示
还是基于 User类进行演示,新增一个add方法,并加上对应的注释:
/**
* 注解 JavaDoc 描述
*
* @author: <a href="568227120@qq.com">搬运 Gong</a>
* @version: 1.0
*/
public class User {
private String id;
private String name;
@Override
public String toString() {
return "User{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
'}';
}
/**
* 两个整数相加
*
* @param a 第一个整数
* @param b 第二个整数
* @return: int 返回的结果
*/
public int add(int a, int b) {
return a + b;
}
}
下面使用 JavaDoc 命令进行doc 文档生成:
javadoc User.java
找到 index.html,打开查看
我们发现,通过注解加入的一些描述,都被放到了 JavaDoc中。
3. 利用反射分析代码
这一块放到下面进行讲解,需要配合注解的使用一起来看。
三、JDK 中预定义的注解
@Override:检查被该注解标注的方法是否是继承自父类
@Deprecated: 表示注释的内容过时(过时了,但是依然可以使用)
@SuppressWarings: 压制警告
像图中的那些黄色警告,都可以使用 @SuppressWarings 来进行压制,可以放到方法上面,也可以放到类上面,比如:
这样是不是就看着舒服多了。
四、自定义注解及使用
看了很多注解,那么我们是不是可以写自己的注解呢?又改如何去写?
1. 注解的格式
//元注解
public @interface 注解名称 {
//属性列表
}
知道了注解的格式后,来写一个自定义的注解:
public @interface MyAnno {
}
这样就好了,可以使用了:
@MyAnno
public int add(int a, int b) {
return a + b;
}
可以使用后,有的同学又会问了,那这个注解有啥用呢?别急,下面来分析一下,这个注解到底是个什么东西,先把它进行编译,然后再反编译后查看:
javac MyAnno.java
得到 class 文件后,使用 javap 来反编译查看注解的内容:
这是反编译后的内容,可以看到,它的本质就是一个接口,集成的 java.lang.annotation.Annotation 接口。
既然它的本质就是一个接口,那我们就可以在它的里面写接口里面可以写的东西,比如定义常量、方法等,如下:
public @interface MyAnno {
public static String str = "Annotation";
String show1();
}
属性:在接口中定义的抽象方法
返回结果必须是如下类型:
1. 基本数据类型
2. String 类型
3. 枚举类型
4. 注解
5. 以上的数组
注意,不可以是 void !
2. 注解的使用
自定义了注解之后,开始使用注解:
public @interface MyAnno {
String show1();
String[] show2();
int age() default 18;
}
// 自定义注解中的 age 由于给了默认值,所以可以不写,写了话,以传入的值为准
@MyAnno(show1 = "show1",show2 = {"show2.1","show2.2"})
public int add(int a, int b) {
return a + b;
}
属性赋值注意点:
1. 如果定义的属性,使用 default 关键字给默认属性初始值,可以在使用注解时不赋值
2. 如果只有一个属性需要赋值,而该属性的名称是 value ,那么赋值的时候value可以省略
3. 数组赋值的时候,值使用{}包裹,如果数组中只有一个值,那么{}可以省略
3. 元注解
在看代码中的注解时,都会发现,它们的定义上面有另外3-4 个注解,这几个注解有什么作用呢?它们是 JDK 中给我们提供的 4 个元注解,含义分别是:
@Target : 描述当前注解能够作用的位置
ElementType.TYPE : 可以作用在类上
ElementType.METHOD: 可以作用在方法上
ElementType.FIELD: 可以作用在成员变量上
@Retention : 描述注解被保留到的阶段
SOURCE < CLASS < RUNTIME
SOURCE : 表示当前注解只在代码阶段有效
CLASS : 表示该注解会被保留到字节码阶段
RUNTIME : 表示该注解会被保留到运行阶段 JVM
通常,自定义的注解都会使用 RetentionPolicy.RUNTIME
@Documented : 描述注解是否被抽取到 JavaDoc api 中
@inherited : 描述注解是否可以被子类继承
4. 自定义注解使用案例
假设我们定义一个注解,它的作用就是根据传入的类名和类中方法,去执行对应的类方法,那么可以这样实现:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface InvokeAnno {
/**
* 需要执行的类名
* @return
*/
String className();
/**
* 需要执行的类方法
* @return
*/
String classMethod();
}
然后,再写两个测试类:
public class Worker1 {
public void workHome() {
System.out.println("work1执行 workHome 方法...");
}
}
public class Worker2 {
public void workHome() {
System.out.println("work2执行 workHome 方法...");
}
}
在写一个使用注解的测试类:
@InvokeAnno(className = "com.custom.anno.Worker1",classMethod = "workHome")
public class MyAnnoTest {
public static void main(String[] args) throws Exception {
// 获取类对象
Class<MyAnnoTest> clazz = MyAnnoTest.class;
// 获取类中指定注解
InvokeAnno annotation = clazz.getAnnotation(InvokeAnno.class);
// 获取注解中的 className 属性
String className = annotation.className();
// 获取注解中的 classMethod 属性
String classMethod = annotation.classMethod();
// 通过反射,获取对应的类对象
Class<?> clazz1 = Class.forName(className);
// 获取类对象中指定的方法
Method method = clazz1.getDeclaredMethod(classMethod);
// 执行指定类中的指定方法
method.invoke(clazz1.newInstance());
}
}
可以看到,利用反射和注解完成了我们需求。通常情况下,注解和反射都是在一起使用的。
希望通过阅读本文,能够帮助小伙伴加深对 Java 注解的理解。