Java反射(Reflection)

Author Avatar
王善勇、沈伟锐、张佳鉴 6月 26, 2017
  • 在其它设备中阅读本文章

Java中很多框架和应用都会使用反射,本文简单的介绍了Java的反射。

简介

Reflection和Introspection这两个机制都是作用在Java运行时。
每一个类在JVM运行时,都是一个Class实例,该对象描述了响应类的全部属性,包括Annotation、Field、Method等等。
Java的反射和内省都非常的使用,在比较流行的框架Spring和MyBatis中就使用了反射和内省。

先看一个简单的Demo

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
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
public class StartDemo {
public static void main(String[] args)
throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Class<HashMap> mapClazz = (Class<HashMap>) Class.forName("java.util.HashMap");
System.out.println(mapClazz.getName());
System.out.println("------------------------------------------");
Method[] methods = mapClazz.getMethods();
for (Method method : methods) {
System.out.print(" " + method.getName() + "(");
Parameter[] parameters = method.getParameters();
for (Parameter parameter : parameters) {
System.out.print(parameter.getType().getName() + ",");
}
System.out.println(");");
}
System.out.println("------------------------------------------");
Field[] fields = mapClazz.getDeclaredFields();
for (Field field : fields) {
System.out.println(" " + Modifier.toString(field.getModifiers()) + " " + field.getName());
}
Map<String,String> map = new HashMap<>();
Method putMethod = mapClazz.getMethod("put", Object.class,Object.class);
Method getMethod = mapClazz.getMethod("get",Object.class);
putMethod.invoke(map,"hello","world");
String world = (String) getMethod.invoke(map,"hello");
System.out.println("------------------------------------------");
System.out.println(world);
}
}

这个示例中首先通过Class的描述符获取ClassLoader中对应的Class实例对象。
第二步,遍历该类可访问方法(包含父类继承的)。
第三步,遍历该类声明的属性(不包括父类声明的属性)。
第四步,获取Map接口的put方法,获取Map接口的get方法。通过反射得到的put方法将[“hello”:”world”]放入到map对象中,在通过反射得到的方法get,获得map中对应的value。

Java反射的机制的主要作用:

  1. Jvm运行时校验类的信息。
  2. Jvm运行时修改类或者对象的信息。
  3. Jvm运行时动态创建对象实例。
  4. Jvm运行时动态调用对象方法。
  5. 修改构造函数、方法、属性的访问权限。

不足

  1. 性能消耗
    • 由于反射是动态解析类的结构,所有不能执行包含了虚拟机优化的动作。因此,反射操作会比未使用反射的部分性能要差,应当避免在频繁调用的、性能敏感的代码块中使用反射。
  2. 安全限制
    • 反射需要运行时的权限,当一个安全管理器内运行时,肯能是不存在该权限的。当代码必须在受限的安全上下文中调用时,这是一个重要的考虑因素,例如Applet。
  3. 私有信息曝光
    • 由于反射允许非反射代码执行代码操作,这可能导致非法操作,例如访问私有(private)属性和变量,使用反射可能导致意外的副作用,可能是使代码功能失调和破坏可移植性。
      反射会打破抽象设计,因此升级平台后可能导致不同的执行动作。

在Java中,每个对象不是一个引用类型,就是一个原始类型。所有的引用类型都继承自java.lang.Object。类、枚举、数组和接口都是引用类型。
原始类型的固定集合是booleancharbyteshortintlongfloatdouble
对于每种类型,JVM都会实例化一个不可变的java.lang.Class实例,该实例提供了一系列的方法,检查该对象运行时的属性(包括成员和类型信息)。
java.lang.Class还提供了创建新类和新对象的能力。最重要的是,它是所有反射接口的入口。

Class的获取方式

所有反射操作的入口,都是java.lang.Class。有多种获取java.lang.Class对象的方法,使用何种,取决于代码是否有权访问该类的对象、或者该类的名称、或者一个类型、或者一个已有的java.lang.Class对象。

可以获取java.lang.Class的方法和描述如下:

  1. Object.getClass()

如果有一个该类的实例,获取Class最简单的方法就是通过调用对象的getClass()方法获取。这只适用于引用类型的对象。
示例:

Class stringClazz = “foo”.getClass()

  1. .class语法

如果不能访问对象实例,可以访问对象的类型,那么可以通过”Class.class”语法获取Class实例。这个语法可用于原始类型和引用数据类型。
示例:

Class intClazz = int.class;

