本文目录
1、Java注解概述
1.1 注解简介
Annontation是Java5开始引入的新特征,中文名称叫注解。它提供了一种安全的类似注释的机制,用来将任何的信息或元数据(metadata)与程序元素(类、方法、成员变量等)进行关联。为程序的元素(类、方法、成员变量)加上更直观更明了的说明,这些说明信息是与程序的业务逻辑无关,并且供指定的工具或框架使用。Annontation像一种修饰符一样,应用于包、类型、构造方法、方法、成员变量、参数及本地变量的声明语句中。
Java注解是附加在代码中的一些元信息,用于一些工具在编译、运行时进行解析和使用,起到说明、配置的功能。注解不会也不能影响代码的实际逻辑,仅仅起到辅助性的作用。包含在 java.lang.annotation 包中。
1.2 注解的作用
(1)生成文档。这是最常见的,也是java 最早提供的注解。常用的有@param @return 等;
(2)跟踪代码依赖性,实现替代配置文件功能。比如Dagger 2依赖注入,未来java开发,将大量注解配置,具有很大用处;
(3)在编译时进行格式检查。如@override 放在方法前,如果你这个方法并不是覆盖了超类方法,则编译时就能检查出。
2、Java注解的原理
注解本质是一个继承了Annotation的特殊接口,其具体实现类是Java运行时生成的动态代理类。而我们通过反射获取注解时,返回的是Java运行时生成的动态代理对象$Proxy1。通过代理对象调用自定义注解(接口)的方法,会最终调用AnnotationInvocationHandler的invoke方法。该方法会从memberValues这个Map中索引出对应的值。而memberValues的来源是Java常量池。
2.1 元注解
java.lang.annotation提供了四种元注解,专门注解其他的注解(在自定义注解的时候,需要使用到元注解):
@Documented –注解是否将包含在JavaDoc中
@Retention –什么时候使用该注解
@Target –注解用于什么地方
@Inherited – 是否允许子类继承该注解
(1)@Retention– 定义该注解的生命周期
- RetentionPolicy.SOURCE : 在编译阶段丢弃。这些注解在编译结束之后就不再有任何意义,所以它们不会写入字节码。@Override, @SuppressWarnings都属于这类注解。
- RetentionPolicy.CLASS : 在类加载的时候丢弃。在字节码文件的处理中有用。注解默认使用这种方式
- RetentionPolicy.RUNTIME : 始终不会丢弃,运行期也保留该注解,因此可以使用反射机制读取该注解的信息。我们自定义的注解通常使用这种方式。Target – 表示该注解用于什么地方。默认值为任何元素,表示该注解用于什么地方。可用的ElementType参数包括
(2)Target – 表示该注解用于什么地方。默认值为任何元素,表示该注解用于什么地方。可用的ElementType参数包括:
● ElementType.CONSTRUCTOR:用于描述构造器
● ElementType.FIELD:成员变量、对象、属性(包括enum实例)
● ElementType.LOCAL_VARIABLE:用于描述局部变量
● ElementType.METHOD:用于描述方法
● ElementType.PACKAGE:用于描述包
● ElementType.PARAMETER:用于描述参数
● ElementType.TYPE:用于描述类、接口(包括注解类型) 或enum声明
(3)@Documented–一个简单的Annotations标记注解,表示是否将注解信息添加在java文档中。
(4)@Inherited – 定义该注释和子类的关系 @Inherited 元注解是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。
2.2 JDK里的注解
(1)@Override 这个注解是为了检查此方法是否真的是重写父类的方法。这时候就不用我们用肉眼去观察到底是不是重写了。
(2)@Deprecated 此注解代表过时了,但是如果可以调用到,当然也可以正常使用;但是,此方法有可能在以后的版本升级中会被慢慢的淘汰,可以放在类,变量,方法上面都起作用。
(3)@SuppressWarnings 因为定义的 name 没有使用,那么编译器就会有警告,这时候使用此注解可以屏蔽掉警告;即任意不想看到的编译时期的警告都可以用此注解屏蔽掉,但是不推荐,有警告的代码最好还是处理一下。
(4)@FunctionalInterface 此注解是Java8 提出的函数式接口,接口中只允许有一个抽象方法。加上这个注解之后,类中多一个抽象方法或者少一个抽象方法都会报错。
3、注解实例
(1)注解处理器
注解处理器才是使用注解整个流程中最重要的一步了。所有在代码中出现的注解,它到底起了什么作用,都是在注解处理器中定义好的。
概念:注解本身并不会对程序的编译方式产生影响,而是注解处理器起的作用;注解处理器能够通过在运行时使用反射获取在程序代码中的使用的注解信息,从而实现一些额外功能。前提是我们自定义的注解使用的是 RetentionPolicy.RUNTIME 修饰的。这也是我们在开发中使用频率很高的一种方式。
如何通过在运行时使用反射获取在程序中的使用的注解信息。如下类注解和方法注解。
//类注解
Class aClass = ApiController.class;
Annotation[] annotations = aClass.getAnnotations();
for(Annotation annotation : annotations) {
if(annotation instanceof ApiAuthAnnotation) {
ApiAuthAnnotation apiAuthAnnotation = (ApiAuthAnnotation) annotation;
System.out.println("name: " + apiAuthAnnotation.name());
System.out.println("age: " + apiAuthAnnotation.age());
}
}
//方法注解
Method method = ... //通过反射获取方法对象
Annotation[] annotations = method.getDeclaredAnnotations();
for(Annotation annotation : annotations) {
if(annotation instanceof ApiAuthAnnotation) {
ApiAuthAnnotation apiAuthAnnotation = (ApiAuthAnnotation) annotation;
System.out.println("name: " + apiAuthAnnotation.name());
System.out.println("age: " + apiAuthAnnotation.age());
}
}
4、不同类型的注解
4.1 类注解
你可以在运行期访问类,方法或者变量的注解信息,下是一个访问类注解的例子:
Class aClass = TheClass.class;
Annotation[] annotations = aClass.getAnnotations();
for(Annotation annotation : annotations){
if(annotation instanceof MyAnnotation){
MyAnnotation myAnnotation = (MyAnnotation) annotation;
System.out.println("name: " + myAnnotation.name());
System.out.println("value: " + myAnnotation.value());
}
}
你还可以像下面这样指定访问一个类的注解:
Class aClass = TheClass.class;
Annotation annotation = aClass.getAnnotation(MyAnnotation.class);
if(annotation instanceof MyAnnotation){
MyAnnotation myAnnotation = (MyAnnotation) annotation;
System.out.println("name: " + myAnnotation.name());
System.out.println("value: " + myAnnotation.value());
}
4.2 方法注解
下面是一个方法注解的例子:
public class TheClass {
@MyAnnotation(name="someName", value = "Hello World")
public void doSomething(){}
}
你可以像这样访问方法注解:
Method method = ... //获取方法对象
Annotation[] annotations = method.getDeclaredAnnotations();
for(Annotation annotation : annotations){
if(annotation instanceof MyAnnotation){
MyAnnotation myAnnotation = (MyAnnotation) annotation;
System.out.println("name: " + myAnnotation.name());
System.out.println("value: " + myAnnotation.value());
}
}
你可以像这样访问指定的方法注解:
Method method = ... // 获取方法对象
Annotation annotation = method.getAnnotation(MyAnnotation.class);
if(annotation instanceof MyAnnotation){
MyAnnotation myAnnotation = (MyAnnotation) annotation;
System.out.println("name: " + myAnnotation.name());
System.out.println("value: " + myAnnotation.value());
}
4.3 参数注解
方法参数也可以添加注解,就像下面这样:
public class TheClass {
public static void doSomethingElse(
@MyAnnotation(name="aName", value="aValue") String parameter){
}
}
你可以通过 Method对象来访问方法参数注解:
Method method = ... //获取方法对象
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
Class[] parameterTypes = method.getParameterTypes();
int i=0;
for(Annotation[] annotations : parameterAnnotations){
Class parameterType = parameterTypes[i++];
for(Annotation annotation : annotations){
if(annotation instanceof MyAnnotation){
MyAnnotation myAnnotation = (MyAnnotation) annotation;
System.out.println("param: " + parameterType.getName());
System.out.println("name : " + myAnnotation.name());
System.out.println("value: " + myAnnotation.value());
}
}
}
需要注意的是 Method.getParameterAnnotations()方法返回一个注解类型的二维数组,每一个方法的参数包含一个注解数组。
4.4 变量注解
下面是一个变量注解的例子:
public class TheClass {
@MyAnnotation(name="someName", value = "Hello World")
public String myField = null;
}
你可以像这样来访问变量的注解:
Field field = ... //获取方法对象</pre>
<pre>Annotation[] annotations = field.getDeclaredAnnotations();
for(Annotation annotation : annotations){
if(annotation instanceof MyAnnotation){
MyAnnotation myAnnotation = (MyAnnotation) annotation;
System.out.println("name: " + myAnnotation.name());
System.out.println("value: " + myAnnotation.value());
}
}
你可以像这样访问指定的变量注解:
Field field = ...//获取方法对象</pre>
<pre>
Annotation annotation = field.getAnnotation(MyAnnotation.class);
if(annotation instanceof MyAnnotation){
MyAnnotation myAnnotation = (MyAnnotation) annotation;
System.out.println("name: " + myAnnotation.name());
System.out.println("value: " + myAnnotation.value());
}
5、Java注解相关题目
(1)什么是注解?他们的典型用例是什么?
注解是绑定到程序源代码元素的元数据,对运行代码的操作没有影响。
他们的典型用例是:
-
编译器的信息 - 使用注解,编译器可以检测错误或抑制警告
-
编译时和部署时处理 - 软件工具可以处理注解并生成代码,配置文件等。
-
运行时处理 - 可以在运行时检查注解以自定义程序的行为
(2)描述标准库中一些有用的注解。
java.lang和java.lang.annotation包中有几个注解,更常见的包括但不限于此:
-
@Override -标记方法是否覆盖超类中声明的元素。如果它无法正确覆盖该方法,编译器将发出错误
-
@Deprecated - 表示该元素已弃用且不应使用。如果程序使用标有此批注的方法,类或字段,编译器将发出警告
-
@SuppressWarnings - 告诉编译器禁止特定警告。在与泛型出现之前编写的遗留代码接口时最常用的
-
@FunctionalInterface - 在Java 8中引入,表明类型声明是一个功能接口,可以使用Lambda Expression提供其实现
(3)可以从注解方法声明返回哪些对象类型?
返回类型必须是基本类型,String,Class,Enum或数组类型之一。否则,编译器将抛出错误。
(4)哪些程序元素可以注解?
注解可以应用于整个源代码的多个位置。它们可以应用于类,构造函数和字段的声明,方法及其参数,局部变量,包括循环和资源变量,甚至包,类型的使用,类实例创建,类型转换,接口,异常抛出上。
(5)有没有办法限制可以应用注解的元素?
有,@ Target注解可用于此目的。如果我们尝试在不适用的上下文中使用注解,编译器将发出错误。
(6)什么是元注解?
元注解适用于其他注解的注解。
所有未使用@Target标记或使用它标记但包含ANNOTATION_TYPE常量的注解也是元注解:
(7)下面的代码会编译吗?
@Target({ ElementType.FIELD, ElementType.TYPE, ElementType.FIELD })
public @interface TestAnnotation {
int[] value() default {};
}
不能。如果在@Target注解中多次出现相同的枚举常量,那么这是一个编译时错误。
删除重复常量将使代码成功编译:
@Target({ ElementType.FIELD, ElementType.TYPE})
附言:
本文整理来源于网络、博客等资源,仅做个人学习笔记复习所用。
如果对你学习有用,请点赞共同学习!
如有侵权,请联系我删!