反射 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; }
|
我的碎碎念
我感觉反射很奇妙,也很强大。有点像借尸还魂的感觉。
本文参考