- 注解定义
- 作用分类
- 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.
*
* 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.
*
*
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 not cause
* warnings to be issued for uses of types within the module.
*
*
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.
*
*
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.
*
*
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.
*
*
There is no defined order among annotation elements. As a matter of
* style, the {@code since} element should be placed first.
*
*
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
的枚举值。
描述注解被保留的阶段(源码阶段、编译阶段、运行时阶段)
有三个值: 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 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);
}
}
总结
在实际开发过程中,多数情况我们不会自定义注解,而是使用注解。
注解给谁用? 给解析程序用,编译器也属于解析程序,解析程序识别注解,然后实现有关的业务逻辑。
注解可以理解为程序中一种标签、标记