什么是反射

Java 反射机制的核心是在程序运行时动态加载类并获取类的详细信息,从而操作类或对象的属性和方法。本质是 JVM 得到 class 对象之后,再通过 class 对象进行反编译,从而获取对象的各种信息。

反射是框架设计的灵魂

Java 属于先编译再运行的语言,程序中对象的类型在编译期就确定下来了,而当程序在运行时可能需要动态加载某些类,这些类因为之前用不到,所以没有被加载到 JVM。通过反射,可以在运行时动态地创建对象并调用其属性,不需要提前在编译期知道运行的对象是谁。

反射就是把 Java 类中的各个组成部分进行解剖,并映射成一个个的 Java 对象,拿到这些对象后可以做一些事情

既然说反射是解剖 Java 类中的各个组成部分,所以说咱们得知道一个类中有哪些部分?

例如,一个类有:构造方法,方法,成员变量等信息,利用反射技术咱们可以把这些组成部分映射成一个个对象

拿到映射后的构造方法,可以用它来生成对象;拿到映射后的方法,可以调用它来执行对应的方法;拿到映射后的字段,可以用它来获取或改变对应字段的值;

什么是 Class 类

在面向对象的世界里,万事万物皆是对象。

在 java 语言中,static 修饰的东西不是对象,但是它属于类。

普通的数据类型不是对象,例如:int a = 5;它不是面向对象,但是它有其包装类 Integer 或者分装类来弥补了它。

除了以上两种不是面向对象,其余的,包括类也有它的面向对象,类是 java.lang.Class 的实例化对象。也就是说: Class A{}

当我创建了 A 类,那么类 A 本身就是一个对象,java.lang.Class 的实例对象。

那么这个对象又该怎么表示呢?

我们先看一下下面这段代码:

1
2
3
4
5
6
public class Demo() {
F a = new A();
}

class A {
}

这里的 A 的实例化对象就可以用 a 表达出来。

同理 A 类也是一个实例化对象:java.lang.Class 类的实例化对象。

获取 Class 对象的四种方式

  • 类型名.class

    • 要求编译期间已知类型
  • 对象.getClass()

    • 获取对象的运行时类型
  • Class.forName(“”全限定类名”)

    • 可以获取编译期间未知的类型
  • ClassLoader 的类加载器对象.loadClass(类型全名称)

    • 可以用系统类加载对象或自定义加载器对象加载指定路径下的类型
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
package com.shiguang;

public class Demo {
public static void main(String[] args) {

A f = new A();

// 第一种表达方式
// 这种表达方式同时也告诉了我们任何一个类都有一个隐含的静态成员变量class
Class c1 = A.class;
// class com.shiguang.A
System.out.println(c1);

//第二种表达方式
//这种表达方式在已知了该类的对象的情况下通过getClass方法获取
Class c2 = f.getClass();
// class com.shiguang.A
System.out.println(c2);

//第三种表达方式
Class c3 = null;
try {
//类的全称
c3 = Class.forName("com.shiguang.A");
// class com.shiguang.A
System.out.println(c3);
} catch (ClassNotFoundException classNotFoundException) {
classNotFoundException.printStackTrace();
}

//第四种方式
//首先获取ClassLoader的类加载器对象
ClassLoader classLoader = c1.getClassLoader();
try {
Class c4 = classLoader.loadClass("com.shiguang.A");
// class com.shiguang.A
System.out.println(c4);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}


class A {
}

方法的反射

我们知道一个类里一般有构造函数、方法、成员变量(字段/属性)这三部分组成

首先尝试获取一个类中所有的方法

在 java 里面,万事万物都是对象,方法也是对象,方法是 Method 类的对象,一个成员方法就是一个 Method 的对象,那么 Method 就封装了对这个成员

如果我们要获得所有的方法,可以用 getMethods()方法

这个方法返回的是表示此类中公共(Public)方法的 Method 对象的数组,包括父类继承而来的

如果我们要获取所有该类自己声明的方法,就可以用 getDeclaredMethods()方法,这个方法是不问访问权限的

尝试写一个工具类

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
public class ClassUtils {
public static void printClassMethodMessage(Object obj) {
// 1、要获取类的信息 -> 首先我们要获取类的类类型
Class c = obj.getClass();
// 我们知道Object类是一切类的父类,所以我们传递的是哪个子类的对象,c就是该子类的类类型。
// 接下来我们要获取类的名称
System.out.println("类的名称是:" + c.getName());

// 2、获取所有公共方法
Method[] ms = c.getMethods();

// 拿到这些方法之后就可以获取这些方法的信息,比如方法的名字。
// 3、首先我们要循环遍历 Method 对象的数组
for (int i = 0; i < ms.length; i++) {
//然后可以得到方法的返回值类型的类类型
Class returnType = ms[i].getReturnType();
//得到方法的返回值类型的名字
System.out.print("返回值类型的名字:" + returnType.getName() + " ");
//得到方法的名称
System.out.print("方法的名称:" + ms[i].getName() + "(");
//获取参数类型--->得到的是参数列表的类型的类类型
Class[] paramTypes = ms[i].getParameterTypes();
for (Class class1 : paramTypes) {
System.out.print("参数类型:" + class1.getName() + ",");
}
System.out.println(")");
System.out.println("-----------------------------");
}
}
}

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Demo {

public static void main(String[] args) {
A a = new A();
ClassUtils.printClassMethodMessage(a);
}
}

class A {
public Double getMoney(Double money, Integer age) {
return null;
}
}

结果

类的名称是:A
返回值类型的名字:java.lang.Double 方法的名称:getMoney(参数类型:java.lang.Double,参数类型:java.lang.Integer,)

成员变量的反射

1
2
3
4
5
6
7
8
9
10
11
12
public class ClassUtils {
public static void printClassMethodMessage(Object obj) {
// 要获取类的信息 -> 首先我们要获取类的类类型
Class c = obj.getClass();

// 得到Field对象的数组
Field[] fs = c.getDeclaredFields();
for (Field f : fs) {
System.out.println(f);
}
}
}

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Demo {

public static void main(String[] args) {
A a = new A();
ClassUtils.printClassMethodMessage(a);
}
}

class A {
private String name;
private Integer age;
private Double money;
}

结果

private java.lang.String A.name
private java.lang.Integer A.age
private java.lang.Double A.money

构造函数的反射

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class ClassUtils {
public static void printClassMethodMessage(Object obj) {
// 要获取类的信息 -> 首先我们要获取类的类类型
Class c = obj.getClass();

// 获取 Constructor 对象的一个数组
Constructor[] cs = c.getDeclaredConstructors();

// 遍历
for (Constructor constructor : cs) {
//构造方法是没有返回值类型的,但是我们可以:
System.out.print(constructor.getName() + "(");
//获取构造函数的参数列表 ---> 得到的是参数列表的类类型
Class[] paramTypes = constructor.getParameterTypes();
for (Class class1 : paramTypes) {
System.out.print(class1.getName() + ",");
}
System.out.println(")");
}
}
}

测试

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
public class Demo {

public static void main(String[] args) {
A a = new A();
ClassUtils.printClassMethodMessage(a);
}
}

class A {
private String name;
private Integer age;
private Double money;

public A() {
}

public A(String name) {
this.name = name;
}

public A(String name, Integer age, Double money) {
this.name = name;
this.age = age;
this.money = money;
}
}

结果

A()
A(java.lang.String,)
A(java.lang.String,java.lang.Integer,java.lang.Double,)

Class 类的动态加载类

如何动态加载一个类呢?

首先我们需要区分什么是动态加载?什么是静态加载?

我们普遍认为编译时刻加载的类是静态加载类,运行时刻加载的类是动态加载类

new 创建对象是静态加载类(newInstance 是动态加载类),在编译时刻就需要加载所有可能用到的类,而所以不管你用不用这个类。此时如果有一个类找不到,那么编译就无法通过

我们想要的就是我需要用哪个类就加载那个类,也就是常说的:运行时刻加载,动态加载类

写一个需要动态加载的类

1
2
3
4
5
6
7
8
9
10
11
package com.shiguang;

public class Demo {

/**
* 动态加载
*/
public void start() {
System.out.println("Demo start");
}
}

写一个测试类,测试动态加载

1
2
3
4
5
6
7
8
9
10
11
12
public class DemoTest {
public static void main(String[] args) {
try {
// 动态加载类,在运行时加载
Class<?> c = Class.forName("com.shiguang.Demo");
Demo demo = (Demo) c.newInstance();
demo.start();
} catch (Exception e) {
e.printStackTrace();
}
}
}

结果:

Demo start