浅谈 Java 中的反射机制

文章目录
  1. 1. 简介反射
  2. 2. 关于 Class
  3. 3. 应用实例
  4. 4. 参考文献

火跳动着绝望/谁在低声吟唱/说遗忘者的哀伤 /用战斗证明希望

说起 Java 中的反射机制,初学者或者 Java 基础不扎实的人或许会有点懵,甚至唯恐避之不及。事实上,Java 的反射机制很重要,许多开发框架都是基于反射来实现对目标对象的操作,反射配合注解,用好用活,能大大提升代码的效率和性能,并且,如今 Android 中一些高效的注解库也与此反射机制有着极大的关联,这期就来探究并总结下。

简介反射

说及反射,先说说内省。

内省

指计算机程序在运行时 (Run time) 检查对象 (Object) 类型的一种能力,也称为运行时类型检查。

反射

指计算机程序在运行时 (Run time) 可以访问、检测和修改它本身状态或行为的一种能力。用比喻来说,程序能够”观察“并且修改自己的行为。

一些编程语言比如 Java 具有反射特性,而 C++ 不具有反射特性只具有内省特性。

以上来源于维基百科。

就 Java 中的反射,其可以让我们在运行时获取类的方法、属性、父类和接口等 Class 内部的信息。同时,其还可以让我们在运行期实例化对象,调用方法,如通过调用 get/set 方法获取变量的值,甚至属性或方法是私有的也可以。其中,“看透 Class” 的能力即为内省了。反射多用于以下场景:

场景一:也是使用比较多的场景,要使用的类在运行时才会确定,这时不能在编译期使用,只能通过反射的形式来使用在运行时才存在的类(符合某种特定规范的类,如 JDBC 即 Java 数据库连接)。

场景二:比较常见的场景,编译时对类的内部信息不知道,必须到运行时才能获得类的具体信息(如 ORM 即对象关系映射框架,在运行时才能获取类中的各个属性,再通过反射的形式获取其属性名和值,存入数据库)。

关于 Class

上文所指的 Class,结合示意图如下所示:

当一个 Java 项目被编写完,所有的 Java 文件会被编译成一个 .class 文件,这些 Class 对象承载了这个类型的父类、接口、构造函数、方法和属性等原始信息,随后,这些 .class 文件在程序运行时会被 ClassLoader 加载到虚拟机中。当一个类被加载后,Java 虚拟机就会在内存中自动产生一个 Class 对象。事实上,通过 new 的形式创建对象就是通过这些 Class 来创建,只是过程对外界不透明。

应用实例

先给出以下代码:

Person.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Person {

String mName;

public Person(String name) {
this.mName = name;
}

private void sayHello(String friendName) {
System.out.println(mName + " say hello to " + friendName);
}

protected void showMyName() {
System.out.println("My name is " + mName);
}

public void breath() {
System.out.println("take breath");
}
}

Student.java

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
public class Student extends Person implements Examination {

int mGrade; // 年级

public Student(String name) {
super(name);
}

public Student(int grade, String name) {
super(name);
this.mGrade = grade;
}

private void study(String course) {
System.out.println(mName + " study " + course);
}

public void takeAnExamination() {
System.out.println("takeAnExamination");
}

public String toString() {
return "Student: " + mName;
}
}

Breath.java

1
2
3
4
// 呼吸接口
public interface Breath {
public void breath();
}

Examination.java

1
2
3
4
// 考试接口
public interface Examination {
public void takeAnExamination();
}

反射 Class 以及构造对象

I. 获取 Class 对象

想检查一个类的信息之前,首先需要获取类的 Class 对象。Java 中,所有类型,即使是数组,都有与之关联的 Class 类的对象。若在编译期知道一个类的名字,则可以使用如下方法获取一个类的 Class 对象:

1
Class<?> myObjectClass = MyObject.class;

若得到了某个对象,但是想获取该对象的 Class 对象,示例如:

