什么是反射?

反射就是在运行时才知道要操作的类是什么,并且可以在运行时获取类的完整构造,并调用对应的方法。

一个简单的例子

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
public class Apple {
private int price;

public int getPrice() {
return price;
}

public void setPrice(int price) {
this.price = price;
}

public static void main(String[] args) throws Exception {
// 正常的调用
Apple apple = new Apple();
apple.setPrice(5);
System.out.println("Apple Price:" + apple.getPrice());

// 反射调用
Class<?> clazz = Class.forName("com.kanxz.reflect.Apple");
Method setPriceMethod = clazz.getMethod("setPrice", int.class);
Constructor<?> clazzConstructor = clazz.getConstructor();
Object appleObj = clazzConstructor.newInstance();
setPriceMethod.invoke(appleObj, 15);
Method getPrice = clazz.getMethod("getPrice");
System.out.println("Apple Price:" + getPrice.invoke(appleObj));
}
}

输出结果为:

Apple Price:5
Apple Price:15

从这个简单的例子可以看出,一般情况下我们使用反射获取一个对象的步骤:

1
2
3
4
5
6
7
8
// 获取类的 Class 对象实例
Class<?> clazz = Class.forName("com.kanxz.reflect.Apple");

// 根据 Class 对象实例获取 Constructor 对象
Constructor<?> clazzConstructor = clazz.getConstructor();

// 使用 Constructor 对象的 newInstance 方法获取反射类对象
Object appleObj = clazzConstructor.newInstance();

如果要调用某一个方法,则需要经过下面的步骤:

1
2
3
4
5
// 获取方法的 Method 对象
Method setPriceMethod = clazz.getMethod("setPrice", int.class);

// 利用 invoke 方法调用方法
setPriceMethod.invoke(appleObj, 15);

看完这个例子之后,来看看反射常用 API 的具体解释:

常用API

获取Class对象

使用 .class 方法

这种方法适用于在编译时已经知道具体的类。

1
Class clz = String.class;

使用 Class.forName静态方法

适用于运行时动态获取Class对象,只需将类名作为forName方法的参数:

1
Class clz = Class.forName("java.lang.String");

使用类对象的 getClass() 方法

1
2
String str = new String("Hello");
Class clz = str.getClass();

获取类名

1
2
3
Class<Apple> appleClass = Apple.class;
System.out.println(appleClass.getName());
System.out.println(appleClass.getSimpleName());

运行结果如下:

com.kanxz.reflect.Apple
Apple

getName()方法获取的类名包含包信息。getSimpleName()方法只是获取类名,不包含包信息。

获取类修饰符

1
2
System.out.println(appleClass.getModifiers()); // 1
System.out.println(Modifier.isPublic(appleClass.getModifiers())); // true

类修饰符有public、private等类型,getModifiers()可以获取一个类的修饰符,但是返回的结果是int,结合Modifier提供的方法,就可以确认修饰符的类型。

1
2
3
4
5
6
7
8
9
10
11
12
Modifier.isAbstract(int modifiers)
Modifier.isFinal(int modifiers)
Modifier.isInterface(int modifiers)
Modifier.isNative(int modifiers)
Modifier.isPrivate(int modifiers)
Modifier.isProtected(int modifiers)
Modifier.isPublic(int modifiers)
Modifier.isStatic(int modifiers)
Modifier.isStrict(int modifiers)
Modifier.isSynchronized(int modifiers)
Modifier.isTransient(int modifiers)
Modifier.isVolatile(int modifiers)

获取包信息

1
System.out.println(appleClass.getPackage()); // package com.kanxz.reflect

获取构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/*
public Apple(int price) {
this.price = price;
}

public Apple() {}
*/

Class<Apple> appleClass = Apple.class;
Constructor<?>[] constructors = appleClass.getConstructors();

/*
打印 constructors 结果:
public com.kanxz.reflect.Apple(int)
public com.kanxz.reflect.Apple()
*/

一个类会有多个构造函数,getConstructors()返回的是Constructor[]数组,包含了所有声明的用public修饰的构造函数。

如果你已经知道了某个构造的参数,可以通过下面的方法获取到回应的构造函数对象:

1
Constructor<Apple> constructor = appleClass.getConstructor(new Class[]{int.class});

上面获取构造函数的方式有2点需要注意:

  • 只能获取到public修饰的构造函数。

  • 需要捕获NoSuchMethodException异常。

初始化对象

通过反射获取到构造器之后,通过newInstance()方法就可以生成类对象。

1
2
Constructor<Apple> constructor = appleClass.getConstructor(new Class[]{int.class});
Apple apple = constructor.newInstance(33);

获取Methods方法信息

下面代码是通过反射可以获取到该类的声明的成员方法信息:

1
2
3
4
Method[] method = appleClass.getMethods();
Method[] declaredMethods = appleClass.getDeclaredMethods();
Method method1 = appleClass.getMethod("setPrice", int.class);
Method method2 = appleClass.getDeclaredMethod("getPrice");

无参的getMethods()获取到所有public修饰的方法,返回的是Method[]数组。
无参的getDeclaredMethods()方法到的是所有的成员方法,和修饰符无关。
对于有参的getMethods()方法,需提供要获取的方法名以及方法名的参数。

无参的getMethods()和getDeclaredMethods()都只能获取到类声明的成员方法,不能获取到继承父类的方法。

invoke()方法

java反射提供invoke()方法,在运行时根据业务需要调用相应的方法,这种情况在运行时非常常见,只要通过反射获取到方法名之后,就可以调用对应的方法:

1
2
3
4
5
6
7
8
9
10
Constructor<Apple> constructor = appleClass.getConstructor(int.class);
Apple apple = constructor.newInstance(33);

Method method = appleClass.getMethod("setPrice", int.class);
Method method2 = appleClass.getMethod("getPrice");

System.out.println(method2.invoke(apple)); // 33

method.invoke(apple, 50);
System.out.println(method2.invoke(apple)); // 50

invoke方法有两个参数,第一个参数是要调用方法的对象,上面的代码中就是apple的对象,第二个参数是调用方法要传入的参数。如果有多个参数,则用数组。

如果调用的是static方法,invoke()方法第一个参数就用null代替。

获取成员变量

通过反射可以在运行时获取到类的所有成员变量,还可以给成员变量赋值和获取成员变量的值。

1
2
3
4
Field[] fields1 = appleClass.getFields();
Field[] fields2 = appleClass.getDeclaredFields();
Field price1 = appleClass.getField("price"); // 报错
Field price2 = appleClass.getDeclaredField("price");

getFields()方法获取所有public修饰的成员变量,getField()方法需要传入变量名,并且变量必须是public修饰符修饰。

getDeclaredFields方法获取所有生命的成员变量,不管是public还是private。

成员变量赋值和取值

一旦获取到成员变量的Field引用,就可以获取通过get()方法获取变量值,通过set()方法给变量赋值:

1
2
3
4
5
6
7
8
Field price = appleClass.getDeclaredField("price");

Object value = price.get(apple);
System.out.println(value); // 50

price.set(apple, 10);
value = price.get(apple);
System.out.println(value); // 10

上面是在同一个类中测试的结果,但是在不同的类中访问其他类的私有成员变量需要加上price.setAccessible(true);,访问私有方法也同样需要加上setAccessible(true)


参考: