反射基础

反射主要指程序可以访问、检测和修改他本身状态和行为的一种能力,程序在运行时能获取自身的信息;对于任意一个类,都能够获取到这个类的所有属性和方法,对于任意一个对象,都能够调用它的任意一个方法和属性,包括私有的方法和属性,这种动态获取的信息以及动态调用对象的方法的功能就称为Java语言的反射机制。

反射的优点:能使代码更灵活,更加容易实现面向对象,能够使我们很方便的创建灵活的代码,这些代码可以在运行时再装配,无需组件之间进行源代码的链接,体现了多态的应用,降低类之间的耦合性,可以动态的创建对象和编译;

反射的缺点:打破了Java的封装性,导致了Java对像的不安全,使软件的性能降低,复杂度增加,维护成本变高。

反射相关的主要API:

  • java.lang.Class:代表一个类
  • java.lang.reflect.Method:代表类的方法
  • java.lang.reflect.Field:代表类的成员变量
  • java.lang.reflect.Constructor:代表类的构造方法

用于测试的基础类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class ReflectIssue {
public String publicParam = "this is a reflect public parameter";
private String privateParam = "this is a reflect private parameter";

public ReflectIssue() {
}

public ReflectIssue(String publicParam, String privateParam) {
this.publicParam = publicParam;
this.privateParam = privateParam;
}

private void reflectPrivate(String str) {
System.out.println("private : " + str);
}

public void reflectPublic(String str) {
System.out.println("public : " + str);
}

public void moreParam(String paramA, String paramB) {
System.out.println("paramA : " + paramA + ", paramB:" + paramB);
}
}

Reflection方式

获取类的Class对象有以下四种方式,实际应用中最常用的是通过Class.forNameclassLoader的方式来获取Class对象。

1
2
3
4
5
6
7
8
9
ReflectIssue reflectIssue = new ReflectIssue();
Class clazz = reflectIssue.getClass();

Class clazz = ReflectIssue.class;

Class clazz = Class.forName("com.example.ReflectIssue");

ClassLoader classLoader = this.getClass().getClassLoader();
Class clazz = classLoader.loadClass("com.example.ReflectIssue");

getField不能获取私有属性,要获取私有属性使用getDeclaredField方法,该方法也可以用于获取public属性。如果要访问私有属性必须通过setAccessible访问权限打开。如果在当前类中进行反射调用自己可以不用通过setAccessible打开权限。

1
2
3
4
5
6
7
8
9
10
11
Class clazz = Class.forName("com.example.ReflectIssue");
ReflectIssue reflectIssue = (ReflectIssue) clazz.newInstance();
Field publicParam = clazz.getField("publicParam");
System.out.println(publicParam.getName());
System.out.println(publicParam.get(reflectIssue));

Field privateParam = clazz.getDeclaredField("privateParam");
privateParam.setAccessible(true);
System.out.println(privateParam.getName());
System.out.println(privateParam.get(reflectIssue));
privateParam.set(reflectIssue, "new private param value");

同样针对于方法的获取,私有方法获取必须使用getDeclaredMethod方法,该方法也可以用于获取public方法,关于参数的列表可以直接传入一个数组,也可以按照顺序传入多个参数。私有方法的调用需要通过setAccessible方法打开权限。

1
2
3
4
5
6
7
8
9
10
11
Method method = clazz.getMethod("reflectPublic", String.class);
method.invoke(clazz.newInstance(), "this is a public function");

Method method = clazz.getMethod("reflectPublic", new Class[]{String.class});
method.invoke(clazz.newInstance(), new Object[]{"this is a public function"});

Method reflectPrivate = clazz.getDeclaredMethod("reflectPrivate", String.class);
reflectPrivate.setAccessible(true);
reflectPrivate.invoke(clazz.newInstance(), "this is a private function");

((ReflectIssue) clazz.newInstance()).reflectPrivate("this is a private function");

在实际使用中可能需要用到多参数构造方法进行对象的实例化,多参数构造方法实例化类,使用getConstructorgetDeclaredConstructor都可以。getDeclaredConstructor可以获取私有构造方法。和私有属性私有方法访问一样私有构造方法访问需要通过ConstructorsetAccessible打开访问权限。

1
2
3
4
5
6
7
Class clazz = Class.forName("com.example.ReflectIssue");
Constructor constructor = clazz.getDeclaredConstructor(String.class, String.class);
ReflectIssue reflectIssue2 = (ReflectIssue) constructor.newInstance("paramA", "paramB");

Constructor constructor = clazz.getDeclaredConstructor(String.class);
constructor.setAccessible(true);
ReflectIssue reflectIssue2 = (ReflectIssue) constructor.newInstance("paramA");

MethodHandle方式

也可以使用虚拟机提供的MethodHandle通过模拟字节码层次的调用来实现反射,需要主意得是,默认所有得方法得第一个参数一定是一个void参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Class clazz = Class.forName("com.example.ReflectIssue");

MethodType methodType = MethodType.methodType(void.class, String.class);
MethodHandle methodHandle = MethodHandles.lookup().findVirtual(clazz, "reflectPublic", methodType).bindTo(clazz.newInstance());
methodHandle.invokeExact("a");

MethodType methodType = MethodType.methodType(void.class, String.class, String.class);
MethodHandle methodHandle = MethodHandles.lookup().findVirtual(clazz, "moreParam", methodType).bindTo(clazz.newInstance());
methodHandle.invokeExact("a", "b");

MethodHandles.Lookup lookup = MethodHandles.lookup();
Method pm = clazz.getDeclaredMethod("reflectPrivate", String.class);
pm.setAccessible(true);

MethodHandle methodHandle = lookup.unreflect(pm);
methodHandle.invoke(clazz.newInstance(), "a");
methodHandle.invokeExact((ReflectIssue) clazz.newInstance(), "a");

MethodHandle服务于所有java虚拟机上的语言Reflection仅仅服务于java语言Reflection模拟Java代码层次的调用,而MethodHandle模拟字节码层次的方法调用。Reflection重量级,而MethodHandle轻量级MethodHandle可以进行内联优化Reflection完全没有。但JDK8环境下MethodHandles.lookup方法是调用者敏感的。不同调用者访问权限不同,其结果也不同。