一、概述
反射是Java中提供的一种机制,它允许我们在程序运行的时候,动态获取一个类中的基本信息,并且可以调用类中的属性(field)、方法(method)、构造器(constructor)。反射主要依赖于java.lang.Class类型对象实现。
每种类型(无论是基本类型还是引用类型)加载到内存之后,都会在内存中生成一个Class类型对象(每种类型对应一个唯一的Class对象),这个对象就代表这个具体的Java类型,并且保存了这个类型中的基本信息(元数据)。Class类型的对象又被称为字节码对象或镜像。要获取类型的镜像,有以下三种方式(这4个对象都是相同的):
Class<> c1 = 类型名.class; // 对于基本数据类型、类、接口和数组,类型名如:int、int[]、Integer、Student
Class<> c2 = Class.forName("全限定类名"); // 对于数组、类和接口,全限定类名如:[[list、[list、com.briup.Student
Class<Student> c21 = (Student) Class.forName("com.briup.Student"); // 对于具有范型的类类型,通过forName方法获取时必须强转,且需要抛异常
Class c3 = new 类型名().getClass(); // 对于类和数组
二、获取类的元数据
通过类的镜像,可以获取类的修饰符、类名、父类名、实现的接口、包名,以及其中的属性、方法和构造器。
Class c = student.getClass(); // 获取student对象的类对象
c.getName(); // 全限定类名
c.getSimpleName(); // 简单类名
c.getPackage().getName(); // 包名
Modifier.toString(c.getModifiers()); // 修饰符,这里的返回值是修饰符的编码,故通过toString方法获取解码后的修饰符
c.getSuperclass().getName(); // 父类名
Arrays.toString(c.getInterfaces()); // 实现的接口,这里返回的是一个数组,故通过toString方法以便打印
Object.class.isAssignableFrom(c); // 判断Object是不是c的父类
Field[] fields = c.getFields(); // 获取类中属性的公有对象列表
Field[] decFields = c.getDeclaredFields(); // 获取类中属性的对象列表(包括私有)
Field field = c.getField("name"); // 获取类中name属性的对象(须为公有)
Field decField = c.getDeclaredField("name"); // 获取类中name属性的对象(可以是私有)
Method[] methods = c.getDeclaredMethods(); // 获取类中方法的对象列表(包括私有)
Constructor[] constructors = c.getDeclaredConstructors(); // 获取类中构造器的对象列表(包括私有)
从上面可以看出,不单是类拥有对象,类中的属性、方法、构造器,甚至是参数列表的参数(java.lang.reflect.Parameter)、异常的类型,都有对应的对象。这体现了Java“万物皆对象”的理念。
三、获取类的属性、方法和构造器对象
通过属性、方法和构造器对象,可以访问属性、调用方法、创建对象。其中,根据不同的修饰符,具体步骤也不太一样。
(一)属性(java.lang.reflect.Field)
Field[] fs = c.getDeclaredFields(); // 获取属性列表
Field f = f[0]; // 获取第0个属性(的对象),也可以通过遍历等方式获取
Modifier.toString(f.getModifiers()); // 修饰符
f.getType().getName(); // 类型
f.getName(); // 属性名
f.setAccessible(true); // 若f为私有属性,需要修改可访问性(可见性)为true
f.set(stu, "tom"); // 对stu对象的f属性赋值tom
f.set(null, "tom"); // 对静态属性f赋值tom,因为没有对象,故为null
f.get(stu); // 获取stu对象的f属性
(二)方法(java.lang.reflect.Method)
Method m = c.getMethod("toString"); // 获取方法名为toString、参数列表为空(null)的public方法对象
m = c.getMethod("getName", String.class, int.class); // 获取方法名为getName、参数列表为(String, int)的方法对象
Modifier.toString(m.getModifiers()); // 修饰符
m.getReturnType().getName(); // 返回值类型
m.getName(); // 方法名
m.getParameterCount(); // 参数列表参数个数
Class[] paramArr = m.getParameterTypes(); // 参数类型列表
Class[] exceptionArr = m.getExceptionTypes(); // 异常列表
m.setAccessible(true); // 若m为私有方法,需要修改可访问性为true
m.invoke(stu, null); // 调用stu对象的m方法,参数为null,返回值为方法的返回值;若方法返回值为void,则返回null
m.invoke(stu, "tom", 12); // 调用stu对象的m方法,参数为tom和12
m.invoke(null, "tom", 12); // 调用静态方法m,参数为tom和12
(三)构造器(java.lang.reflect.Constructor)
Constructor constructor = c.getConstructor(String.class, int.class); // 获取参数列表为(String, int)的public构造器
Modifier.toString(constructor.getModifiers()); // 修饰符
constructor.getName(); // 构造器名
Class[] paramArr = constructor.getParameterTypes(); // 参数类型列表
Class[] exceptionArr = constructor.getExceptionTypes(); // 异常列表
constructor.newInstance("tom",20); // 调用参数列表为(String, int)的构造器获取一个对象
c.newInstance(); // 调用Student的无参构造器,使用无参构造器不需要构造器对象
四、总结
- 对于私有代码,需要使用getDeclared系列方法获取对象,再使用setAccessible开放可访问性后才可以使用。
- 对于静态代码,使用时将对应参数位置的对象设为null即可。
- 使用反射,可以访问私有的代码,破坏了类的封装性。这对于属性和方法是推荐的,但不推荐使用反射创建对象。
- 在使用字符串获取类、属性和方法对象时,对于能否获取到对应的对象、对象的类型都是未知的,故需要抛异常和强转(若规定了范型)。