1
2
Student me = new Student("Iamasoldier6");
Class<?> clazz = me.getClass();

若在编译期获取不到目标类型,但是知其完整类路径,则可以通过如下的形式来获取 Class 对象:

1
Class<?> myObjectClass = Class.forName("com.iamasoldier6.Student");

以上在使用 Class.forName() 方法时,必须提供一个类的全名,包括类所在包的名字,如 Student 类位于 com.iamasoldier6 包,则其完整类路径为 com.iamasoldier6.Student。另外,调用 Class.forName() 方法时,未在编译路径 (classpath)下找到对应的类,则会抛出 ClassNotFoundException。

接口说明

1
2
3
4
5
6
// 加载指定的 Class 对象,参数为要加载类的完整路径,如 "com.iamasoldier6.Student"
public static Class<?> forName(String className);

// 加载指定的 Class 对象,参数一为要加载类的完整路径,如 "com.iamasoldier6.Student",参数二为是否要初
// 始化该 Class 对象,参数三为指定加载该类的 ClassLoader
public static Class<?> forName(String className, boolean shouldInitialize, ClassLoader classLoader);

II. 通过 Class 对象构造目标类型的对象

拿到 Class 对象后,通过 Class 对象构造出该类型的对象,然后通过该对象实现其功能。在 Java 中,要构造对象,须通过该类的构造函数,反射也如此。但是也有区别,通过反射构造对象,首先要获取类的 Constructor (构造器)对象,再通过 Constructor 创建目标类的对象,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private static void classForName() {

try {
// 获取 Class 对象
Class<?> clazz = Class.forName("com.iamasoldier6.Student");
// 通过 Class 对象获取 Constructor,Student 的构造函数有一个字符串 String 参数,
// 故这里需要传递参数的类型
Constructor<?> constructor = clazz.getConstructor(String.class);
// 通过 Constructor 来创建 Student 对象
Object obj = constructor.newInstance("Iamasoldier6");
System.out.println("obj: " + obj.toString());
} catch (Exception e) {
e.printStackTrace();
}
}

以上方式使得我们可以在运行时通过完整的类名来构建对象。

接口说明

1
2
3
4
5
6
// 获取一个公有的构造函数,参数为可变参数,若构造函数有参数,则需要将参数的类型传递给 getConstructor()
// 方法
public Constructor<T> getConstructor(Class...<?> parameterTypes);

// 获取目标类所有的公有构造函数
public Constructor[]<?> getConstructors();

当通过反射获取到 Constructor、Method、Field 后,在反射调用前将该对象的 accessible 标志设置为 true,以此来提升反射速度:值为 true,则指示反射的对象在使用时取消 Java 语言的访问检查;值为 false,则指示反射的对象在使用时实施 Java 语言的访问检查。示例如下:

1
2
3
4
5
6
// 设置 Constructor 的 Accessible
Constructor<?> constructor = clazz.getConstructor(String.class);
constructor.setAccessible(true);

// 设置 Method 的 Accessible
Method studyMethod = Student.class.getMethod("study", String.class);

反射获取类中的方法

I. 获取当前类中定义的方法

通过 Class 中的 getDeclaredMethods() 方法获取当前类中定义的所有方法,即获取到当前类中的 public、default、protected、private 的所有方法。其中,getDeclaredMethod(String name, Class…<?> parameterTypes) 是获取某个指定的方法。代码示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private static void showDeclaredMethods() {

Student student = new Student("Iamasoldier6");
Method[] methods = student.getClass().getDeclaredMethods();
for (Method method : methods) {
System.out.println("Declared method name: " + method.getName());
}

try {
Method studyMethod = student.getClass().getDeclaredMethod("study", String.class);
// 获取方法的参数类型列表
Class<?>[] paramClasses = studyMethod.getParameterTypes();
for (Class<?> class1 : paramClasses) {
System.out.println("Study 方法的参数类型:" + class1.getName());
}
// 是否是 private 方法
System.out.println(studyMethod.getName() + " is private "
+ Modifier.isPrivate(studyMethod.getModifiers()));
studyMethod.invoke(student, "java");
} catch (Exception e) {
e.printStackTrace();
}
}

