[TOC]

文章参考:https://www.jianshu.com/p/607ff4e79a13

概述

动态语言

针对动态语言,大致认同的一个定义是:“程序运行时,允许改变程序结构或变量类型,这种语言称为动态语言”。反射机制在运行时只能调用methods或改变fields内容,却无法修改程序结构或变量类型。从这个观点看,Perl,Python,Ruby是动态语言,C++,Java,C#不是动态语言。

反射的功能

对于Java反射,平常工作中虽然经常用到,尤其是一些开源框架的设计中,大量用到注解和反射配合使用,来完成很多动能。

JAVA反射机制,可在运行态直接操作任意类或对象的所有属性和方法,主要有以下几个功能:

  • 在运行时获取任意对象所属的类
  • 在运行时构造类的实例对象
  • 在运行时获取或修改类/成员的属性
  • 在运行时调用某个类/对象的方法
  • 另外还可获取类的其他信息,比如描述修饰符、父类信息等

这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

反射的使用

我们平时使用Java反射主要涉及两个类(接口)Class, Member等一些基础概念,下面我们就来分别学习这些基础概念

Class类

Class可以说是反射能够实现的基础;注意这里说的Class与class关键字不是同一种东西。class关键字是在声明java类时使用的;而Class 是java JDK提供的一个类,完整路径为 java.lang.Class,本质上与Math, String 或者你自己定义各种类没什么区别。

那Class到底在反射中起到什么作用呢?

For every type of object, the Java virtual machine instantiates an immutable instance of java.lang.Class which provides methods to examine the runtime properties of the object including its members and type information. Class also provides the ability to create new classes and objects. Most importantly, it is the entry point for all of the Reflection APIs.

对于每一种类,Java虚拟机都会初始化出一个Class类型的实例,所以我们就可以知道class其实是Class类型的实例。每当我们编写并且编译一个新创建的类就会产生一个对应Class对象,并且这个Class对象会被保存在同名.class文件里。当我们new一个新对象或者引用静态成员变量时,Java虚拟机(JVM)中的类加载器系统会将对应Class对象加载到JVM中,然后JVM再根据这个类型信息相关的Class对象创建我们需要实例对象或者提供静态变量的引用值。

比如创建编译一个People类,那么,JVM就会创建一个People对应Class类的Class实例,这个实例就是People.class,该Class实例保存了People类相关的类型信息,包括属性,方法,构造方法等等,通过这个Class实例可以在运行时访问People对象的属性和方法等。另外通过Class类还可以创建出一个新的People对象。这就是反射能够实现的原因,可以说Class是反射操作的基础。

需要特别注意的是,每个class(注意class是小写,代表普通类)类,无论创建多少个实例对象,在JVM中都对应同一个Class对象。

Class实例的获取

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
private static void testGetClass() {
//1、通过People.class 这种形式
// 其实这种方式也很好理解,我们都知道People.class是Class类型的实例嘛
Class clazz = People.class;
Class stringClazz1 = String.class;
Class booleanClazz1 = boolean.class;

// 2、通过Class.forName的全限定名来获取Class实例
try {
//反射获取Class对象 一般尽量采用这种形式
Class clazz2 = Class.forName("com.frewen.reflect.People");
Class stringClazz2 = Class.forName("java.lang.String");

// 对于数组比较特殊
Class cDoubleArray = Class.forName("[D"); //相当于double[].class
Class cStringArray = Class.forName("[[Ljava.lang.String;"); //相当于String[][].class

} catch (ClassNotFoundException e) {
e.printStackTrace();
}
// 3、通过实例对象的getClass方法
Class peopleClazz3 = new People().getClass();
// 这个是String实例对象的getClass方法
Class stringClazz3 = "HellString".getClass();
byte[] bytes = new byte[1024];
Class bytesClazz3 = bytes.getClass();
//Returns the Class corresponding to the enumeration type E.
Class enumObjValue = EnumObj.A.getClass();

Set<String> hashSet3 = new HashSet<String>();
//Returns the Class corresponding to java.util.HashSet.
Class hashSetClazz3 = hashSet3.getClass();
}

说Class是反射能够实现的基础的另一个原因是:Java反射包java.lang.reflect中的所有类都没有public构造方法,要想获得这些类实例,只能通过Class类获取。所以说如果想使用反射,必须得获得Class对象。

我们从上面的代码可以看出,共有三种获取Class对象的方法:

1、实例化对象的getClass方法(object.getClass())

2、.class语法(People.class)

image

3、通过Class.forName()全限定名

4、基本数据类型TYPE属性

基本类型,如BOOLEAN,都有TYPE属性,可以得到这个基本类型的类型:

image

另外还有一些反射方法可以获取Class对象,但前提是你已经获取了一个Class对象。
有点拗口,比如说你已经获取了一个类的Class对象,就可以通过反射方法获取这个类的父类的Class对象。

Class.getSuperclass()

获得给定类的父类Class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Class c = people.class.getSuperclass();

// 类似的还有:
Class.getClasses()

Class.getDeclaredClasses()

Class.getDeclaringClass()

Class.getEnclosingClass()

java.lang.reflect.Field.getDeclaringClass()

java.lang.reflect.Method.getDeclaringClass()

java.lang.reflect.Constructor.getDeclaringClass()

通过Class获取类修饰符和类型

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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
/**
* 通过Class获取类的修饰符和类型
* 我们以HashMap为例,来看一下怎么获取这些信息
*/
private static void testGetClassModifier() {
// 获取HashMap的Class类型的实例
Class<?> clazz = HashMap.class;
//获取类名
System.out.println("Class : " + clazz.getCanonicalName());

//获取类限定符
System.out.println("Class Modifiers : " + Modifier.toString(clazz.getModifiers()));

//获取类泛型信息
TypeVariable[] tv = clazz.getTypeParameters();
if (tv.length != 0) {
StringBuilder parameter = new StringBuilder("Parameters : ");
for (TypeVariable t : tv) {
parameter.append(t.getName());
parameter.append(" ");
}
System.out.println(parameter.toString());
} else {
System.out.println(" -- No Type Parameters --");
}

//获取类实现的所有接口
Type[] genericInterfaces = clazz.getGenericInterfaces();
if (genericInterfaces.length != 0) {
StringBuilder interfaces = new StringBuilder("Implemented Interfaces : ");
for (Type genericInterface : genericInterfaces) {
interfaces.append(genericInterface.toString());
interfaces.append(" ");
}
System.out.println(interfaces.toString());
} else {
System.out.println(" -- No Implemented Interfaces --");
}

//获取类继承树上的所有父类
List<Class> l = new ArrayList<>();
printAncestor(clazz, l);
if (l.size() != 0) {
StringBuilder inheritance = new StringBuilder("Inheritance Path : ");
for (Class<?> cl : l) {
inheritance.append(cl.getCanonicalName());
inheritance.append(" ");
}
System.out.println(inheritance.toString());
} else {
System.out.println(" -- No Super Classes --");
}


//获取类的注解(只能获取到 RUNTIME 类型的注解)
Annotation[] ann = clazz.getAnnotations();
if (ann.length != 0) {
StringBuilder annotation = new StringBuilder("Annotations : ");
for (Annotation a : ann) {
annotation.append(a.toString());
annotation.append(" ");
}
System.out.println(annotation.toString());
} else {
System.out.println(" -- No Annotations --");
}
}

private static void printAncestor(Class<?> clazz, List<Class> superClassList) {
Class<?> ancestor = clazz.getSuperclass();
if (ancestor != null) {
superClassList.add(ancestor);
printAncestor(ancestor, superClassList);
}
}

输出结果:

1
2
3
4
5
6
Class : java.util.HashMap
Class Modifiers : public
Parameters : K V
Implemented Interfaces : java.util.Map<K, V> interface java.lang.Cloneable interface java.io.Serializable
Inheritance Path : java.util.AbstractMap java.lang.Object
-- No Annotations --%n%n

我们可以看到,获取类的限定符以及类型基本有下面几种方法:

1
2
3
4
5
6
7
8
9
10
//获取类名
clazz.getCanonicalName()
//获取类限定符
clazz.getModifiers()
//获取类泛型信息
clazz.getTypeParameters()
//获取类实现的所有接口
clazz.getGenericInterfaces()
//获取类的注解(只能获取到 RUNTIME 类型的注解)
clazz.getAnnotations()

Member相关

++Reflection defines an interface java.lang.reflect.Member which is implemented by java.lang.reflect.Field, java.lang.reflect.Method, and java.lang.reflect.Constructor .++

对于Member接口可能会有人不清楚是干什么的,但如果提到实现它的三个实现类,估计用过反射的人都能知道。我们知道类成员主要包括构造函数,变量和方法,Java中的操作基本都和这三者相关,而Member的这三个实现类就分别对应他们。

1
2
3
4
5
java.lang.reflect.Field  //对应类变量

java.lang.reflect.Method //对应类方法

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
25
26
27
28
29
30
31
32
33
34
private static void testFiled() {
// 通过.class的语法获取People的Class类型的实例
Class clazz = People.class;
// 获取指定的变量(只要是声明的变量都能获得,包括private)
Field[] fields = clazz.getDeclaredFields();

for (Field field : fields) {
StringBuilder builder = new StringBuilder();
//获取名称
builder.append("类变量名称 = ");
builder.append(field.getName());
//获取类型
builder.append(",变量类型 = ");
builder.append(field.getType());
//获取修饰符
builder.append(",变量修饰符 = ");
builder.append(Modifier.toString(field.getModifiers()));

//获取注解
Annotation[] ann = field.getAnnotations();
if (ann.length != 0) {
builder.append(",变量的注解 = ");
for (Annotation a : ann) {
builder.append(a.toString());
builder.append(" ");
}
} else {
builder.append(" -- No Annotations --");
}


System.out.println("field:" + builder.toString());
}
}

输出结果:

1
2
3
4
field:类变量名称 = id,变量类型 = int,变量修饰符 = private  -- No Annotations --
field:类变量名称 = name,变量类型 = class java.lang.String,变量修饰符 = public -- No Annotations --
field:类变量名称 = oldName,变量类型 = class java.lang.String,变量修饰符 = private,变量的注解 = @java.lang.Deprecated(forRemoval=false, since="")
field:类变量名称 = age,变量类型 = int,变量修饰符 = private -- No Annotations --

获取Field的相关信息

通过Field你可以访问给定对象的类变量,包括获取变量的类型、修饰符、注解、变量名、变量的值或者重新设置变量值,即使变量是private的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Class提供了4种方法获得给定类的Field。

getDeclaredField(String name) // 获取指定的变量(只要是声明的变量都能获得,包括private)
getField(String name) //获取指定的变量(只能获得public的)
getDeclaredFields() // 获取所有声明的变量(包括private)
getFields() // 获取所有的public变量


// 获取变量类型、修饰符、注解

field.getName() // 获取变量名称
field.getType() // 获取变量类型
Modifier.toString(field.getModifiers()) // 获取变量修饰符
field.getAnnotations() // 获取变量注解

注意:上面的获取变量,是获取类变量,所以类中的所有变量(包括静态变量) 都能获取到

