什么是注解?
Java 注解(Annotation)又称 Java 标注,是 JDK5.0 引入的一种注释机制。 比如我们常见的@Override和@Deprecated都是注解,注解可以加在类、方法、成员变量等上面,类似于给他们“打标签"。
注解怎么定义?
public @interface 注解名{} 看起来和定义接口很相似只是多了一个@符号 接口: public interface 接口名 注解: public @interface 注解名
public @interface lkx {
}
注解怎么使用?
现在我们注解已经定义好了,使用的时候直接“@注解名”就可以使用了 比如下面我们可以定义在“类、成员变量、成员方法”上:
@lkx
public class Test {
@lkx
private int num;
@lkx
public static void main(String[] args) {
System.out.println("hello");
}
}
现在注解定义好也已经使用了,但是我不想定义到类上和成员方法上,只想定义在成员方法上,如何才能让注解只能定义到方法上,定义到别的地方报错呢? 想要的效果:
这个时候就要使用元注解来限定范围了。
元注解
元注解通俗的来说就是定义在注解上的注解,在Java中有四个元注解 @Target @Retention @Documented @Inherited
@Target
@Target就是用于描述注解的定义范围,可以限制这个注解定义的元素类型。
参数 | 作用 |
---|---|
ElementType.ANNOTATION_TYPE | 可以应用于注解类型 |
ElementType.CONSTRUCTOR | 可以应用于构造函数 |
ElementType.FIELD | 可以应用于字段或属性 |
ElementType.LOCAL_VARIABLE | 可以应用于局部变量 |
ElementType.METHOD | 可以应用于方法级注解 |
ElementType.PACKAGE | 可以应用于包声明 |
ElementType.PARAMETER | 可以应用于方法的参数 |
ElementType.TYPE | 可以应用于类的任何元素 |
因为我们要限制只想定义在成员变量上,所以我们应该使用ElementType.FIELD
@Target(ElementType.FIELD)
public @interface lkx {
}
但是如果我们想同时定义在成员变量和成员方法上应该怎么办呢? 多个参数只需要用大括号包起来,逗号隔开就可以了
@Target({ElementType.FIELD,ElementType.METHOD})
public @interface lkx {
}
现在就只有定义在类上的注解报错了
@Retention
@Retention是用于定义注解的生命周期,也可以理解为存储方式。
参数 | 作用 |
---|---|
RetentionPolicy.SOURCE | 标记的注解仅保留在源级别中,并被编译器忽略 |
RetentionPolicy.CLASS | 标记的注解在编译时由编译器保留,但 Java 虚拟机(JVM)会忽略 |
RetentionPolicy.RUNTIME | 标记的注解由 JVM 保留,因此运行时环境可以使用它 |
下面两个元注解使用不多,暂时不做详解
@Documented
@Documented是用于描述生成帮助文档时是否要保留其注解信息。
@Inherited
@Inherited是用于描述使被它修饰的注解是否具有继承性。
注解元素
上面我们只是定义了一个注解,但是不能传任何信息,只是相当于一个标签,现在我们看看要怎么给注解定义参数:
@Target({ElementType.FIELD,ElementType.METHOD})
@Retention(RetentionPolicy.SOURCE)
public @interface lkx {
String name() default "张三"; //可以使用default定义默认的值
int age();
}
现在我们看一下如何传参:
public class Test {
//name有默认值,也可以不写
@lkx(name = "李四",age = 18)
private int num;
public static void main(String[] args) {
System.out.println("hello");
}
}
实战
现在注解也定义好了,参数也传入了,你是否还在想注解有什么卵用。。。现在我们有个小需求,把注解传入的参数赋值到成员变量上。 例如:
@lkx(name = "李四",age = 18)
private int num; //num没有被赋值,等于0
赋值完成后
num = 18
注意事项:
- 下面代码需要用到反射,如果还不会的小伙伴可以看我前面几篇文章。
- 因为要用反射拿到注解参数,所以@Retention需要定义为RetentionPolicy.RUNTIME
实现代码:
@Target({ElementType.FIELD,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface lkx {
String name() default "张三";
int age();
}
java复制代码public class Test {
//name有默认值,也可以不写
@lkx(name = "李四",age = 18)
private static int num;
@lkx(name = "王五",age = 38)
private static int age;
public static void main(String[] args) throws InstantiationException, IllegalAccessException {
System.out.println("赋值前: num: " + num+" age: "+age);
//拿到类的字节码
Class<Test> testClass = Test.class;
//拿到所有成员变量
for (Field declaredField : testClass.getDeclaredFields()) {
//检测成员变量上是否有@lkx注解
if (declaredField.isAnnotationPresent(lkx.class)) {
lkx annotation = declaredField.getAnnotation(lkx.class);
//获取到注解中的age的值
int age = annotation.age();
declaredField.set(testClass.newInstance(),age);
}
}
System.out.println("赋值后: num: " + num+" age: "+age);
}
}
运行结果:
java复制代码赋值前: num: 0 age: 0
赋值后: num: 18 age: 38