什么是反射?
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
反射的作用
在运行时,通过反射可以判断任意一个对象所属的类、构造任意一个类的对象、判断任意一个类所具有的成员变量和方法、调用任意一个对象的方法以及给任意一个类的属性设值等等。
Class类
在Java中,除了int等基本数据类型以外,其他的类型全部都是class(包括interface)。
class是由JVM在运行状态中动态加载的,JVM第一次读取到class类型时,就将它加入到内存。每加载一种class,JVM就为它创建一个Class类型的实例,并关联起来。
例如String类型,JVM加载String类型时,先读取String.class字节码文件到内存,并创建一个与String类关联的Class实例。
通过查看源码可以知道,Class类型的构造方法是用了private修饰,因此,只有JVM才能够创建实例,自己的程序是无法创建的。
由于JVM为每个加载的class创建了对应的Class实例,并在实例中保存了该class的所有信息,包括类名、包名、父类、实现的接口、所有方法、字段等,因此,如果获取了某个Class实例,我们就可以通过这个Class实例获取到该实例对应的class的所有信息。
获取Class实例的三种方法
- 通过一个类的静态变量
class获取:
Class cls = String.class;
- 通过实例变量提供的getClass()方法获取:
String s = "Hello";
Class cls = s.getClass();
- 知道完整类名,通过静态方法Class.forName()获取
Class cls = Class.forName("java.lang.String");
当获取到Class实例,就可以通过该实例获取对应类型的实例:
// 获取String的Class实例
Class cls = String.class;
// 创建一个String实例
//相当于 new String()
String s = (String) cls.newInstance();
注意:通过Class.newInstance()可以创建类实例,它的局限是:只能调用public的无参数构造方法。带参数的构造方法,或者非public的构造方法都无法通过Class.newInstance()被调用。
动态加载
JVM在执行Java程序的时候,并不是一次性把所有用到的class全部加载到内存,而是第一次需要用到class时才加载。利用JVM动态加载class的特性,我们可以在运行期间根据条件加载不同的实现类。
访问字段
Class类提供以下几个方法来获取相应的字段:
Field getField(name):根据字段名获取某个public的field(包括父类)Field getDeclaredField(name):根据字段名获取当前类的某个field(不包括父类)Field[] getFields():获取所有public的field(包括父类)Field[] getDeclaredFields():获取当前类的所有field(不包括父类)
package edu.gzc;
public class Main {
public static void main(String[] args) throws NoSuchFieldException {
// write your code here
Class stdClass = Student.class;
// 获取public字段"score":
System.out.println(stdClass.getField("score"));
// 获取继承的public字段"name":
System.out.println(stdClass.getField("name"));
// 获取private字段"grade":
System.out.println(stdClass.getDeclaredField("grade"));
}
}
class Student extends Persion{
public int score;
private int grade;
}
class Persion{
public String name;
}
输出:

