首先引入官方的一句话:Java注解用于为Java代码提供元数据。作为元数据,注解不直接影响你的代码执行,但也有一些类型的注解实际上可以用于这一目的。也许你对这句话不是很理解,从下面的说明中我们就能逐渐理解这句话的含义。
一、注解的定义
Java注解是从Java5开始引入的,它是类的一种类型,修饰符是@interface。
二、元注解
在写一个注解之前我们要对元注解的概念进行理解。元注解,顾名思义,就是最基础的注解,自定义的一些注解都要依赖于这些元注解。常用的元注解有以下五种:
(1)@Retention
Retention中文有保留、保持的意思,它用于声明注解保留存在于编译期(源码)、类加载(字节码)还是运行期(JVM解释运行过程),在@Retention注解中使用枚举RetentionPolicy来表示注解保留时期:
保留时期 | 说明 |
---|---|
@Retention(RetentionPolicy.SOURCE) | 注解仅存在于源码中,在class字节码文件中不包含 |
@Retention(RetentionPolicy.CLASS) | 默认的保留策略,注解会在class字节码文件中存在,但运行时无法获得 |
@Retention(RetentionPolicy.RUNTIME) | 注解会在class字节码文件中存在,在运行时可以通过反射获取到 |
说明:如果我们使用到了自定的注解,如果只存在于源码或字节码文件中,那么在运行时就无法使用,也就无法发挥作用,所以自定义注解一般使用@Retention(RetentionPolicy.RUNTIME)保留策略。
(2)@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) | 作用于包 |
@Target(ElementType.TYPE_PARAMETER) | 作用于类型泛型,即泛型方法、泛型类、泛型接口 (jdk1.8加入) |
@Target(ElementType.TYPE_USE) | 类型使用,可以用于标注任意类型除了 class (jdk1.8加入) |
(3)@Inherited
Inherited的中文意思是继承,一个被@Inherited注解标注的注解修饰的父类,如果它的子类没有被其它注解所修饰,则它的子类也会继承该父类的该注解。
(4)@Documented
Documented中文意思是文档。它的作用是将注解中的元素包含到Javadoc中去。
(5)@Repeatable
Repeatable中文意思是可重复的,被这个元注解修饰的注解可以作用于一个对象多次,可用于多次元数据赋值操作。
三、注解属性
注解中属性可以有以下类型:
- 基本数据类型
- String类型
- 枚举类型
- 注解类型
- Class类型
- 以上类型的一维数组类型
四、使用示例
1)编写一个注解People.java,定义了三个属性。
package com.lpl.anno;
import java.lang.annotation.*;
/**
* 人类注解
*/
@Documented //将注解中的元素包含到Javadoc中
@Inherited //该注解修饰的一个父类,如果它的子类没有被其它注解修饰,则它的子类也继承了该父类的该注解
@Retention(RetentionPolicy.RUNTIME) //该注解会在运行时存在(存在于class字节码文件中)
@Target(ElementType.TYPE) //作用于接口、类、枚举、注解上
public @interface People {
String name() default "人类"; //注解属性,姓名
int age() default 0; //注解属性,年龄
Game[] value() ; //玩的游戏,定义为游戏注解类型数组
}
其中,属性value用到了Game注解数组,编写Game.java注解,
package com.lpl.anno;
import java.lang.annotation.*;
/**
* 游戏注解
*/
@Repeatable(People.class) //@Game注解可重复标注,声明了此注解的目标注解必须要指定value属性
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Game {
String value() default ""; //游戏名称
String name() default ""; //游戏名称
}
2)编写一个父类Father.java,使用注解People修饰,并指定其属性值。然后获取定义的属性值打印输出。
package com.lpl.anno;
/**
* 父类,标注了自定义注解People
*/
@People(value = {@Game(value = "王者荣耀"), @Game(value = "部落冲突")},
name = "父亲", age = 50) //自定义注解提供元数据
public class Father {
public static void main(String[] args) {
//获取Father的Class对象
Class<Father> fatherClass = Father.class;
//获取指定类型的public自定义注解
People peopleAnnotation = fatherClass.getAnnotation(People.class);
//获取注解属性
String name = peopleAnnotation.name();
int age = peopleAnnotation.age();
System.out.println("姓名:" + name + ",年龄:" + age + ",喜欢的游戏:");
//获取注解数组类型的注解属性
Game[] games = peopleAnnotation.value();
for (int i=0; i<games.length; i++){
String gameName = games[i].value();
System.out.print(gameName + " ");
}
}
}
3)编写子类Son.java,继承自父类Father,因为父类的自定义注解声明了元注解@Inherited,所以也会继承父类的该注解,此时子类拿到的是父类注解声明的属性值。当然子类也可自己声明自定义注解People并指定属性值。
package com.lpl.anno;
/**
* 子类,继承自父类Father,未标注任何注解,所以可继承父类的自定义注解People(自定义注解People标注
* 了元注解@Inherited)
*
* 如果此处未声明注解,使用的就是父类上声明的自定义注解及注解中元数据。
* 如果此处未声明注解元数据,使用的是自定义注解默认元数据
*/
@People(value = {@Game(value = "王者荣耀"), @Game(value = "LOL")}, name = "儿子", age = 23)
public class Son extends Father {
/**
* 子类未声明注解的情况下,也可以获取父类自定义注解(前提是父类自定义注解有@Inherited元注解)
*/
public static void main(String[] args) {
//获取Son的Class对象
Class<Son> sonClass = Son.class;
//获取继承自父类指定类型的public自定义注解
People peopleAnnotation = sonClass.getAnnotation(People.class);
//获取注解属性
String name = peopleAnnotation.name();
int age = peopleAnnotation.age();
System.out.println("姓名:" + name + ",年龄:" + age + ",喜欢的游戏:");
//获取注解数组类型的注解属性
Game[] games = peopleAnnotation.value();
for (int i=0; i<games.length; i++){
String gameName = games[i].value();
System.out.print(gameName + " ");
}
}
}
五、JDK提供的注解
注解 | 作用 |
---|---|
@Override | 它是用来描述当前方法是一个重写的方法,在编译阶段对方法进行检查 |
@Deprecated | 它是用于描述当前方法是一个过时的方法 |
@SuppressWarnings | 去除程序中的警告 |
六、应用示例
下面我们看一个银行转账的例子,银行转账限额会根据汇率变化,我们可以通过注解灵活配置转账限额,而不必每次都去修改业务代码。
1)编写转账限额注解TransferMoneyLimit.java,属性是转账限额大小。
package com.lpl.anno;
import java.lang.annotation.*;
/**
* 定义转账限额注解
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface TransferMoneyLimit {
double maxMoney() default 10000; //转账限额,默认值为10000
}
2)编写转账业务类TransferService.java,使用注解配置转账限额。
package com.lpl.anno;
import java.lang.reflect.Method;
/**
* 转账业务类
*/
public class TransferService {
/**
* 转账金额限制
*/
@TransferMoneyLimit(maxMoney = 12000) //指定转账限额为12000
public static void transferMoney(double money){
String processResult = processTransferMoney(money);
System.out.println(processResult);
}
/**
* 处理转账金额
*/
private static String processTransferMoney(double money) {
try{
//1.获取当前业务类的Class对象
Class<TransferService> transferServiceClass = TransferService.class;
//2.获取转账方法对象
Method transferMoneyMethod = transferServiceClass.getDeclaredMethod("transferMoney", double.class);
//3.判断方法上是否有指定类型注解
boolean annotationPresent = transferMoneyMethod.isAnnotationPresent(TransferMoneyLimit.class);
if (annotationPresent){
//4.获取注解对象
TransferMoneyLimit transferMoneyLimitAnnotation = transferMoneyMethod.getAnnotation(TransferMoneyLimit.class);
//5.获取注解属性数据
double maxMoney = transferMoneyLimitAnnotation.maxMoney();
if (money > maxMoney){ //如果大于转账限额
return "转账金额大于限额,转账失败!";
}else{
return "转账金额为:" + money + ",转账成功!";
}
}
}catch (Exception e){
e.printStackTrace();
}
return "转账发生异常!";
}
public static void main(String[] args) {
transferMoney(15000);
}
}
参考:Java 注解完全解析