0%

注解与反射

注解与注释其实非常的相似,只不过注释是用来让开发人员知道某段代码的作用是什么都有哪些参数等,而注解则是写给程序看的一个“注释”,注解中主要包含了它本身的名称,以及它的参数,单纯地看一个注解其实与注释没啥区别。但注解往往会与反射组合使用,反射可以让我们在程序运行时观察并操作某个类中的信息,当然包括注解,这在实现一些框架的过程中有很大作用(比如实现实体类到数据库表的自动映射)。

注解(Annotation)

元注解

元注解的作用是注解其他的注解,Java定义了4个标准的meta-annotation类型,他们被用来提供对其他annotation类型的说明。

存在包java.lang.annotation中

四个标准元注解:

注解注解的注解,也就是注解的注解,只能用在注解上。

  • @Target 用于描述注解的使用范围(如方法、类、字段上等)
  • @Retention 表示在什么级别保存该注释信息,用于描述注解的生命周期
    • SOURCE < CLASS < RUNTIME
  • @Document 说明该注解将被包含在javadoc中
  • @Inherited 说明子类可以继承父类中的该注解

@Target

示例:

1
2
3
@Target(ElementType.METHOD)	// 示例在这
public @interface myAnnotation {
}

其中参数ElementType的可选值有:

TYPE FIELD METHOD PARAMETER CONSTRUCTOR LOCAL_VARIABLE ANNOTATION_TYPE PACKAGE TYPE_PARAMETER TYPE_USE

@Retention

表示注解在什么范围有效,如源码级别、编译后的class级别、运行时级别

示例:

1
2
3
4
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME) // 示例在这
public @interface MyAnnotation {
}

其中参数的可选值有:

SOURCE CLASS RUNTIME

参数的关系为包含关系,RUNTIME > CLASS > SOURCE

自定义注解

自定义多参数的注解示例:

1
2
3
4
5
6
7
8
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
// @interface 自动继承java.lang.annotation.Annotation接口
public @interface MyAnnotation {
// 自定义注解的参数:注解的类型 参数名称();
String name() default "defaultName"; // 参数可以设定默认值,否则使用该注解是时就必须写参数
String school();
}

自定义只带一个参数的注解示例:

如果之定义一个参数最好将参数命名为value(),因为命名为value在使用注解时可以省略value =

1
2
3
4
5
6
7
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
// @interface 自动继承java.lang.annotation.Annotation接口
public @interface MyAnnotation {
// 自定义注解的参数:注解的类型 参数名称();
String[] value();
}

反射(Reflection)

反射式编程(英语:reflective programming)或反射(英语:reflection),是指计算机程序在运行时(runtime)可以访问、检测和修改它本身状态或行为的一种能力。用比喻来说,反射就是程序在运行的时候能够“观察”并且修改自己的行为。——维基百科

理解反射

我对反射的一个比较形象的理解是,java中的反射与物理中光的反射也有一些相似的地方。在现实生活中,我们能够通过镜子看到某个物体反射后的像,而java中的反射就是以类加载过程中会生成的Class对象作为“镜子”,通过这面镜子,我们能在程序运行时观察并操作某个类的内部结构。因此学习反射绕不开对Class类的学习。(个人的理解,可能表达并不准确)

反射机制提供的功能

  • 在运行时判断任意一个对象所属的类型
  • 在运行时构造任意一个类的对象
  • 在运行时判断任意一个类所具有的成员变量和方法
  • 在运行时获取泛型信息
  • 在运行时调用任意一个对象的成员变量和方法
  • 在运行时处理注解
  • 生成动态代理
  • ……

理解java中的Class类

Class类

  • Class本身也是一个类
  • Class类只能由系统创建对象
  • 一个加载的类在JVM中有且仅有一个Class实例
  • 一个Class对象对应的是一个加载到JVM中的一个.class文件
  • 每个类的实例都会知道自己是由哪个Class实例所生成
  • 通过Class可以完全地得到一个类中的所有被加载的结构
  • Class类是Reflection的根源,针对任何你想动态加载、运行的类,唯有先获得相应的Class对象