一个Field对象包含了一个字段的所有信息:
getName():返回字段名称,例如,“name”;getType():返回字段类型,也是一个Class实例,例如,String.class;getModifiers():返回字段的修饰符,它是一个int,不同的bit表示不同的含义。
通过Field对象获取字段信息
Field score = stdClass.getDeclaredField("score");
System.out.println(score.getName()); //输出 score
System.out.println(score.getType()); ///输出 int
//获取字段修饰符
int m =score.getModifiers();
System.out.println(Modifier.isPublic(m)); // 输出 true
获取字段值和设置字段值
先获取Class实例,再获取Field实例:
- 用
Field.get(Object)获取指定实例的指定字段的值。 - 用
Field.set(Object, Object)实现设置字段值,其中第一个Object参数是指定的实例,第二个Object参数是待修改的值。
public class Main {
public static void main(String[] args) throws Exception {
// write your code here
//获取字段值
Persion p = new Persion("xiaoming");
Class c = p.getClass();
Field f = c.getDeclaredField("name");
//不管字段是public,还是private,都允许访问
f.setAccessible(true);
Object value = f.get(p);
System.out.println(value); // 输出 xiaoming
//设置字段值
f.set(p, "zhangsan");
System.out.println(p.getName()); //输出 zhangsan
}
}
class Persion {
public String name;
public Persion(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
如果获取非public字段,会抛出IllegalAccessException异常,可以将private修改为public ,或者在得到Field对象后,设置访问权限:f.setAccessible(true);。
但该方法也可能会失败,如果JVM运行期存在SecurityManager,那么它会根据规则进行检查,有可能阻止setAccessible(true)。
调用方法
通过Class实例获取所有Method信息。Class类提供了以下几个方法来获取Method:
Method getMethod(name, Class...):获取某个public的Method(包括父类)Method getDeclaredMethod(name, Class...):获取当前类的某个Method(不包括父类)Method[] getMethods():获取所有public的Method(包括父类)Method[] getDeclaredMethods():获取当前类的所有Method(不包括父类)
package edu.gzc;
public class Main {
public static void main(String[] args) throws Exception {
Class stuClass=Student.class;
//获取public 方法setScore,参数为int
System.out.println(stuClass.getDeclaredMethod("setScore", int.class));;
//获取继承的public方法getName,无参数
System.out.println(stuClass.getMethod("getName"));
}
}
class Student extends Persion {
private int score;
private int grade;
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
}
class Persion {
private String name;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
输出:

一个Method对象包含一个方法的所有信息:
getName():返回方法名称,例如:”getScore“;getReturnType():返回方法返回值类型,也是一个Class实例,例如:String.class;getParameterTypes():返回方法的参数类型,是一个Class数组,例如:{String.class, int.class};getModifiers():返回方法的修饰符,它是一个int,不同的bit表示不同的含义。
调用方法
对Method实例调用invoke就相当于调用该方法,invoke的第一个参数是对象实例,即在哪个实例上调用该方法,后面的可变参数要与方法参数一致,否则将报错。
方法:Object invoke(Object instance, Object... parameters)
import java.lang.reflect.Method;
public class Test {
public static void main(String[] args) throws Exception {
String str = "hello world";
// 获取String substring(int)方法,参数为int:
Method m =String.class.getDeclaredMethod("substring", int.class);
// 在str对象上调用该方法并获取结果:
String result= (String) m.invoke(str, 6);
// 打印调用结果:
System.out.println(result); //输出 world
}
}
调用静态方法
如果获取到的Method表示一个静态方法,调用静态方法时,由于无需指定实例对象,所以invoke方法传入的第一个参数永远为null。
例如 :
Integer n = (Integer) m.invoke(null, "12345");
调用非public方法
通过设置setAccessible(true)来访问非public方法。setAccessible(true)可能会失败。
注意:使用反射调用方法时,仍然遵循多态原则。
调用构造方法
使用反射来创建实例,则需要调用 Class提供的newInstance()方法。
Person p = Person.class.newInstance();
要注意的是,newInstance()只能调用该类无参的public的无参构造方法。
那么如何调用任意构造方法呢???
为了调用任意的构造方法,Java的反射API提供了Constructor对象,它包含一个构造方法的所有信息,可以创建一个实例。
通过Class实例获取Constructor的方法如下:
getConstructor(Class...):获取某个public的Constructor;getDeclaredConstructor(Class...):获取某个Constructor;getConstructors():获取所有public的Constructor;getDeclaredConstructors():获取所有Constructor。
调用非public的Constructor时,必须首先通过setAccessible(true)设置允许访问。setAccessible(true)可能会失败。
import java.lang.reflect.Constructor;
public class Test {
public static void main(String[] args) throws Exception {
//获取构造方法Integer(int)
Constructor cons1 = Integer.class.getConstructor(int.class);
//调用构造方法
Integer n1 = (Integer) cons1.newInstance(123);
System.out.println(n1); // 输出 123
// 获取构造方法Integer(String)
Constructor cons2 = Integer.class.getConstructor(String.class);
Integer n2 = (Integer) cons2.newInstance("456");
System.out.println(n2); // 输出 456
}
获取继承关系
获取父类的Class:
通过Class提供的Class getSuperclass()方法可以获取父类的Class。
获取interface:
- 通过
Class提供的Class[] getInterfaces()方法可以获取该类实现的接口的Class。 getInterfaces()只返回当前类直接实现的接口类型,并不包括其父类实现的接口类型。- 对所有
interface的Class调用getSuperclass()返回的是null,获取接口的父接口要用getInterfaces()。 - 如果一个类没有实现任何
interface,那么getInterfaces()返回空数组。 - 两个
Class实例,要判断一个向上转型是否成立,可以调用isAssignableFrom()。
参考文章:https://www.liaoxuefeng.com/wiki/1252599548343744/1264799402020448