简介

反射是 Java 的特征之一。通过反射可以获得并调用任意一个类的所有属性和方法。反射可以在运行期间动态地创造对象、调用方法。

反射的主要使用

反射最重要的用途就是开发各种通用框架。很多框架(比如 Spring)都是配置化的(例如通过 XML 文件配置 Bean),为了保证框架的通用性,它们可能需要根据配置文件加载不同的对象或类,调用不同的方法,这个时候就必须用到反射,运行时动态加载需要加载的对象。

反射是框架实现的灵魂

优缺点

  • 优点:代码实现灵活、为各种框架的功能实现提供便利。
  • 缺点:
    • 滥用反射会额外消耗系统资源。
    • 反射会忽略权限检查,增加安全问题。

基本用法

TargetObject 为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class TargetObject {
private String value;

public TargetObject(String value) {
this.value = value;
}

public TargetObject() {
value = "value";
}

public void publicMethod(String s) {
System.out.println("I love " + s);
}

private void privateMethod() {
System.out.println("value is " + value);
}

}

获得 Class 的方式

  1. 通过Class.forName()方法传入类的全路径获取:

    1
    2
    3
    4
    public static Class<?> forName(String className)

    // xx.xxxx.TargetObject:类的全路径
    Class alunbarClass1 = Class.forName("xx.xxxxx.TargetObject");
  2. 通过具体类的.class属性获取:

    1
    Class alunbarClass2 = TartgetObject.class;
  3. 通过实例对象的instance.getClass()方法获取:

    1
    2
    TargetObject o = new TargetObject();
    Class alunbarClass3 = o.getClass();

三种方法的区别在于:

  1. Class.forName不执行静态块和动态构造块。
  2. xxxx.class执行静态块,但是不执行动态构造块。
  3. instance.getClass()方法因为需要实力对象,所以静态块和动态块都会执行。

静态块仅在类加载时执行一次,若类已加载便不再重复执行;而动态构造块在每次 new 对象时均会执行。

创建实例对象

  1. 通过Class对象的newInstance()方法创建实例对象:

    1
    2
    3
    4
    5
    /**
    * 获取 TargetObject 类的 Class 对象并且创建 TargetObject 类实例
    */
    Class<?> targetClass = TargetObject.class;
    TargetObject targetObject = (TargetObject) targetClass.newInstance();
  2. 通过创建Class对象的构造器对象,再调用构造器对象的newInstance()方法来创建实例,即用制定的构造器来创建对象:

    1
    2
    3
    4
    5
    6
    /**
    * 获取 TargetObject 类的 Class 对象并且创建 TargetObject 类实例
    */
    Class<?> targetClass = TargetObject.class;
    Constructor<?> constructor = targetClass.getConstructor(String.class);
    TargetObject targetObject2 = (TargetObject) constructor.newInstance("value");

方法获取及调用

方法的获取主要有以下三种方法:

  1. getDeclaredMethods:返回类或者接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但是不包括继承的方法。

    1
    public Method[] getDeclaredMethods() throws SecurityException
  2. getMethods:返回某个类的所有公共方法,包括继承类的公用方法。

    1
    public Method[] getMethods() throws SecurityException
  3. getDeclaredMethod:返回类的一个特定的方法,第一个参数为方法的名称,其余为方法的参数对应的Class对象。

    1
    2
    public Method getDeclaredMethod(String name, Class<?>... parameterTypes)
    throws NoSuchMethodException, SecurityException

所有获取到的类方法都需要通过invode()方法调用,以TargetObject为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 获取TargetObject类中定义的所有方法
*/
Method[] methods = targetClass.getDeclaredMethods();
for (Method method : methods) {
System.out.println(method.getName());
}

/**
* 获取指定方法并调用
*/
Method publicMethod = targetClass.getDeclaredMethod("publicMethod", String.class);
publicMethod.invoke(targetObject, "new value");

Method privateMethod = targetClass.getDeclaredMethod("privateMethod");
// 取消安全检查
privateMethod.setAccessible(true);
privateMethod.invoke(targetObject);

成员变量的获取

  • getField():所有的公有变量信息。
  • getDeclaredField():所有已声明的成员变量,但是不能得到父类的成员变量。
    1
    2
    3
    4
    5
    6
    7
    /**
    * 获取指定参数并对参数进行修改
    */
    Field field = targetClass.getDeclaredField("value");
    // 取消安全检查
    field.setAccessible(true);
    field.set(targetObject, "new new value");

利用反射创造数组

利用反射创建数组的例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public static void testArray() throws ClassNotFoundException {
Class<?> cls = Class.forName("java.lang.String");
Object array = Array.newInstance(cls,25);

//往数组里添加内容
Array.set(array,0,"hello");
Array.set(array,1,"Java");
Array.set(array,2,"fuck");
Array.set(array,3,"Scala");
Array.set(array,4,"Clojure");

//获取某一项的内容
System.out.println(Array.get(array,3));
}

/**
* Array为java.lang.reflect.Array
* Array.newInstance()的原型如下
*/
public static Object newInstance(Class<?> componentType, int length)
throws NegativeArraySizeException {
return newArray(componentType, length);
}

其中的Array类为java.lang.reflect.Array类,通过Array.newInstance()创建数组对象。

参考

  1. 深入解析Java反射(1) - 基础
  2. Java 反射获取Class对象的几种方式