获取Class对象的几种方式

这里我们写了两个类Person与Student,其中Student继承Person,本文章中所有的Person类与Student类都是以下列出的代码。

Person类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Person {
public String name;

public Person() {
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}
}

Student类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import com.experiment.annotation.MyAnnotation;
import com.experiment.annotation.MyAnnotation02;

@MyAnnotation(name = "MakerHu", school = "BJTU")
public class Student extends Person{

@MyAnnotation02("studentCardId")
private String studentId;

public Student() {
}

public Student(String name) {
this.setName(name);
}

public String getStudentId() {
return studentId;
}

public void setStudentId(String studentId) {
this.studentId = studentId;
}
}

通过代码可以更好地理解如何获取Class对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.experiment.reflection.getclass;

public class Main {
public static void main(String[] args) throws ClassNotFoundException {
Person student1 = new Student("学生");
Person student2 = new Student("学生");

// 获取Class对象的几种方法
// 方法一:通过对象获得
Class c1_1 = student1.getClass();
Class c1_2 = student2.getClass();

// 方法二:通过Class.forName获得(需要抛出异常)
Class c2 = Class.forName("com.experiment.reflection.getclass.Student");

// 方法三:通过类名.Class获得(效率最高)
Class c3 = Student.class;

// 方式四:基本内置类型的包装类有一个Type属性
Class c4 = Integer.TYPE;

// 获得父类类型
Class c5 = c1_1.getSuperclass();

System.out.println("c1_1: " + c1_1.hashCode());
System.out.println("c1_2: " + c1_2.hashCode());
System.out.println("c2: " + c2.hashCode());
System.out.println("c3: " + c3.hashCode());
System.out.println("c4: " + c4);
System.out.println("c5: " + c5);
}
}

以上代码的输出为:

1
2
3
4
5
6
c1_1: 460141958
c1_2: 460141958
c2: 460141958
c3: 460141958
c4: int
c5: class com.experiment.reflection.getclass.Person

从c1到c3的输出我们可以发现,不管Class对象是如何获得的,只要其为某个类的Class对象,其在内存中就只有一个,也就是一个类拥有唯一的一个Class对象

通过Class对象获取类的信息

不说废话,直接上代码,详见注释。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package com.experiment.reflection.getClassMsg;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

// 获得类的信息
public class GetClassMsg {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException {
Class c1 = Class.forName("com.experiment.reflection.getclass.Student");

// 获得类的名字
System.out.println(c1.getName()); // 获得包名+类名
System.out.println(c1.getSimpleName()); // 获得类名

// 获得类的属性
Field[] fields = c1.getFields(); // getFields()只能获取到本类或父类中的publics属性
fields = c1.getDeclaredFields(); // getDeclaredFields()可以获取到本类所有属性
Field name = c1.getField("name"); // 获取到本类或父类中的指定名称的public属性
Field studentId = c1.getDeclaredField("studentId"); // 获取到本类的指定名称的属性,private属性也可获取到

// 获得类中的方法
Method[] methods = c1.getMethods(); // 获得本类及其父类的所有public方法
methods = c1.getDeclaredMethods(); // 获得本类的所有方法

// 获得指定的方法
Method setName = c1.getMethod("setName",String.class); // 通过方法名和参数类型获取本类或父类的指定public方法,为的是防止有重载时找不到特定方法
Method setStudentId = c1.getDeclaredMethod("setStudentId",String.class); //获取本类的所有指定方法

// 获取构造器
Constructor[] constructors = c1.getConstructors(); // 获取本类所有public构造器
Constructor[] constructors1 = c1.getDeclaredConstructors(); // 获取本类的所有构造器
Constructor constructor = c1.getConstructor(String.class); //获得本类的指定参数的public构造器
Constructor constructor1 = c1.getDeclaredConstructor(null); //获得本类的指定参数的构造器
}
}

