文章目录
- 注解定义
- 作用分类
- API 文档注解
- JDK 预定义的注解
- 自定义注解
- 注解的格式
- 注解的本质
- 注解的属性
- 属性的返回值类型
- 属性的特点
- 属性的赋值
- 元注解
- @Target
- @Retention
- @Documented
- @Inherited
- 解析注解
- 总结
注解定义
注解( Annotation),也叫元数据。一种代码级别的说明。它是 JDK1.5 及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。
注解的叫法很多:元数据、标签、标记…
使用注解的叫法很多:使用xxx注解标注、使用xxx注解标记、使用xxx注解描述…
作用分类
①编写文档:通过代码里标识的元数据生成文档
编写程序时以一套特定的标签(即注解)作注释,在程序编写完成后,通过 Javadoc 就可以同时形成程序的开发文档了。
②代码分析:通过代码里标识的元数据对代码进行分析
使用反射技术,获取注解属性的值,然后处理有关的业务逻辑
③编译检査:通过代码里标识的元数据让编译器能够实现基本的编译检查
例如:@Override 注解,就可以检测被注解的方法是否正确覆盖重写父类的方法。
API 文档注解
演示代码:
package priv.lwx.javaprac.annotation;/*** 生成文档(javadoc)的注解演示代码** @author liaowenxiong* @date 2021/9/16 下午3:58* @since JDK 1.5*/
public class Demo01Annotation {public static void main(String[] args) {}/*** 计算两个整数的和* @param a 整数* @param b 整数* @return 两个整数的和*/public int add(int a, int b) {return a + b;}
}
如上的演示代码,在注释中有很多的 @xxx
,这些就是文档注解,可以将这些注解的内容提取成为 API 文档。
关于如何编写文档注解,以及如何生成 javadoc 请参见《JDK 命令之 javadoc – 生成API文档》。
JDK 预定义的注解
@Override
用来检查被该注解标注的方法是不是有效的方法重写。在方法签名相同的情况下覆盖重写父类的方法,在其它地方如果有问题,会直接报编译错误,无需该注解来检测。只有方法签名不同的情况下,而又希望覆盖重写父类的方法,使用该注解检测才有意义。那么什么情况下会出现这个问题,那么就是父类方法的参数很多,确实容易写错,而你的本意又确实是重写父类的方法,那么此时使用此注解就可以帮到你了。
@Deprecated
用来表明被该注解标注的类成员已经过时,如果标注的是方法则会在方法名上显示一条“删除线”
@SuppressWarnings
抑制警告,禁止警告
注:一般传递参数“all”
@SuppressWarnings("all")
public void test() {show();
}
自定义注解
可以通过反编译来查看注解实际的代码。
例如,查看注解 @Deprecated
的实际代码,你需要先编译它的源代码,再反编译字节码文件才能看到。
@Deprecated
注解的源代码如下:
package java.lang;import java.lang.annotation.*;
import static java.lang.annotation.ElementType.*;/*** A program element annotated {@code @Deprecated} is one that programmers* are discouraged from using. An element may be deprecated for any of several* reasons, for example, its usage is likely to lead to errors; it may* be changed incompatibly or removed in a future version; it has been* superseded by a newer, usually preferable alternative; or it is obsolete.** <p>Compilers issue warnings when a deprecated program element is used or* overridden in non-deprecated code. Use of the {@code @Deprecated}* annotation on a local variable declaration or on a parameter declaration* or a package declaration has no effect on the warnings issued by a compiler.** <p>When a module is deprecated, the use of that module in {@code* requires}, but not in {@code exports} or {@code opens} clauses causes* a warning to be issued. A module being deprecated does <em>not</em> cause* warnings to be issued for uses of types within the module.** <p>This annotation type has a string-valued element {@code since}. The value* of this element indicates the version in which the annotated program element* was first deprecated.** <p>This annotation type has a boolean-valued element {@code forRemoval}.* A value of {@code true} indicates intent to remove the annotated program* element in a future version. A value of {@code false} indicates that use of* the annotated program element is discouraged, but at the time the program* element was annotated, there was no specific intent to remove it.** @apiNote* It is strongly recommended that the reason for deprecating a program element* be explained in the documentation, using the {@code @deprecated}* javadoc tag. The documentation should also suggest and link to a* recommended replacement API, if applicable. A replacement API often* has subtly different semantics, so such issues should be discussed as* well.** <p>It is recommended that a {@code since} value be provided with all newly* annotated program elements. Note that {@code since} cannot be mandatory,* as there are many existing annotations that lack this element value.** <p>There is no defined order among annotation elements. As a matter of* style, the {@code since} element should be placed first.** <p>The {@code @Deprecated} annotation should always be present if* the {@code @deprecated} javadoc tag is present, and vice-versa.** @author Neal Gafter* @since 1.5* @jls 9.6.4.6 @Deprecated*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, MODULE, PARAMETER, TYPE})
public @interface Deprecated {/*** Returns the version in which the annotated element became deprecated.* The version string is in the same format and namespace as the value of* the {@code @since} javadoc tag. The default value is the empty* string.** @return the version string* @since 9*/String since() default "";/*** Indicates whether the annotated element is subject to removal in a* future version. The default value is {@code false}.** @return whether the element is subject to removal* @since 9*/boolean forRemoval() default false;
}
把这份源代码文件复制到其它地方,使用命令编译和反编译,命令如下:
liaowenongdeair:test liaowenxiong$ javac Deprecated.java # 先编译源代码
liaowenongdeair:test liaowenxiong$ javap Deprecated.class # 反编译字节码文件
Compiled from "Deprecated.java"
public interface Deprecated extends java.lang.annotation.Annotation {public abstract java.lang.String since();public abstract boolean forRemoval();
}
liaowenongdeair:test liaowenxiong$
注解的格式
元注解
public @interface 注解名称 {属性列表(本质就是抽象方法)
}
注解的本质
注解本质就是接口,继承自父接口 Annotation
。
注解的属性
所谓“属性”就是注解接口体(即大括号 {}
)中声明的常量和方法。所以“属性”的本质就是抽象方法。声明了属性则使用注解时必须给属性赋值。
为什么将方法称之为属性,看下面的示例代码:
// 使用自定义的注解
@MyAnno1(name = "李瓶儿") // 其中name是注解声明的抽象方法名,使用注解时需要赋值,赋值语法格式类似属性赋值的格式,所以将注解中声明的抽象方法称为"属性"
public void test() {show();
}
属性的返回值类型
返回值类型:
1.基本数据类型
2.String
3.枚举
4.注解
5.以上类型的数组
除了以上五种,其它类型不能作为注解接口中声明的抽象方法的返回值类型。
属性的特点
1.属性的默认值:在声明注解的属性时,如果使用关键字 default 给属性默认值,则使用注解时可以不进行属性的赋值,会取默认值。
2.在使用注解时,如果只有一个属性,且属性名称为 value,那么在给该属性赋值时,可以省略属性名称,即本来要 这么写 @MyAnno1(value = "李瓶儿")
,可以省略成 @MyAnno1("李瓶儿")
。
3.注解类中声明的抽象方法名,返回值是字符串数组,那么给属性赋值时,如果多个值使用大括号包裹,如果只有一个值,则可以省略大括号。
声明定义注解及属性示例代码:
package priv.lwx.javaprac.annotation;/*** 自定义注解** @author liaowenxiong* @date 2021/9/20 下午5:18*/
public @interface MyAnno1 {String name(); // 这是抽象方法,在注解中可以称为属性,使用此注解时,需要赋值,赋值格式:name = 一个字符串int age() default 12; // 默认值12,在使用注解时,没有指定该属性,那么该属性的默认值就是12
}
属性的赋值
各种返回值类型的属性如何赋值,请看下面的示例代码:
@MyAnno1(name = "高圆圆", setColor = Color.C1, test = @MyAnno2, names = {"双儿", "小栗子"})
/*
name方法的返回值是字符串,所以赋值字符串;setColor方法的返回值是枚举类,所以赋值时取枚举值,类似类的静态常量test方法的返回值是注解类,所以赋值时格式为 @注解类名称names是注解类中声明的抽象方法名,返回值是字符串数组,那么给属性names赋值时,
如果多个值使用大括号包裹,如果只有一个值,则可以省略大括号。
*/
public void test() {show();
}
元注解
用于描述注解的注解。
@Target
描述注解可以作用的位置
@Target(value={ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.LOCAL_VARIABLE, ElementType.METHOD, ElementType.PACKAGE, ElementType.MODULE, ElementType.PARAMETER, ElementType.TYPE})
public @interface MyAnno3 {
}
如上示例,表示被描述的注解(MyAnno3
)可以作用于构造器、字段、局部变量、方法、包、模块、参数、类上。{}
内的都是枚举类 ElementType
的枚举值。
@Retention
描述注解被保留的阶段(源码阶段、编译阶段、运行时阶段)
有三个值:
SOURCE:被描述的注解仅在源码阶段保留,编译时就被舍弃了
CLASS:被描述的注解会保留到字节码文件中,类加载进内存时被舍弃了
RUNTIME:被描述的注解会保留到运行时阶段,即类加载进入内存时,注解也会被加载进内存,可以通过反射获取相关信息
@Documented
描述注解是否可以被 javadoc 抽取到文档中,即被描述的注解会原样出现在API文档中
@Inherited
描述注解是否被子类继承
解析注解
获取注解属性中定义的值。
本质:就是获取注解类的实例对象,然后调用注解属性对应的成员方法,获得对应的返回值
步骤:
1.获得被注解的类/方法/字段对应的反射对象,即类就是 Class 对象,方法就是 Method 对象,变量就是 Field 对象
2.通过反射对象获得注解的实例对象,即调用反射对象的 getAnnotation/getAnnotations 等方法获取注解对象
3.调用注解对象的方法,获得返回值,该返回值就是对应注解属性中定义的值
演示代码:
package priv.lwx.javaprac.annotation;import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;/*** 该类可以创建任意类型的对象,执行其中的任意方法.** 使用注解的方式取代属性文件的方式来获取类名和方法名.* @author liaowenxiong* @date 2021/9/21 上午7:49*/
@Pro(className = "priv.lwx.javaprac.annotation.Person", methodName = "eat")
public class Demo03Annotation { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {// 1.获取当前类的Class对象Class<Demo03Annotation> c = Demo03Annotation.class;// 2.获取当前类的注解类的实例对象// 其实就是在内存中生成了一个注解接口的实现类对象/*其实编译器会自动生成如下的代码:public class ProImpl implements Pro {public String className() {return "priv/lwx/javaprac/annotation/Demo03Annotation";}public String methodName() {return "eat";}}*/Pro pro = c.getAnnotation(Pro.class);// 3.调用注解对象上的抽象方法,获取返回值String className = pro.className(); // 返回值就是使用Pro注解时所定义的className属性值String methodName = pro.methodName(); // 返回值就是使用Pro注解时所定义的methodName属性值System.out.println(className);System.out.println(methodName);// 使用Class的静态方法forName将类加载进内存中Class c2 = Class.forName(className);// 获取无参构造器Constructor constructor = c2.getConstructor();// 通过无参构造器创建对象Object obj = constructor.newInstance();// 获取方法对象Method method = c2.getMethod(methodName);// 执行方法Object result = method.invoke(obj);System.out.println(result);}}
总结
在实际开发过程中,多数情况我们不会自定义注解,而是使用注解。
注解给谁用?
给解析程序用,编译器也属于解析程序,解析程序识别注解,然后实现有关的业务逻辑。
注解可以理解为程序中一种标签、标记