设置Field的相关变量

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
private static void testSetFiled() {
People people = new People(10001, "王大明", "王小明", 18);

System.out.println(people.toString());

Class peopleClazz = people.getClass();


try {
Field publicFiledName = peopleClazz.getField("name");
//注意获取private变量时,需要用getDeclaredField
Field privateFiledAge = peopleClazz.getDeclaredField("age");

//反射获取名字, 年龄
String name = (String) publicFiledName.get(people);

// 这个地方要
// 变量是private,Java运行时会进行访问权限检查,private类型的变量无法进行直接访问,
// 刚刚进行的反射操作并没有打破这种封装,所以我们依然没有权限对private属性进行直接访问。
// 如果不加的话,会报下面的异常:
//java.lang.IllegalAccessException: class com.frewen.reflect.ReflectTest
// cannot access a member of class com.frewen.reflect.People with modifiers "private"
//at java.base/jdk.internal.reflect.Reflection.newIllegalAccessException(Reflection.java:361)
//at java.base/java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:591)
// at java.base/java.lang.reflect.Field.checkAccess(Field.java:1075)
// at java.base/java.lang.reflect.Field.getInt(Field.java:592)
// at com.frewen.reflect.ReflectTest.testSetFiled(ReflectTest.java:44)
// at com.frewen.reflect.ReflectTest.main(ReflectTest.java:21)
privateFiledAge.setAccessible(true);
int age = privateFiledAge.getInt(people);
System.out.println("反射获取:People name:" + name + ",age:" + age);

// 通过反射设置新的变量值
publicFiledName.set(people, "王大明反射名字");
privateFiledAge.set(people, 23);

/// 重新获取名字
name = people.getName();
age = privateFiledAge.getInt(people);
System.out.println("反射设置名字之后:People name:" + name + ",age:" + age);


} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
}

输出结果:

1
2
3
4
People constructor called with id = 10001,name = 王大明,oldName = 王小明,name = 18
People id:10001,name:王大明,oldName:王小明,age:18
反射获取:People name:王大明,age:18
反射设置名字之后:People name:王大明反射名字,age:23

这个相关的操作都比较简单,我们重点说一下setAccessible(boolean flag)这个方法

People的age变量是private,Java运行时会进行访问权限检查,private类型的变量无法进行直接访问,刚刚进行的反射操作并没有打破这种封装,所以我们依然没有权限对private属性进行直接访问。
难道就没有办法打破这种限制吗?

反射包里为我们提供了一个强大的类。

1
java.lang.reflect.AccessibleObject

AccessibleObject为我们提供了一个方法 setAccessible(boolean flag),该方法的作用就是可以取消 Java 语言访问权限检查。所以任何继承AccessibleObject的类的对象都可以使用该方法取消 Java 语言访问权限检查。(final类型变量也可以通过这种办法访问)

1
public final class Field extends AccessibleObject implements Member

Field正是AccessibleObject的子类,那么简单了,只要在访问私有变量前调用filed.setAccessible(true)就可以了

注意Method和Constructor也都是继承AccessibleObject,所以如果遇到私有方法和私有构造函数无法访问,记得处理方法一样。

获取Method的相关信息

++The java.lang.reflect.Method class provides APIs to access information about a method’s modifiers, return type, parameters, annotations, and thrown exceptions. It also be used to invoke methods.++

Class依然提供了4种方法获取Method:

1
2
3
4
5
6
7
8
9
getDeclaredMethod(String name, Class... parameterTypes)  //根据方法名获得指定的方法, 参数name为方法名,参数parameterTypes为方法的参数类型,如 getDeclaredMethod(“eat”, String.class)

getMethod(String name, Class... parameterTypes) // 根据方法名获取指定的public方法,其它同上

getDeclaredMethods() //获取所有声明的方法

getMethods() //获取所有的public方法

// 注意:获取带参数方法时,如果参数类型错误会报NoSuchMethodException,对于参数是泛型的情况,泛型须当成Object处理(Object.class)

下面是获取方法的返回值类型

1
2
getReturnType()  // 获取目标方法返回类型对应的Class对象
getGenericReturnType() //获取目标方法返回类型对应的Type对象

这两个方法有啥区别呢?

getReturnType()返回类型为Class,getGenericReturnType()返回类型为Type; Class实现Type。

返回值为普通简单类型如Object, int, String等,getGenericReturnType()返回值和getReturnType()一样

下面我们举几个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
例如 public String method()

那么各自返回值为:

getReturnType() : class java.lang.String
getGenericReturnType() : class java.lang.String

例如 public T function2()

那么各自返回值为:

getReturnType() : class java.lang.Object
getGenericReturnType() : T

例如public Class function3()

那么各自返回值为:

getReturnType() : class java.lang.Class
getGenericReturnType() : java.lang.Class