II. 获取当前类以及父类中定义的公有方法

通过 Class 中的 getMethods() 方法获取当前类以及父类中定义的公有方法。其中,getMethod() 是获取某个指定的方法。代码示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private static void showMethods() {

Student student = new Student("Iamasoldier6");
Method[] methods = student.getClass().getMethods();
for (Method method : methods) {
System.out.println("Method name: " + method.getName());
}

try {
// 通过 getMethod() 只能获取公有方法,若获取私有方法则抛出异常,比如这里会抛异常
Method studyMethod = student.getClass().getMethod("study", String.class);
// 是否是 private
System.out.println(studyMethod.getName() + " is private "
+ Modifier.isPrivate(studyMethod.getModifiers()));
studyMethod.invoke(student, "java");
} catch (Exception e) {
e.printStackTrace();
}
}

接口说明

1
2
3
4
5
6
7
8
9
10
11
// 获取指定的 Class 对象中指定方法名和参数,参数一为方法名,参数二为参数类型列表
public Method getDeclaredMethod(String name, Class...<?> parameterTypes);

// 获取该 Class 对象中所有的方法(不包含从父类继承的方法)
public Method[] getDeclaredMethods();

// 获取指定的 Class 对象中的公有方法,参数一为方法名,参数二为参数类型列表
public Method getMethod(String name, Class...<?> parameterTypes);

// 获取该 Class 对象中所有的公有方法(包含从父类和接口类继承或实现的方法)
public Method[] getMethods();

getDeclaredMethod() 和 getDeclaredMethods() 包含 private、protected、default、public 的方法,通过这两个方法获取到的只是在自身中定义的方法,从父类中继承的方法无法获取。

getMethod() 和 getMethods() 只包含 public 方法,父类中的公有方法可以获取。

反射获取类中的属性

获取属性与上文中的获取方法是很相似的,这里,getField() 类比之前的 getMethod(),getDeclaredField() 类比之前的 getDeclaredMethod()。

I. 获取当前类中定义的属性

通过 Class 中的 getDeclaredFields() 方法获取当前类中定义的所有属性,会获取到当前类中 public、default、protected、private 的所有属性,getDeclaredField() 方法则是获取某个指定的属性。代码示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private static void showDeclaredFields() {

Student student = new Student("Iamasoldier6");
// 获取当前类和父类的所有公有属性
Field[] publicFields = student.getClass().getDeclaredFields();
for (Field field : publicFields) {
System.out.println("Declared field name: " + field.getName());
}

try {
// 获取当前类和父类的某个公有属性
Field gradeField = student.getClass().getDeclaredField("mGrade");
// 获取属性值
System.out.println("My grade is: " + gradeField.getInt(student));
// 设置属性值
gradeField.set(student, 10);
System.out.println("My grade is: " + gradeField.getInt(student));
} catch (Exception e) {
e.printStackTrace();
}
}

II. 获取当前类以及父类中定义的公有属性

通过 Class 中的 getFields() 方法获取当前类以及父类中所有的公有属性。其中,getField() 是获取某个指定的属性。代码示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private static void showFields() {

Student student = new Student("Iamasoldier6");
// 获取当前类和父类的所有公有属性
Field[] publicFields = student.getClass().getFields();
for (Field field : publicFields) {
System.out.println("Field name: " + field.getName());
}

try {
// 获取当前类的和父类的某个公有属性
Field ageField = student.getClass().getField("mAge");
System.out.println("Age is: " + ageField.getInt(student));
} catch (Exception e) {
e.printStackTrace();
}
}

接口说明

