提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
前言
注解其实就是一种标记,本质上是一种接口
一、Java 预置的注解
- @Deprecated
这个注解是用来标记过时的元素。首先定义一个 Hero 类,为其中的一个方法加上此注解:
public class Hero {
@Deprecated
public void say(){
System.out.println("什么都没说");
}
public void run(){
System.out.println("正在跑");
}
}
测试一下,会发现编译器中的代码如下:
但是运行结果依旧可以运行。不耽误使用。
- @Override
表示这个方法重写或需要重写。
二、定义一个注解(元注解)
1、定义一个注解
如何定义一个注解,只需要在接口关键字之前加上‘@’:
public @interface TextAnnoation {
}
使用注解时,只需要在测试类中加上 ‘@TextAnnoation’ 即可。
所谓元注解,就是可以注解到注解上的注解,或者说元注解是一种基本注解,但是它能够应用到其它的注解上面。通俗理解,元注解是一张特殊的标签,它的作用和目的就是给其它普通标签进行解释说明的。
元标签有 @Retention、@Documented、@Target、@Inherited
2、元注解
@Retention
Retention 的英文是保留期的意思。当 @Retention 应用到一个注解上的时候,它解释说明了这个注解的存活时间。它的取值如下:
取值 | 描述 | 作用范围 | 使用场景 |
---|---|---|---|
RetentionPolicy.SOURCE | 表示注解只保留在源文件,当java文件编译成class文件,就会消失 | 源文件 | 只是做一些检查性的操作,比如 @Override 和 @SuppressWarnings |
RetentionPolicy.CLASS | 注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期 | class文件(默认) | 要在编译时进行一些预处理操作,比如生成一些辅助代码(如 ButterKnife) |
RetentionPolicy.RUNTIME | 注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在 | 运行时也存在 | 需要在运行时去动态获取注解信息 |
使用形式为(以 RUNTIME 为例):
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface TextAnnoation {
}
此代码中,我们指定 TestAnnotation 可以在程序运行周期被获取到,因此它的生命周期非常长。
@Documented
顾名思义,这个元注解肯定是和文档有关。它的作用是将注解中的元素包含到 Javadoc 中去。
@Target
Target 是目标的意思,@Target 指定了注解运用的地方。它取值如下:
取值 | 作用目标 |
---|---|
@Target(ElementType.TYPE) | 接口、类、枚举、注解 |
@Target(ElementType.FIELD) | 字段、枚举的常量 |
@Target(ElementType.METHOD) | 方法 |
@Target(ElementType.PARAMETER) | 方法参数 |
@Target(ElementType.CONSTRUCTOR) | 构造函数 |
@Target(ElementType.LOCAL_VARIABLE) | 局部变量 |
@Target(ElementType.ANNOTATION_TYPE) | 注解 |
@Target(ElementType.PACKAGE) | 包 |
举个栗子:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.TYPE)
public @interface TextAnnoation {
}
@Inherited
Inherited 是继承的意思,但是它并不是说注解本身可以继承,而是说如果一个超类被 @Inherited 注解过的注解进行注解的话,你们它的子类没有被注解应用的话,那么这个子类就继承了超类的注解。说白了就是子类可以继承父类中的该注解。
三、注解的属性
注解既然本质上是接口,那么注解里面自然就是抽象方法,也被叫做属性。
定义一个注解:
import java.lang.annotation.*;
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.TYPE)
@Inherited
public @interface TextAnnoation {
int id() default -1;//默认值为-1
String massage() default "hello";//默认值为“hello”
}
测试类中使用该注解:
@TextAnnoation(id = 2,massage = "hello0")//如果给默认值在这个地方就不需要赋值了
public class Hero {
@Deprecated
public void say(){
System.out.println("什么都没说");
}
public void run(){
System.out.println("正在跑");
}
}
四、注解的提取
把注解比作为标签,前面的内容将怎么写注解,然后贴到什么地方去,而现在我们要做的工作就是检阅这些标签内容。形象的比喻就是你把这些注解标签在合适的时候撕下来,然后检阅上面的信息。
想要正确检阅注解,离不开反射。
1、类上注解的提取
举个栗子:
@TextAnnoation
public class Hero {
public static void main(String[] args) {
//判断 Hero 类上是够有 TextAnnoation 注解
Boolean isHas = Hero.class.isAnnotationPresent(TextAnnoation.class);
if(isHas){
//获取到 TextAnnoation 注解
TextAnnoation ann = Hero.class.getAnnotation(TextAnnoation.class);
System.out.println(ann.id());
System.out.println(ann.massage());
}
}
}
运行结果为:
注解里这么写的,所以会输出注解里面定义的默认属性:
public @interface TextAnnoation {
int id() default -1;//默认值为-1
String massage() default "hello";//默认值为“hello”
}
2、属性上注解的提取
注解是这么写的:
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
public @interface TextAnnoation {
int id() default -1;//默认值为-1
String massage() default "hello";//默认值为“hello”
}
测试类:
import java.lang.reflect.Field;
public class Hero {
@TextAnnoation(value = "hello")
int a;
public static void main(String[] args) throws NoSuchFieldException {
Field f = Hero.class.getDeclaredField("a");//获取到属性a
f.setAccessible(true);
TextAnnoation c = f.getAnnotation(TextAnnoation.class);
System.out.println(c.value());
}
}
运行结果:
3、方法上注解的提取
注解是这么写的:
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
public @interface TextAnnoation {
}
测试类:
public class Hero {
@TextAnnoation
public void text1(){
System.out.println("text1方法");
}
public static void main(String[] args) throws NoSuchMethodException {
Method m = Hero.class.getDeclaredMethod("text1");
m.setAccessible(true);
Annotation[] anns = m.getAnnotations();
for(int i = 0;i < anns.length;i++){
System.out.println(anns[i].annotationType().getSimpleName());
}
}
}
运行结果:
五、注解案例(举个栗子)
首先让我们写个注解:
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface Check {
}
然后我们写一个计算器类,里面有三个方法,但是其中有一个方法在调用时会出现异常。
public class Calculater {
@Check
public void add(){
System.out.println("add");
}
@Check
public void sub(){
int t = 2/0;
System.out.println("sub");
}
@Check
public void times(){
System.out.println("times");
}
}
然后编写一下测试类:
public class Text {
public static void main(String[] args) {
Calculater cal = new Calculater();
Class c = cal.getClass();//获取到Calculater的字节码对象
Method[] m = c.getMethods();//获取Calculater的所有方法
int tmp = 0;// 记录出错的次数
StringBuilder bu = new StringBuilder();//记录出错的信息
for (Method method : m) {
if(method.isAnnotationPresent(Check.class)){
try{
method.invoke(cal,null);
}catch(Exception e){
tmp++;
bu.append("出错的方法是:" + method.getName() + "\n错误是:" + e.getCause().getMessage());
}
}
}
System.out.println("出错了" + tmp + "次");
System.out.println(bu.toString());
}
}
运行结果为:
@Check 注解本身里面没有任何东西,但是通过测试类可以为添加了 @Check 的方法检查是否出错。
总结
Java注解作用分类:
- 编写文档:通过代码里标识的元数据生成文档【生成文档 doc 文档】
- 代码 分析:通过代码里标识的元数据对代码进行分析【使用反射】
- 编译检查:通过代码里标识的元数据让编译器能够实现基本的编译检查【Override】