反射 Reflection
Java 的反射是指程序在运行期可以拿到一个对象的所有信息。正常情况下,如果我们要调用一个对象的方法,或者访问一个对象的字段,通常会传入对象实例。反射是为了解决在运行期,对某个实例一无所知的情况下,如何调用其方法。
除了 int 等基本类型外,Java 的其他类型全部都是 class(包括 interface)。例如:String、Object、Runnable、Exception 等等。
我们可以得出结论:class(包括 interface)的本质是数据类型 Type。无继承关系的数据类型无法赋值。
1 2 3
| Number n = new Double(123.456); String s = new Double(123.456);
|
JVM 持有的每个 Class 实例都指向一个数据类型(class 或 interface):

一个 Class 实例包含了该 class 的所有完整信息:
由于 JVM 为每个加载的 class 创建了对应的 Class 实例,并在实例中保存了该 class 的所有信息,包括类名、包名、父类、实现的接口、所有方法、字段等。因此,如果获取了某个 Class 实例,我们就可以通过这个 Class 实例获取到该实例对应的 class 的所有信息。
这种通过 Class 实例获取 class 信息的方法称为反射(Reflection)。
动态加载
JVM 在执行 Java 程序的时候,并不是一次性把所有用到的 class 全部加载到内存,而是第一次需要用到 class 时才加载。动态加载 class 的特性对于 Java 程序非常重要。利用 JVM 动态加载 class 的特性,我们才能在运行期根据条件加载不同的实现类。
访问字段
Class 类提供了以下几个方法来获取字段:
Field getField(name):根据字段名获取某个 public 的 field(包括父类) Field getDeclaredField(name):根据字段名获取当前类的某个 field(不包括父类) Field[] getFields():获取所有 public 的 field(包括父类) Field[] getDeclaredFields():获取当前类的所有 field(不包括父类)
一个 Field 对象包含了一个字段的所有信息:
getName():返回字段名称,例如,“name”; getType():返回字段类型,也是一个 Class 实例,例如,String.class; getModifiers():返回字段的修饰符,它是一个 int,不同的 bit 表示不同的含义。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| package top.uuanqin;
import java.lang.reflect.Field;
public class Main { public static void main(String[] args) throws Exception { Student student = new Student("Lingling",775); student.sayHello(); dynamicFieldSet(student,"name","LiHua"); student.sayHello(); }
public static void dynamicFieldSet(Object obj, String fieldName, Object value) throws Exception { Field field = obj.getClass().getField(fieldName); field.set(obj, value); } }
|
输出:
1 2
| hello my name is Lingling hello my name is LiHua
|
注意,这种方法设置属性值时对应属性不能是 private。获取 private 修饰的变量时,可以先 field.setAccessible(true),作用是不管这个字段是不是 public,一律允许访问 。
如果使用反射可以获取 private 字段的值,那么类的封装还有什么意义?
正常情况下,我们总是通过 p.name 来访问 Person 的 name 字段,编译器会根据 public、protected 和 private 决定是否允许访问字段,这样就达到了数据封装的目的。
而反射是一种非常规的用法,使用反射,首先代码非常繁琐,其次,它更多地是给工具或者底层框架来使用,目的是在不知道目标实例任何信息的情况下,获取特定字段的值。
获取字段的值示例:
1 2 3 4 5 6
| public static void main(String[] args) throws Exception { Object student = new Student("李华",333); Field field = student.getClass().getField("name"); Object value = field.get(student); System.out.println(value); }
|
调用方法
Class 类提供了以下几个方法来获取 Method:
Method getMethod(name, Class...):获取某个 public 的 Method(包括父类) Method getDeclaredMethod(name, Class...):获取当前类的某个 Method(不包括父类) Method[] getMethods():获取所有 public 的 Method(包括父类) Method[] getDeclaredMethods():获取当前类的所有 Method(不包括父类)
一个 Method 对象包含一个方法的所有信息:
getName():返回方法名称,例如:"getScore"; getReturnType():返回方法返回值类型,也是一个 Class 实例,例如:String.class; getParameterTypes():返回方法的参数类型,是一个 Class 数组,例如:{String.class, int.class}; getModifiers():返回方法的修饰符,它是一个 int,不同的 bit 表示不同的含义。
对 Method 实例调用 invoke 就相当于调用该方法,invoke 的第一个参数是对象实例,即在哪个实例上调用该方法,后面的可变参数要与方法参数一致,否则将报错。
如果获取到的 Method 表示一个静态方法,调用静态方法时,由于无需指定实例对象,所以 invoke 方法传入的第一个参数永远为 null。
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
| package top.uuanqin;
import java.lang.reflect.Method;
public class Main { public static void main(String[] args) throws Exception { Student student = new Student("Daming",4456); dynamicInvokeMethodWithNoParams(student,"sayHello"); dynamicInvokeMethodWithParams(student,"sayLocation","London",557); }
public static Object dynamicInvokeMethodWithNoParams(Object obj, String methodName) throws Exception { Method method = obj.getClass().getMethod(methodName); return method.invoke(obj); }
public static Object dynamicInvokeMethodWithParams(Object obj, String methodName, Object... values) throws Exception { Class[] classes = new Class[values.length]; for (int i = 0; i < values.length; i++) { if (values[i] instanceof Integer) { classes[i] = Integer.TYPE; } else { classes[i] = values[i].getClass(); } } Method method = obj.getClass().getMethod(methodName,classes); return method.invoke(obj,values); } }
|
输出:
1 2
| hello my name is Daming I am in London 557
|
对于非 public 方法,我们虽然可以通过 Class.getDeclaredMethod() 获取该方法实例,但直接对其调用将得到一个 IllegalAccessException。为了调用非 public 方法,我们通过 Method.setAccessible(true) 允许其调用 。
使用反射调用方法时,仍然遵循多态原则:即总是调用实际类型(如果存在)的覆写方法。
构造一个类(调用构造方法)
获取一个 class 的 Class 实例:
1 2 3 4 5 6 7 8 9
| Class cls = String.class;
String s = "Hello"; Class cls = s.getClass();
Class cls = Class.forName("java.lang.String");
|
因为 Class 实例在 JVM 中是唯一的,所以,上述方法获取的 Class 实例是同一个实例。可以用 == 比较两个 Class 实例。
用 instanceof 不但匹配指定类型,还匹配指定类型的子类。而用 == 判断 class 实例可以精确地判断数据类型,但不能作子类型比较。
JVM 为每一种基本类型,如 int,也创建了 Class 实例,通过 int.class 访问。
如果获取到了一个 Class 实例,我们就可以通过该 Class 实例来创建对应类型的实例:
1 2 3 4 5
| Class cls = String.class;
String s = (String) cls.newInstance();
|
为了调用任意的构造方法,Java 的反射 API 提供了 Constructor 对象,它包含一个构造方法的所有信息,可以创建一个实例。Constructor 对象和 Method 非常类似,不同之处仅在于它是一个构造方法,并且,调用结果总是返回实例。
通过 Class 实例获取 Constructor 的方法如下:
getConstructor(Class...):获取某个 public 的 Constructor getDeclaredConstructor(Class...):获取某个 Constructor getConstructors():获取所有 public 的 Constructor getDeclaredConstructors():获取所有 Constructor
注意 Constructor 总是当前类定义的构造方法,和父类无关,因此不存在多态的问题。
调用非 public 的 Constructor 时,必须首先通过 setAccessible(true) 设置允许访问,但可能会失败 。
无参数构造方法演示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class Main { public static void main(String[] args) throws Exception { String className = "top.uuanqin.Student";
System.out.println("**** 无参数构造方法演示 ****"); Class clazz = Class.forName(className); Object obj = clazz.newInstance(); Student student = (Student) obj; System.out.println("obj.class=" + obj.getClass().toString()); System.out.println("student.class=" + student.getClass().toString()); } }
|
有参数构造方法演示:
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
| package top.uuanqin;
public class Main { public static void main(String[] args) throws Exception { String className = "top.uuanqin.Student"; System.out.println("**** 有参数构造方法1演示 ****"); Class clazz = Class.forName(className); Class[] classes = new Class[2]; classes[0] = String.class; classes[1] = Integer.class; Object obj = clazz.getConstructor(classes).newInstance("tom", 123); Student student = (Student) obj; System.out.println("obj.class=" + obj.getClass().toString()); System.out.println("student.class=" + student.getClass().toString()); System.out.println("Student Name: " + student.getName()); System.out.println("Student Id: " + student.getId());
System.out.println("**** 有参数构造方法2演示 ****"); Object obj = createObjectUsingParams(className, "Alice", 456); Student student = (Student) obj; System.out.println("obj.class=" + obj.getClass().toString()); System.out.println("student.class=" + student.getClass().toString()); System.out.println("Student Name: " + student.getName()); System.out.println("Student Id: " + student.getId()); }
public static Object createObjectUsingParams(String className, Object... values) throws Exception { Class clazz = Class.forName(className); Class[] classes = new Class[values.length]; for (int i = 0; i < classes.length; i++) { classes[i] = values[i].getClass(); } Object obj = clazz.getConstructor(classes).newInstance(values); return obj; } }
|
输出:
1 2 3 4 5 6 7 8 9 10 11 12 13
| **** 无参数构造方法演示 **** obj.class=class top.uuanqin.Student student.class=class top.uuanqin.Student **** 有参数构造方法1演示 **** obj.class=class top.uuanqin.Student student.class=class top.uuanqin.Student Student Name: tom Student Id: 123 **** 有参数构造方法2演示 **** obj.class=class top.uuanqin.Student student.class=class top.uuanqin.Student Student Name: Alice Student Id: 456
|
获取继承关系
获取类的继承和实现关系:
- 获取父类的
Class:getSuperclass()。返回类型 Class。 - 获取
interface:getInterfaces()。由于一个类可能实现一个或多个接口,返回类型 Class[]。注意,只返回当前类直接实现的接口类型,并不包括其父类实现的接口类型。
用 instanceof 操作符判断一个实例是否是某个类型。
如果是两个 Class 实例,要判断一个向上转型是否成立,可以调用 isAssignableFrom():
1 2 3 4 5 6 7 8
| Integer.class.isAssignableFrom(Integer.class);
Number.class.isAssignableFrom(Integer.class);
Object.class.isAssignableFrom(Integer.class);
Integer.class.isAssignableFrom(Number.class);
|
后记
本文使用的实体类 Student
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
| package top.uuanqin;
public class Student { private String name;
private Integer id;
public Student() { this("unset", 0); }
public Student(String name, Integer id) { super(); this.name = name; this.id = id; }
public String getName() { return name; }
@Override public String toString() { return "Student [name=" + name + ", id=" + id + "]"; }
public void setName(String name) { this.name = name; }
public Integer getId() { return id; }
public void setId(Integer id) { this.id = id; }
public void sayHello() { System.out.println("hello my name is " + this.name); }
public void sayLocation(String location, int num) { System.out.println("I am in " + location + " " + num); } }
|
知识点:Java 中的不定长参数
1 2 3 4 5 6 7 8
| public static <T> List<T> asList(T... a) { java.util.ArrayList<T> list = new ArrayList<T>(); for (int i = 0; i < a.length; i++) { list.add(a[i]); } return list; }
|
我的碎碎念
我感觉反射很奇妙,也很强大。有点像借尸还魂的感觉。
本文参考