1
2
3
4
5
6
7
8
9
10
11
// 获取 Class 对象中指定属性名的属性,参数为属性名
public Method getDeclaredField(String name);

// 获取该 Class 对象中所有的属性(不包含从父类继承的属性)
public Method][] getDeclaredFields();

// 获取指定 Class 对象中的公有属性,参数为属性名
public Method getField(String name);

// 获取该 Class 对象中的所有公有属性(包含从父类和接口类继承或实现的公有属性)
public Method[] getFields();

getDeclaredField() 和 getDeclaredFields() 包含 private、protected、default 和 public 的属性,并且,通过这两个函数获取到的只是在自身中定义的属性,从父类中继承的属性无法获取到。

getField() 和 getFields() 只包含 public 属性,父类中的公有属性可以获取到。

反射获取父类与接口

I. 获取父类

获取 Class 对象的父类,代码示例如下:

1
2
3
4
5
6
7
8
9
10
private static void showSuperClass() {

Student student = new Student("Iamasoldier6");
Class<?> superClass = student.getClass().getSuperClass();
while (superClass != null) {
System.out.println("Student's super class is: " + superClass.getName());
// 再获取父类的上一层父类,直到最后的 Object 类,Object 的父类为 null
superClass = superClass.getSuperClass();
}
}

II. 获取接口

获取 Class 对象中实现的接口,代码示例如下:

1
2
3
4
5
6
7
8
private static void showInterfaces() {

Student student = new Student("Iamasoldier6");
Class<?>[] interfaces = student.getClass().getInterfaces();
for (Class<?> class1 : interfaces) {
System.out.println("Student's interface is: " + class1.getName());
}
}

反射获取注解信息

框架开发中,注解加反射的组合使用最为常见,定义注解时会通过 @Target 指定该注解能够作用的类型,示例如下:

1
2
3
4
5
6
7
8
@Target({
ElementType.METHOD, ElementType.FIELD, ElementType.TYPE
})

@Retention(RetentionPolicy.RUNTIME)
static @interface Test {

}

以上注解的 @Target 表示该注解只能用在函数上。

事实上,通过反射能获取一个 Class 对象,以获取类型、属性和方法等相关的对象,通过这些对象的 getAnnotation 接口获取到对应的注解信息。

首先,在目标对象上添加注解,代码如下:

1
2
3
4
5
6
7
8
9
@Test(tag = "Student class Test Annotation")
public class Student extends Person implements Examination {

// 年级
@Test(tag = "mGrade Test Annotation")
int mGrade;

// ...
}

再通过相关的注解方法得到注解信息,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private static void getAnnotationInfos() {

Student student = new Student("Iamasoldier6");
Test classTest = student.getClass().getAnnotation(Test.class);
System.out.println("Class annotation tag = " + classTest.tag());

Field field = null;
try {
field = student.getClass().getDeclaredField("mGrade");
Test testAnnotation = field.getAnnotation(Test.class);
System.out.println("Test 注解 tag: " + testAnnotation.tag());
} catch (Exception e) {
e.printStackTrace();
}
}

输出结果:

1
2
Class annotation tag = Student class Test Annotation
Test 注解 tag: mGrade Test Annotation

接口说明

1
2
3
4
// 获取指定类型的注解
public <A extends Annotation> A getAnnotation(Class<A> annotationClass);
// 获取 Class 对象中的所有注解
public Annotation[] getAnnotations();

至此,关于 Java 中的反射机制探究并总结完毕。

本人才疏学浅,如有疏漏错误之处,望读者中有识之士不吝赐教,谢谢。

1
Email: [email protected] / WeChat: Wolverine623

您也可以关注我个人的微信公众号码农六哥第一时间获得博客的更新通知,或后台留言与我交流

参考文献

1.http://www.cnblogs.com/rollenholt/archive/2011/09/02/2163758.html

2.http://t.cn/Rf1q5Ej