看个例子:

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
private static void testMethod() {
Class<?> peopleClass = People.class;
try {
//构造Cat实例
Constructor constructor = peopleClass.getConstructor(int.class, String.class, String.class, int.class);
Object people = constructor.newInstance(10001, "王大明", "王小明", 18);


//调用无参方法
Method toString = peopleClass.getDeclaredMethod("toString");
toString.invoke(people);
//调用定项参数方法
Method setAge = peopleClass.getDeclaredMethod("setAge", int.class);
setAge.invoke(people, 23);
// 这个地方注意一下: 注意:如果方法是private的,可以使用method.setAccessible(true)方法绕过权限检查
// 否则,会报异常:
// java.lang.IllegalAccessException: class com.frewen.reflect.ReflectTest
// cannot access a member of class com.frewen.reflect.People with modifiers "private"
// at java.base/jdk.internal.reflect.Reflection.newIllegalAccessException(Reflection.java:361)
// at java.base/java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:591)
// at java.base/java.lang.reflect.Method.invoke(Method.java:558)
// at com.frewen.reflect.ReflectTest.testMethod(ReflectTest.java:44)
// at com.frewen.reflect.ReflectTest.main(ReflectTest.java:23)
Method getAge = peopleClass.getDeclaredMethod("getAge");
getAge.setAccessible(true);
System.out.println(getAge.invoke(people));


//调用不定项参数方法
//不定项参数可以当成数组来处理
Class[] argTypes = new Class[]{String[].class};
Method varargsEat = peopleClass.getDeclaredMethod("eat", argTypes);
String[] foods = new String[]{
"milk", "meat"
};
varargsEat.invoke(people, (Object) foods);
} catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
e.printStackTrace();
}
}

注意:如果方法是private的,可以使用method.setAccessible(true)方法绕过权限检查

被调用的方法本身所抛出的异常在反射中都会以InvocationTargetException抛出。换句话说,反射调用过程中如果异常InvocationTargetException抛出,说明反射调用本身是成功的,因为这个异常是目标方法本身所抛出的异

获取Constructor实例相关

和Method一样,Class也为Constructor提供了4种方法获取

1
2
3
4
getDeclaredConstructor(Class... parameterTypes)   // 获取指定构造函数,参数parameterTypes为构造方法的参数类型
getConstructor(Class... parameterTypes) // 获取指定public构造函数,参数parameterTypes为构造方法的参数类型
getDeclaredConstructors() // 获取所有声明的构造方法,包括public和private的构造函数
getConstructors() //获取所有的public构造方法

通过Constructor创建实例对象

通过反射有两种方法可以创建对象:

1
2
java.lang.reflect.Constructor.newInstance()
Class.newInstance()

一般来讲,我们优先使用第一种方法;那么这两种方法有何异同呢?

Class.newInstance()仅可用来调用无参的构造方法;Constructor.newInstance()可以调用任意参数的构造方法。

Class.newInstance()会将构造方法中抛出的异常不作处理原样抛出;Constructor.newInstance()会将构造方法中抛出的异常都包装成InvocationTargetException抛出。

反射的缺点

Java反射拥有强大功能的同时也带来了一些副作用。

  • 1、性能开销

反射涉及类型动态解析,所以JVM无法对这些代码进行优化。因此,反射操作的效率要比那些非反射操作低得多。我们应该避免在经常被执行的代码或对性能要求很高的程序中使用反射。

  • 2、安全限制

使用反射技术要求程序必须在一个没有安全限制的环境中运行。如果一个程序必须在有安全限制的环境中运行,如Applet,那么这就是个问题了。

  • 3、内部曝光

由于反射允许代码执行一些在正常情况下不被允许的操作(比如访问私有的属性和方法),所以使用反射可能会导致意料之外的副作用--代码有功能上的错误,降低可移植性。反射代码破坏了抽象性,因此当平台发生改变的时候,代码的行为就有可能也随着变化。

使用反射的一个原则:如果使用常规方法能够实现,那么就不要用反射。