一、概述

反射是Java中提供的一种机制,它允许我们在程序运行的时候,动态获取一个类中的基本信息,并且可以调用类中的属性(field)、方法(method)、构造器(constructor)。反射主要依赖于java.lang.Class类型对象实现。
每种类型(无论是基本类型还是引用类型)加载到内存之后,都会在内存中生成一个Class类型对象(每种类型对应一个唯一的Class对象),这个对象就代表这个具体的Java类型,并且保存了这个类型中的基本信息(元数据)。Class类型的对象又被称为字节码对象或镜像。要获取类型的镜像,有以下三种方式(这4个对象都是相同的):

  1. Class<> c1 = 类型名.class; // 对于基本数据类型、类、接口和数组,类型名如:int、int[]、Integer、Student
  2. Class<> c2 = Class.forName("全限定类名"); // 对于数组、类和接口,全限定类名如:[[list、[list、com.briup.Student
  3. Class<Student> c21 = (Student) Class.forName("com.briup.Student"); // 对于具有范型的类类型,通过forName方法获取时必须强转,且需要抛异常
  4. Class c3 = new 类型名().getClass(); // 对于类和数组

二、获取类的元数据

通过类的镜像,可以获取类的修饰符、类名、父类名、实现的接口、包名,以及其中的属性、方法和构造器。

  1. Class c = student.getClass(); // 获取student对象的类对象
  2. c.getName(); // 全限定类名
  3. c.getSimpleName(); // 简单类名
  4. c.getPackage().getName(); // 包名
  5. Modifier.toString(c.getModifiers()); // 修饰符,这里的返回值是修饰符的编码,故通过toString方法获取解码后的修饰符
  6. c.getSuperclass().getName(); // 父类名
  7. Arrays.toString(c.getInterfaces()); // 实现的接口,这里返回的是一个数组,故通过toString方法以便打印
  8. Object.class.isAssignableFrom(c); // 判断Object是不是c的父类
  9. Field[] fields = c.getFields(); // 获取类中属性的公有对象列表
  10. Field[] decFields = c.getDeclaredFields(); // 获取类中属性的对象列表(包括私有)
  11. Field field = c.getField("name"); // 获取类中name属性的对象(须为公有)
  12. Field decField = c.getDeclaredField("name"); // 获取类中name属性的对象(可以是私有)
  13. Method[] methods = c.getDeclaredMethods(); // 获取类中方法的对象列表(包括私有)
  14. Constructor[] constructors = c.getDeclaredConstructors(); // 获取类中构造器的对象列表(包括私有)

从上面可以看出,不单是类拥有对象,类中的属性、方法、构造器,甚至是参数列表的参数(java.lang.reflect.Parameter)、异常的类型,都有对应的对象。这体现了Java“万物皆对象”的理念。

三、获取类的属性、方法和构造器对象

通过属性、方法和构造器对象,可以访问属性、调用方法、创建对象。其中,根据不同的修饰符,具体步骤也不太一样。

(一)属性(java.lang.reflect.Field)

  1. Field[] fs = c.getDeclaredFields(); // 获取属性列表
  2. Field f = f[0]; // 获取第0个属性(的对象),也可以通过遍历等方式获取
  3. Modifier.toString(f.getModifiers()); // 修饰符
  4. f.getType().getName(); // 类型
  5. f.getName(); // 属性名
  6. f.setAccessible(true); // 若f为私有属性,需要修改可访问性(可见性)为true
  7. f.set(stu, "tom"); // 对stu对象的f属性赋值tom
  8. f.set(null, "tom"); // 对静态属性f赋值tom,因为没有对象,故为null
  9. f.get(stu); // 获取stu对象的f属性

(二)方法(java.lang.reflect.Method)

  1. Method m = c.getMethod("toString"); // 获取方法名为toString、参数列表为空(null)的public方法对象
  2. m = c.getMethod("getName", String.class, int.class); // 获取方法名为getName、参数列表为(String, int)的方法对象
  3. Modifier.toString(m.getModifiers()); // 修饰符
  4. m.getReturnType().getName(); // 返回值类型
  5. m.getName(); // 方法名
  6. m.getParameterCount(); // 参数列表参数个数
  7. Class[] paramArr = m.getParameterTypes(); // 参数类型列表
  8. Class[] exceptionArr = m.getExceptionTypes(); // 异常列表
  9. m.setAccessible(true); // 若m为私有方法,需要修改可访问性为true
  10. m.invoke(stu, null); // 调用stu对象的m方法,参数为null,返回值为方法的返回值;若方法返回值为void,则返回null
  11. m.invoke(stu, "tom", 12); // 调用stu对象的m方法,参数为tom和12
  12. m.invoke(null, "tom", 12); // 调用静态方法m,参数为tom和12

(三)构造器(java.lang.reflect.Constructor)

  1. Constructor constructor = c.getConstructor(String.class, int.class); // 获取参数列表为(String, int)的public构造器
  2. Modifier.toString(constructor.getModifiers()); // 修饰符
  3. constructor.getName(); // 构造器名
  4. Class[] paramArr = constructor.getParameterTypes(); // 参数类型列表
  5. Class[] exceptionArr = constructor.getExceptionTypes(); // 异常列表
  6. constructor.newInstance("tom",20); // 调用参数列表为(String, int)的构造器获取一个对象
  7. c.newInstance(); // 调用Student的无参构造器,使用无参构造器不需要构造器对象

四、总结

  • 对于私有代码,需要使用getDeclared系列方法获取对象,再使用setAccessible开放可访问性后才可以使用。
  • 对于静态代码,使用时将对应参数位置的对象设为null即可。
  • 使用反射,可以访问私有的代码,破坏了类的封装性。这对于属性和方法是推荐的,但不推荐使用反射创建对象。
  • 在使用字符串获取类、属性和方法对象时,对于能否获取到对应的对象、对象的类型都是未知的,故需要抛异常和强转(若规定了范型)。