Class mapClazz = java.util.Map.class;

  1. java.lang.Class.forName方法

如果类的全限定名称是可用的,则可以通过静态方法Class.forName获取Class对象。该方法不适用于原始类型。
数组的全限定名称的语法可以通过”Class.getName()”方法获取。该语法适用于引用对象的数组和原始类型的数组。
示例:

Class c = Class.forName(“com.duke.MyLocaleServiceProvider”);

Class cDoubleArray = Class.forName(“[D”);

Class cStringArray = Class.forName(“[[Ljava.lang.String;”);

  1. 原始类型包装类的TYPE属性

.class语法用于获取一个原始类型的Class对象是更为方便的方式;但还有其他获取该对象的方式。
每一个原始类型和void类型都有一个在java.lang包中的包装类,用于原始类型和引用类型之间的装箱和拆箱操作。
每一个对应的包装类,都有一个命名为TYPE的属性,这个属性的值就是被包装的原始类型的Class实例。

  1. Class.getSuperclass()

返回该类的超类。

  1. Class.getClasses()

返回该类声明的或者从超类声明继承的内部公共类、接口、枚举。

  1. Class.getDeclaredClasses()

返回该类声明的内部类、接口、枚举。

  1. Class、Field、Method、Constructor.getDeclaringClass()

返回声明该Class、Field、Method、Constructor的Class,如果Class不是被其他Class所声明的、或者是匿名内部类,则该接口返回null。

1
2
3
4
import java.lang.reflect.Field;
Field f = System.class.getField("out");
Class c = f.getDeclaringClass();
  1. Class.getEnclosingClass()

返回声明该内部类、或者匿名类的Class对象。

1
2
3
4
static Object o = new Object() {
public void m() {}
};
static Class<?> c = o.getClass().getEnclosingClass();

Class的方法

自身非私有:自身声明的成员,包括public、protected、default

继承的公共:父类声明的public成员

自身私有:自身声明的private成员

Class接口 自身非私有 继承的公共 自身私有
getDeclaredField() Yes No Yes
getField() Yes Yes No
getDeclaredFields() Yes No Yes
getFields() Yes Yes No
Class接口 自身非私有 继承的公共 自身私有
getDeclaredMethod() Yes No Yes
getMethod() Yes Yes No
getDeclaredMethods() Yes No Yes
getMethods() Yes Yes No
Class接口 自身非私有 继承的 自身私有
getDeclaredConstructor() Yes N/A Yes
getConstructor() Yes N/A No
getDeclaredConstructors() Yes N/A Yes
getConstructors() Yes N/A No

获取父类

通过Class实例的getSuperClass()方法可以获取class的父类,
如果该Class实例表示的是Object、一个接口、一个原始类型或者void类型,该方法会返回null。

1
2
3
4
Class<?> superClass = HashMap.class.getSuperclass();
System.out.println(superClass); // "class java.util.AbstractMap"
System.out.println(Object.class.getSuperclass()); // "null"
System.out.println(String[][].class.getSuperclass()); // "class java.lang.Object"

获取Package

getPackage()将返回该类的包信息。可以调用Package.getName()获取包名称。

1
System.out.println(HashMap.class.getPackage().getName());

Package 对象包含有关 Java 包的实现和规范的版本信息。
规范的版本号使用了一个由句点 “.” 分隔的十进制正整数组成的语法,例如 “2.0” 或 “1.2.3.4.5.6.7”。这允许使用可扩展的编号来表示主版本号、次版本号、缩微版本号,等等。

修饰符

Java中共有四种访问限制修饰符:private、protected、缺省、public

  • private:私有,内部访问
  • 缺省:是默认的,同一个package下可以访问。
  • protected:同一个packege或者子类可以访问
  • public:共有,都可以访问

每种修饰符,具体可以修饰的组件如下:

修饰符 成员方法 构造方法 成员变量 局部变量
abstract(抽象的)
static (静态的)
public(公共的)
protected(受保护的)
private(私有的)
synchronized(同步的)
native(本地的)
transient(暂时的)
volatie(易失的)
final(不要改变的)
1
2
3
4
System.out.println(Modifier.toString(HashMap.class.getModifiers()));
//"public"
System.out.println(Modifier.toString(AbstractMap.class.getModifiers()));
//"public abstract"

泛型的参数

getTypeParameters()方法按照顺序返回定义该类所使用泛型参数信息。

1
2
3
4
5
6
Class<?> hashClass = HashMap.class.getSuperclass();
TypeVariable<?>[] typeParameters = hashClass.getTypeParameters();
for (TypeVariable<?> typeVariable : typeParameters) {
System.out.print(typeVariable+",");
}
//输出 K,V,

获取所有实现的接口

getGenericInterfaces():该方法返回一个Type数组,携带了泛型信息。表示该class所有实现的接口集合。
getInterfaces():该方法返回一个Class数组。表示该class所有实现的接口集合。

1
2
3
4
5
6
7
Class<?> listClass = ArrayList.class;
Type[] typeInterfaces = listClass.getGenericInterfaces();
System.out.println(Arrays.toString(typeInterfaces));
// [java.util.List<E>, interface java.util.RandomAccess, interface java.lang.Cloneable, interface java.io.Serializable]
Class[] interfaces = listClass.getInterfaces();
System.out.println(Arrays.toString(interfaces));
//[interface java.util.List, interface java.util.RandomAccess, interface java.lang.Cloneable, interface java.io.Serializable]

操作对象

首先声明一个Demo类

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
47
public class Person implements Comparable<Person>{
private int age = 0;
public String name = "demo";
/**
* 无参公共构造函数
*/
private Person() {
super();
}
public Person(int age, String name) {
this.age = age;
this.name = name;
}
@Override
public int compareTo(Person o) {
return this.age - o.age;
}
private int getAge() {
return age;
}
private Person setAge(int age) {
this.age = age;
return this;
}
public String getName() {
return name;
}
public Person setName(String name) {
this.name = name;
return this;
}
@Override
public String toString() {
return "Person{" + "age=" + age + ", name='" + name + '\'' + '}';
}
}

获取/设置公共属性

get()方法返回的是一个对象,所以原始类型的属性,会返回相应的包装类。
如果属性是static的,那么方法的对象入参需要是null

有一系列的set*()方法,用于设置不同的类型的属性,需要获取相应属性的类型,来确定使用正确的set方法。
如果属性是final的,则会抛出java.lang.IllegalAccessException

1
2
3
4
5
Field field = Person.class.getField("name");
Person person = new Person(1, "demo-01");
System.out.println(field.get(person)); //demo-01
field.set(person, "demo-02"); //设置成 demo-02
System.out.println(field.get(person)); //demo-01

获取/设置私有成员变量

私有的属性在类的外部是不能被访问的,但是使用反射,我们可以关闭私有属性的访问限制。

1
2
3
4
5
6
Field field = Person.class.getDeclaredField("age");
Person person = new Person(1, "demo-01");
field.setAccessible(true); //关闭访问限制
System.out.println(field.get(person)); // 1
field.set(person, 2); //设置成 2
System.out.println(field.get(person)); //2

调用公共方法

我们可以通过调用Method.invoke()方法来调用一个方法。
如果这个方法是一个static的方法,则相应的对象入参,需要设置为null

1
2
3
4
5
Method setMethod = Person.class.getMethod("setName", String.class);
Person person = new Person(1, "demo-01");
System.out.println(setMethod.invoke(person, "demo-02"));
Method getMethod = Person.class.getMethod("getName");
System.out.println(getMethod.invoke(person)); //demo-02

调用私有方法

调用私有方法需要使用setAccessible()方法关闭访问限制。

1
2
3
4
5
6
7
Method setMethod = Person.class.getDeclaredMethod("setAge", int.class);
Person person = new Person(1, "demo-01");
setMethod.setAccessible(true);//关闭访问限制
System.out.println(setMethod.invoke(person, 2));
Method getMethod = Person.class.getDeclaredMethod("getAge");
getMethod.setAccessible(true);//关闭访问限制
System.out.println(getMethod.invoke(person)); //2

构造函数

我们可以通过调用构造函数的newInstance()

1
2
3
4
5
6
7
8
9
10
//获取无参构造函数
Constructor<Person> constructor = Person.class.getDeclaredConstructor();
constructor.setAccessible(true);//关闭访问限制
Person person = constructor.newInstance();
System.out.println(person);// Person{age=0, name='demo'}
//获取入参属性顺序是int.class、String.class的构造函数
constructor = Person.class.getConstructor(int.class, String.class);
person = constructor.newInstance(1, "demo-2");
System.out.println(person);// Person{age=1, name='demo-2'}
  1. 参考文章:http://tutorials.jenkov.com/java-reflection/index.html

  2. 参考文章: http://docs.oracle.com/javase/tutorial/reflect/index.html


TODO:
注解、泛型、数组