当前位置 博文首页 > smily的博客:Java进阶--深入理解Java的反射机制
在上篇文章《深入JVM–探索Java虚拟机的类加载机制》中我们深入探讨了JVM的类加载机制。我们知道,在实例化一个类时,如果这个类还没有被虚拟机加载,那么虚拟机会先执行类加载过程,将该类所对应的字节码读取到虚拟机,并生成一个与这个类对应的Class对象。而在类加载的过程中,由于有双亲委派机制的存在,虚拟机保证了同一个类会被同一个类加载器所加载,进而保证了在虚拟机中只存在一个被加载类所对应的Class实例。而这个Class实例与我们今天要讲的反射有着莫大的关系。
在学习反射之前,我们先来搞清楚几个概念:
假设现在有一个Person类,代码如下:
package com.test.reflection;
public class Person {
private String name;
protected int age;
public String sex;
private Person() {
}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
private void testPrivateMethod() {
System.out.println("testPrivateMethod被调用");
}
}
你是否能通过Person类来解释清楚上边提到的三个问题呢?我们不妨接着往下看。
提到Class类,大家多多少少都应该有些接触,即使你没有使用过反射,也不可避免的接触到Class类。例如,在Android中进行Activity页面跳转的时候,我们需要一个Intent,而实例化Intent则需用到Intent的构造方法,代码如下:
public Intent(Context packageContext, Class<?> cls) {
mComponent = new ComponentName(packageContext, cls);
}
可以看到,Intent的构造方法中的第二个参数,接受的就是一个Class对象。到这里,我们应该都明白,Class就是JDK为我们提供的一个普普通通的Java类,它跟我们自己定义一个Person类其实并无任何本质上的区别。我们进入Class类的源码可以看到,Class类是一个泛型类,并且它实现了若干个接口,源码如下:
public final class Class<T> implements java.io.Serializable,
GenericDeclaration,
Type,
AnnotatedElement,
TypeDescriptor.OfField<Class<?>>,
Constable {
// ...省略主体代码
}
既然Class就是一个普普通通的Java类,那在使用它的时候一定需要实例化出来一个Class对象。但奇怪的是,我们在平时写代码的时候好像从来没有通过new关键字来实例化过Class对象?那它到底是在哪里被实例化的呢?了解类加载机制的同学想必应该都清楚,当然,我们在文章开头也已经提到了,Class对象是在类加载的时候由虚拟机自动生成的。
我们以上边的Person类为例,当我们使用new关键字实例化Person对象的时候,如果Person类的字节码还没有被加载到虚拟机,那么虚拟机首先启动类加载器将Person类的字节码读取到虚拟机中,并为其生成一个Class<Person>的实例,而类加载器的双亲委派模型保证了虚拟机中只会生成一个Class<Person>的实例。而如果在实例化Person对象的时候,Person已经被加载到了虚拟机,则无需再进行Person的类加载过程,直接实例化Person即可。到这里,我们似乎可以感觉到Person对象跟Class<Person>一定存在着某种关系。我们接着往下看。
现在,我们回想一下我们在进行Activity页面跳转的时候Intent构造方法的第二个参数传的是什么呢?是不是像下边这样:
Intent intent=new Intent(this,MainActivity.class);
通过MainActivity.class我们可以得到MainActivity对应的Class对象:
Class<MainActivity> mainActivityClass = MainActivity.class;
而mainActivityClass 对象就是虚拟机在加载MainActivity的时候生成的,并且虚拟机保证了mainActivityClass在虚拟机中是唯一的。
这一过程对于Person类也是一样的,我们可以通过Person .class来拿到虚拟机中唯一的一个Class<Person>实例。
Class<Person> personClass=Person.class;
另外,我们还可以通过Person 的实例对象来获得Class<Person>对象,如下:
Person person=new Person();
Class<Person> personClass=(Class<Person>)person.getClass();
当然,除了上述两种方法之外,我们还可以通过Person类的包名来获得Class<Person>的实例,代码如下:
try {
Class<Person> personClass=(Class<Person>)Class.forName("com.test.reflection");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
由于Class<Person >对象在虚拟机中是唯一的,那么上述三种方法获取到的Class<Person >一定是同一个实例。
好了,上边啰嗦了这么多,终于到了正题了。那到底什么是反射呢?我们来看下百度百科给出的定义:
Java的反射(reflection)机制是指在程序的运行状态中,可以构造任意一个类的对象,可以了解任意一个对象所属的类,可以了解任意一个类的成员变量和方法,可以调用任意一个对象的属性和方法。这种动态获取程序信息以及动态调用对象的功能称为Java语言的反射机制。
虽然上述定义对反射的描述已经非常清楚。但是对于没有了解过反射的同学来说看了之后可能还是一头雾水。下面,在第一章的基础上来说下我来说下我对反射的理解:
在Java中,所有已经被虚拟机的类加载器加载过的类(称为T)都会在虚拟机中生成一个唯一的与T类所对应的Class<T>对象。在程序运行时,通过这个Class<T>对象,我们可以实例化出来一个T对象;可以通过Class<T>对象访问T对象中的任意成员变量,调用T对象中的任意方法,甚至可以对T对象中的成员变量进行修改。我们将这一系列操作称为Java的反射机制。
到这里我们发现,其实Java的反射也没有那么神秘了。说白了就是通过Class对象来操控我们的对象罢了。因此,接下来我们想要弄懂反射只需要来详细的认识一下Class这个类给我们提供的API即可。
我们知道,一个Java类可以包含成员变量、构造方法、以及普通方法。同时,我们又知道Java是一种很纯粹的面向对象的语言。在Java语言中,万物皆对象,类的成员变量、构造方法以及普通方法在Java中也被封装成了对象。它们分别对应Field类、Constructor类以及Method类。这几个类与反射息息相关。因此,在开始之前,我们需要先了解下这几个与反射相关的类,如下图:
通过上文我们已经知道,所谓的反射,其实就是通过API操作Class对象。因此,在进行反射操作的第一步我们应该首先拿到Class的实例。在第一种中我们已经知道可以通过三种方式来获得Class的实例。以获取Person类的Class对象为例,三种方法分别如下:
// 通过类的class获得Class实例
Class<Person> personClass=Person.class;
// 通过类的包名获得Class实例
Class<Person> personClass=(Class<Person>)Class.forName("com.test.reflection");
// 通过对象获得Class实例
Person person=new Person();
Class<Person> personClass=(Class<Person>)person.getClass();
在拿到Person类的Class实例后,我们就可以通过Class实例获取到Person类中的任意成员,包括构造方法、普通方法、成员变量等。
Class类中为我们提供了两个获取构造方法的API,这两个方法如下:
(1)getDeclaredConstructors()
以Person类为例,我们来先来尝试getDeclaredConstructors方法的使用:
Class<Person> personClass=Person.class;
Constructor[] declaredConstructors= personClass.getDeclaredConstructors();
for(Constructor declaredConstructor:declaredConstructors) {
System.out.println(declaredConstructor);
}
注意,我们在Person类中声明了两个构造方法,其中无参构造方法是一个私有的构造方法。我们来看下上述代码的打印结果:
private com.test.reflection.Person()
public com.test.reflection.Person(java.lang.String,int)
可以看到,getDeclaredConstructors方法可以获取到类中包括私有构造方法在内的所有构造方法。
(2) getConstructors()
接着我们将getDeclaredConstructors()方法换成getConstructors()方法:
Class<Person> personClass=Person.class;
Constructor[] declaredConstructors= personClass.getConstructors();
for(Constructor declaredConstructor:declaredConstructors) {
System.out.println(declaredConstructor);
}
再来看输出结果:
public com.test.reflection.Person(java.lang.String,int)
此时,只有被声明了public的方法被打印了出来。
在Class中同样提供了两个获取指定构造方法的API,如下:
我们可以尝试使用getDeclaredConstructor方法来获取Person的私有构造方法与public的有参构造方法:
try {
Constructor<Person> declaredConstructor = personClass.getDeclaredConstructor();
Constructor<Person> declaredConstructor2 = personClass.getDeclaredConstructor(String.class,int.class);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
而如果使用getConstructor获取私有方法,则会抛出java.lang.NoSuchMethodException。
通过反射实例化对象有多种途径,可以使用Class的newInstance方法,同时也可以使用Constructor类。
(1)通过Class的newInstance实例化对象
这种方式使用起来非常简单,直接调用newInstance方法即可完成对象的实例化。代码如下:
Class<Person> personClass = Person.class;
try {
Person person = personClass.newInstance();
} catch (IllegalAccessException | InstantiationException e) {
e.printStackTrace();
}
但是,通过这一方法有一定的局限性。即只能实例化无参构造方法的类,同时这个无参构造方法不能使用private修饰。否则会抛出异常。这个方法在Java 9中已经被声明为Deprecated,并且推荐使用Constructor来实例化对象。
(2) 使用Constructor实例化对象
try {
Constructor<Person> declaredConstructor = personClass.getDeclaredConstructor(String.class, int.class);
Person ryan = declaredConstructor.newInstance("Ryan", 18);
System.out.println(ryan.getName() + "---" + ryan.getAge());
} catch (NoSuchMethodException | InvocationTargetException
| InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
如上代码,我们通过Person的有参Constructor实例化出来一个Person类,并输出如下结果:
Ryan—18
而通过Constructor实例化私有的构造方法时,需要通过Constructor的setAccessible(true)来使Constructor可见,进而进行实例化。否则则会抛出IllegalAccessException异常。实例化私有构造方法的代码如下:
try {
Constructor<