反射是为了解决在运行期,对某个实例一无所知的情况下,如何调用其方法。

在编译时,编译器必须知道所有要通过RTTI来处理的类.

Class类与java.lang.reflect类库一起对反射的概念进行了支持,该类库包含了Field,Method以及Constructor类(每个类都实现了Member接口).这些类型的对象是由JVM在运行时创建的,用以表示未知类里对应的成员.
如此便可以
通过Constructor创建新的对象,
用get()和set()方法读取和修改与Field对象关联的字段,
用invoke()方法调用与Method对象关联的方法.

调用getFields(),getMethods()和getConstructors()等以返回表示字段,方法以及构造器的对象的数组.

如此,匿名对象的类信息便能在运行时被完全确定下来,而在编译时不需要知道任何事情.

使用反射时那个类的对于JVM来说必须是可获取的:要么在本地机器上,要么可以通过网络取得.

对于RTTI,编译器在编译时打开和检查.class(换句话说,我们可以用”普通”方式调用对象的所有方法)

对于反射机制来说,.class文件在编译时是不可获取的,所以是在运行时打开和检查.class文件.

访问字段

对任意的一个Object实例,只要我们获取了它的Class,就可以获取它的一切信息。
我们先看看如何通过Class实例获取字段信息。Class类提供了以下几个方法来获取字段:

  • Field getField(name):根据字段名获取某个public的field(包括父类)
  • Field getDeclaredField(name):根据字段名获取当前类的某个field(不包括父类)
  • Field[] getFields():获取所有public的field(包括父类)
  • Field[] getDeclaredFields():获取当前类的所有field(不包括父类)
  1. public class Main {
  2. public static void main(String[] args) throws Exception {
  3. Class stdClass = Student.class;
  4. // 获取public字段"score":
  5. System.out.println(stdClass.getField("score"));
  6. // 获取继承的public字段"name":
  7. System.out.println(stdClass.getField("name"));
  8. // 获取private字段"grade":
  9. System.out.println(stdClass.getDeclaredField("grade"));
  10. }
  11. }
  12. class Student extends Person {
  13. public int score;
  14. private int grade;
  15. }
  16. class Person {
  17. public String name;
  18. }

一个Field对象包含了一个字段的所有信息:

  • getName():返回字段名称,例如,”name”;
  • getType():返回字段类型,也是一个Class实例,例如,String.class;
  • getModifiers():返回字段的修饰符,它是一个int,不同的bit表示不同的含义。

获取字段值

  1. public class Main {
  2. public static void main(String[] args) throws Exception {
  3. Object p = new Person("Xiao Ming");
  4. Class c = p.getClass();
  5. Field f = c.getDeclaredField("name");
  6. Object value = f.get(p);
  7. System.out.println(value); // "Xiao Ming"
  8. }
  9. }
  10. class Person {
  11. private String name;
  12. public Person(String name) {
  13. this.name = name;
  14. }
  15. }
  16. 运行代码,如果不出意外,会得到一个IllegalAccessException,这是因为name被定义为一个private字段,
  17. 正常情况下,Main类无法访问Person类的private字段。
  18. 要修复错误,可以将private改为public,或者,在调用Object value = f.get(p);前,先写一句:
  19. f.setAccessible(true);

设置字段值

通过Field实例既然可以获取到指定实例的字段值,自然也可以设置字段的值。

设置字段值是通过Field.set(Object, Object)实现的,其中第一个Object参数是指定的实例,第二个Object参数是待修改的值。

  1. public class Main {
  2. public static void main(String[] args) throws Exception {
  3. Person p = new Person("Xiao Ming");
  4. System.out.println(p.getName()); // "Xiao Ming"
  5. Class c = p.getClass();
  6. Field f = c.getDeclaredField("name");
  7. f.setAccessible(true);
  8. f.set(p, "Xiao Hong");
  9. System.out.println(p.getName()); // "Xiao Hong"
  10. }
  11. }
  12. class Person {
  13. private String name;
  14. public Person(String name) {
  15. this.name = name;
  16. }
  17. public String getName() {
  18. return this.name;
  19. }
  20. }

调用方法

  1. public class Main {
  2. public static void main(String[] args) throws Exception {
  3. Class stdClass = Student.class;
  4. // 获取public方法getScore,参数为String:
  5. System.out.println(stdClass.getMethod("getScore", String.class));
  6. // 获取继承的public方法getName,无参数:
  7. System.out.println(stdClass.getMethod("getName"));
  8. // 获取private方法getGrade,参数为int:
  9. System.out.println(stdClass.getDeclaredMethod("getGrade", int.class));
  10. }
  11. }
  12. class Student extends Person {
  13. public int getScore(String type) {
  14. return 99;
  15. }
  16. private int getGrade(int year) {
  17. return 1;
  18. }
  19. }
  20. class Person {
  21. public String getName() {
  22. return "Person";
  23. }
  24. }

一个Method对象包含一个方法的所有信息:

  • getName():返回方法名称,例如:”getScore”;
  • getReturnType():返回方法返回值类型,也是一个Class实例,例如:String.class;
  • getParameterTypes():返回方法的参数类型,是一个Class数组,例如:{String.class, int.class};
  • getModifiers():返回方法的修饰符,它是一个int,不同的bit表示不同的含义。

调用普通方法

当我们获取到一个Method对象时,就可以对它进行调用。我们以下面的代码为例:

  1. String s = "Hello world";
  2. String r = s.substring(6); // "world"

通过反射

  1. public class Main {
  2. public static void main(String[] args) throws Exception {
  3. // String对象:
  4. String s = "Hello world";
  5. // 获取String substring(int)方法,参数为int:
  6. Method m = String.class.getMethod("substring", int.class);
  7. // 在s对象上调用该方法并获取结果:
  8. String r = (String) m.invoke(s, 6);
  9. // 打印调用结果:
  10. System.out.println(r);
  11. }
  12. }

对Method实例调用invoke就相当于调用该方法,invoke的第一个参数是对象实例,即在哪个实例上调用该方法,后面的可变参数要与方法参数一致,否则将报错。

调用静态方法

如果获取到的Method表示一个静态方法,调用静态方法时,由于无需指定实例对象,所以invoke方法传入的第一个参数永远为null。我们以Integer.parseInt(String)为例:

  1. public class Main {
  2. public static void main(String[] args) throws Exception {
  3. // 获取Integer.parseInt(String)方法,参数为String:
  4. Method m = Integer.class.getMethod("parseInt", String.class);
  5. // 调用该静态方法并获取结果:
  6. Integer n = (Integer) m.invoke(null, "12345");
  7. // 打印调用结果:
  8. System.out.println(n);
  9. }
  10. }

image.png

调用非public方法

xxx.setAccessible(true);

调用构造方法

如果通过反射来创建新的实例,可以调用Class提供的newInstance()方法:
Person p = Person.**class**.newInstance();
调用Class.newInstance()的局限是,它只能调用该类的public无参数构造方法。如果构造方法带有参数,或者不是public,就无法直接通过Class.newInstance()来调用。

为了调用任意的构造方法,Java的反射API提供了Constructor对象,它包含一个构造方法的所有信息,可以创建一个实例。Constructor对象和Method非常类似,不同之处仅在于它是一个构造方法,并且,调用结果总是返回实例:

  1. public class Main {
  2. public static void main(String[] args) throws Exception {
  3. // 获取构造方法Integer(int):
  4. Constructor cons1 = Integer.class.getConstructor(int.class);
  5. // 调用构造方法:
  6. Integer n1 = (Integer) cons1.newInstance(123);
  7. System.out.println(n1);
  8. // 获取构造方法Integer(String)
  9. Constructor cons2 = Integer.class.getConstructor(String.class);
  10. Integer n2 = (Integer) cons2.newInstance("456");
  11. System.out.println(n2);
  12. }
  13. }

通过Class实例获取Constructor的方法如下:

  • getConstructor(Class…):获取某个public的Constructor;
  • getDeclaredConstructor(Class…):获取某个Constructor;
  • getConstructors():获取所有public的Constructor;
  • getDeclaredConstructors():获取所有Constructor。

注意Constructor总是当前类定义的构造方法,和父类无关,因此不存在多态的问题。
调用非public的Constructor时,必须首先通过setAccessible(true)设置允许访问。setAccessible(true)可能会失败。