动态操作对象

同样,看代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.experiment.reflection.operateObject;

import com.experiment.reflection.getclass.Student;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

// 动态操作对象
public class OperateObject {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException, NoSuchFieldException {
// 获得Class对象
Class c1 = Class.forName("com.experiment.reflection.getclass.Student");

// 构造一个对象
Student student = (Student)c1.newInstance(); //利用无参构造器构造

Constructor constructor = c1.getDeclaredConstructor(String.class);
Student student1 = (Student) constructor.newInstance("Makerhu"); // 利用有参构造器构造

// 通过反射调用方法
Method setStudentId = c1.getDeclaredMethod("setStudentId", String.class);
setStudentId.invoke(student1,"123425");

// 通过反射操作属性
Field studentId = c1.getDeclaredField("studentId");
studentId.setAccessible(true); // 私有属性设置访问权限为true后就可以修改
studentId.set(student1, "9876543");
}
}

获取泛型信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import com.experiment.reflection.getclass.Student;

import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;

// 反射获取泛型信息
public class OperateGenericType {
public void test01(Map<String, Student> map, List<Student> list, String str){
System.out.println("test01");
}

public Map<String, Student> test02(){
System.out.println("test02");
return null;
}

public static void main(String[] args) throws NoSuchMethodException {
Method method = OperateGenericType.class.getMethod("test01",Map.class,List.class,String.class);

// 获取参数类型
System.out.println("获取参数类型");
Type[] genericParameterTypes = method.getGenericParameterTypes();

for (Type genericParameterType : genericParameterTypes) {
System.out.println("\n# " + genericParameterType);
if (genericParameterType instanceof ParameterizedType){
System.out.println("=================");
Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println(actualTypeArgument);
}
}
}

// 获取返回值类型
System.out.println("\n获取返回值类型");
method = OperateGenericType.class.getMethod("test02");
Type genericReturnType = method.getGenericReturnType();

if(genericReturnType instanceof ParameterizedType){
Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println(actualTypeArgument);
}
}
}
}

输出结果为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
获取参数类型

# java.util.Map<java.lang.String, com.experiment.reflection.getclass.Student>
=================
class java.lang.String
class com.experiment.reflection.getclass.Student

# java.util.List<com.experiment.reflection.getclass.Student>
=================
class com.experiment.reflection.getclass.Student

# class java.lang.String

获取返回值类型
class java.lang.String
class com.experiment.reflection.getclass.Student

获取注解信息

先看看自定义的两个注解MyAnnotation与MyAnnotation02,这两个注解在Student类中被使用,具体看前面的Student类的代码。

注解1:MyAnnotation

1
2
3
4
5
6
7
8
9
10
11
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String name() default "defaultName";
String school();
}

注解2:MyAnnotation02

1
2
3
4
5
6
7
8
9
10
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation02 {
String value();
}

通过反射获取注解信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import com.experiment.annotation.MyAnnotation;
import com.experiment.annotation.MyAnnotation02;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;

// 通过反射获取注解
public class OperateAnnotation {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
Class c1 = Class.forName("com.experiment.reflection.getclass.Student");

// 通过反射获得某个类的类注解
Annotation[] annotations = c1.getAnnotations();

// 获取某个特定的注解
// 方法一
MyAnnotation annotation = (MyAnnotation) c1.getAnnotation(MyAnnotation.class);
// 方法二
Field field = c1.getDeclaredField("studentId");
MyAnnotation02 annotation02 = field.getAnnotation(MyAnnotation02.class);

// 获得注解的参数的值
System.out.println(annotation.name());
System.out.println(annotation.school());
System.out.println(annotation02.value());

}
}

笔记整理于:【狂神说Java】注解和反射_哔哩哔哩_bilibili