Reflection反射
目录
Class类
Class类
三种获取Class对象的方式
动态加载机制
调用构造方法
反射创建对象
Constructor类
继承关系
获取父类的Class
获取实现接口的Class
判断继承关系
访问字段
获取Field 字段
获取字段值
设置字段值
调用方法
Method类
调用方法
调用静态方法
调用非public方法
多态
Class类
Java 反射机制是 Java 语言的一个重要特性。在学习 Java 反射机制前,大家应该先了解两个概念:编译期和运行期。
编译期:是指把源码交给编译器编译成计算机可以执行的文件的过程。在 Java 中也就是把 Java 代码编成 class 文件的过程。编译期只是做了一些翻译功能,并没有把代码放在内存中运行起来,而只是把代码当成文本进行操作。比如:检查语法错误。
运行期:是把编译后的文件交给计算机执行,直到程序运行结束。所谓运行期就把在磁盘中的代码放到内存中执行起来。
Java 反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;
这种动态获取信息以及动态调用对象方法的功能称为 Java 语言的反射机制。简单来说,反射机制指的是程序在运行时能够获取自身的信息。
Class类
要想知道一个类的属性和方法,必须先获取到该类的字节码文件对象。获取类的信息时,使用的就是 Class 类中的方法。所以先要获取到每一个字节码文件( .class )对应的 Class 类型的对象.
class(包括interface)的本质是数据类型(Type)。class是由JVM在执行过程中动态加载的。JVM在第一次读取到一种class类型时,将其加载进内存。每加载一种class,JVM就为其创建一个Class类型的实例,并关联起来。
注意:这里的Class类型是一个名叫Class的class,定义如下:
// final声明不允许继承
public final class Class {// 私有的构造方法private Class() {}
}
以String类为例,当JVM加载String类时,它首先读取String.class文件到内存,然后,为String类创建一个Class实例并关联起来。
一个Class实例包含了该class的所有完整信息。
由于JVM为每个加载的class创建了对应的Class实例,并在实例中保存了该class的所有信息,包括类名、包名、父类、实现的接口、所有方法、字段(成员变量)等,因此,如果获取了某个Class实例,我们就可以通过这个Class实例获取到该实例对应的class的所有信息。这种通过Class实例获取class信息的方法称为反射(Reflection)。
三种获取Class对象的方式
方法一:直接通过一个class的静态变量class获取:
Class cls = String.class;
方法二:如果我们有一个实例变量,可以通过该实例变量提供的getClass()方法获取:
String s = "Hello";
Class cls = s.getClass();
方法三:如果知道一个class的完整类名,可以通过静态方法Class.forName()获取:
Class cls = Class.forName("java.lang.String");
反射的目的是为了获得某个实例的信息。
动态加载机制
JVM在执行Java程序的时候,并不是一次性把所有用到的class全部加载到内存,而是第一次需要用到class时才加载。
例如:当执行Main.java时,由于用到了Main,因此,JVM首先会把Main.class加载到内存。然而,并不会加载Person.class,除非程序执行到create()方法,JVM发现需要加载Person类时,才会首次加载Person.class。如果没有执行create()方法,那么Person.class根本就不会被加载。
public class Main {static {System.out.println("Main被加载");}public static void main(String[] args) {int rand = new Random().nextInt(10);if (rand > 5) {create(rand);}}static void create(int no) {Person p = new Person(no);}
}class Person{static {System.out.println("Person类被加载");}public Person(int no) {System.out.println("Person类的有参构造方法");}
}
调用构造方法
反射创建对象
调用Class提供的newInstance()方法:
Person p = Person.class.newInstance();
调用Class.newInstance()的局限是,它只能调用该类的public无参数构造方法。如果构造方法带有参数,或者不是public,就无法直接通过Class.newInstance()来调用。
Constructor类
为了调用任意的构造方法,Java的反射API提供了Constructor对象,它拥有一个构造方法的所有信息,可以用来创建一个实例。
Constructor对象代表了一个构造方法,调用结果调用结果是用来创建并返回该类型的对象实例:
public class Main {public static void main(String[] args) throws Exception {// 获取构造方法Integer(int):Constructor cons1 = Integer.class.getConstructor(int.class);// 调用构造方法:Integer n1 = (Integer) cons1.newInstance(123);System.out.println(n1);// 获取构造方法Integer(String)Constructor cons2 = Integer.class.getConstructor(String.class);Integer n2 = (Integer) cons2.newInstance("456");System.out.println(n2);}
}
通过Class实例获取Constructor的方法如下:
- getConstructor(Class...):获取某个public的Constructor;
- getDeclaredConstructor(Class...):获取某个定义的Constructor;
- getConstructors():获取所有public的Constructor;
- getDeclaredConstructors():获取所有定义的Constructor。
通过设置setAccessible(true)来访问非public构造方法。
继承关系
获取父类的Class
有了Class实例,我们还可以获取它的父类的Class:
public class Main {public static void main(String[] args) throws Exception {Class i = Integer.class;Class n = i.getSuperclass();System.out.println(n);Class o = n.getSuperclass();System.out.println(o);System.out.println(o.getSuperclass());}
}
运行上述代码,可以看到,Integer的父类类型是Number,Number的父类是Object,Object的父类是null。除Object外,其他任何非interface的Class都必定存在一个父类类型。
获取实现接口的Class
由于一个类可能实现一个或多个接口,通过 Class 我们就可以查询到实现的接口类型。例如:查询 Integer 实现的接口:
public class Main {public static void main(String[] args) throws Exception {Class s = Integer.class;Class[] is = s.getInterfaces();for (Class i : is) {System.out.println(i);}}
}
要特别注意:getInterfaces()只返回当前类直接实现的接口类型,并不包括其父类实现的接口类型。
此外,对所有interface的Class调用getSuperclass()返回的是null,获取接口的父接口要用getInterfaces()。如果一个类没有实现任何interface,那么getInterfaces()返回空数组。
判断继承关系
当我们判断一个实例是否是某个类型时,正常情况下,使用instanceof操作符:
Object n = Integer.valueOf(123);
boolean isDouble = n instanceof Double; // false
boolean isInteger = n instanceof Integer; // true
boolean isNumber = n instanceof Number; // true
boolean isSerializable = n instanceof java.io.Serializable; // true
如果是两个Class实例,要判断一个向上转型是否成立,可以调用isAssignableFrom():
// Integer i = ?
Integer.class.isAssignableFrom(Integer.class); // true,因为Integer可以赋值给Integer // Number n = ?Number.class.isAssignableFrom(Integer.class); // true,因为Integer可以赋值给Number // Object o = ?
Object.class.isAssignableFrom(Integer.class); // true,因为Integer可以赋值给Object // Integer i = ?
Integer.class.isAssignableFrom(Number.class); // false,因为Number不能赋值给Integer
访问字段
获取Field 字段
对任意的一个Object实例,只要我们获取了它的Class,就可以获取它的一切信息。比如通过一个Class实例获取字段信息。Class类提供了以下几个方法来获取字段:
- Field getField(name):根据字段名获取当前类中某个public的field(包括父类)
- Field getDeclaredField(name):根据字段名获取当前类中定义的某个field(不包括父类)
- Field[] getFields():获取所有public的field(包括父类)
- Field[] getDeclaredFields():获取当前类中定义的所有field(不包括父类)
public class Main {public static void main(String[] args) throws Exception {Class stdClass = Student.class;// 获取public字段"score":System.out.println(stdClass.getField("score"));// 获取继承的public字段"name":System.out.println(stdClass.getField("name"));// 获取private字段"grade":System.out.println(stdClass.getDeclaredField("grade"));}
}class Student extends Person {public int score;private int grade;
}class Person {public String name;
}
一个Field对象包含了一个字段的所有信息:
- getName():返回字段名称,例如,"name";
- getType():返回字段类型,也是一个Class实例,例如,String.class;
- getModifiers():返回字段的修饰符,它是一个int,不同的bit表示不同的含义。
获取字段值
利用反射拿到字段的一个Field实例只是第一步,我们还可以拿到一个实例对应的该字段的值。例如,对于一个Person实例,我们可以先拿到name字段对应的Field,再获取这个实例的name字段的值:
public class Main {public static void main(String[] args) throws Exception {Object p = new Person("贝吉塔");Class c = p.getClass();Field f = c.getDeclaredField("name");Object value = f.get(p);System.out.println(value); // "贝吉塔"}
}class Person {private String name;public Person(String name) {this.name = name;}
}
运行代码,如果不出意外,会得到一个IllegalAccessException,这是因为name被定义为一个private字段,正常情况下,Main类无法访问Person类的private字段。要修复错误,可以将private改为public。或者,在调用Object value = f.get(p);前执行f.setAccessible(true);
调用setAccessible(true)的意思是,不管这个字段是不是public,一律允许访问。
设置字段值
通过Field实例既然可以获取到指定实例的字段值,自然也可以设置字段的值。设置字段值是通过Field.set(Object, Object)实现的,其中第一个Object参数是指定的实例,第二个Object参数是待修改的值。示例代码如下:
public class Main {public static void main(String[] args) throws Exception {Person p = new Person("武松");System.out.println(p.getName()); // 武松Class c = p.getClass();Field f = c.getDeclaredField("name");f.setAccessible(true);f.set(p, "宋江");System.out.println(p.getName()); // 宋江}
}class Person {private String name;public Person(String name) {this.name = name;}public String getName() {return this.name;}
}
调用方法
Method类
我们已经能通过Class实例获取所有Field对象,同样的,可以通过Class实例获取所有方法(Method类型的对象)。Class类提供了以下几个方法来获取Method:
- Method getMethod(name, Class...):获取某个public的Method(包括父类)
- Method getDeclaredMethod(name, Class...):获取当前类的某个Method(不包括父类)
- Method[] getMethods():获取所有public的Method(包括父类)
Method[] getDeclaredMethods():获取当前类的所有Method(不包括父类)
public class Main {public static void main(String[] args) throws Exception {Class stdClass = Student.class;// 获取public方法getScore,参数为String:System.out.println(stdClass.getMethod("getScore", String.class));// 获取继承的public方法getName,无参数:System.out.println(stdClass.getMethod("getName"));// 获取private方法getGrade,参数为int:System.out.println(stdClass.getDeclaredMethod("getGrade", int.class));}
}class Student extends Person {public int getScore(String type) {return 99;}private int getGrade(int year) {return 1;}
}class Person {public String getName() {return "Person";}
}
一个Method对象包含一个方法的所有信息:
- getName():返回方法名称,例如:"getScore";
- getReturnType():返回方法返回值类型,也是一个Class实例,例如:String.class;
- getParameterTypes():返回方法的参数类型,是一个Class数组,例如:{String.class, int.class};
- getModifiers():返回方法的修饰符,它是一个int,不同的value表示不同的访问修饰符;
调用方法
public class Main {public static void main(String[] args) throws Exception {// String对象:String s = "Hello world";// 获取String substring(int)方法,参数为int:Method m = String.class.getMethod("substring", int.class);// 在s对象上调用该方法并获取结果:String r = (String) m.invoke(s, 6);// 打印调用结果:System.out.println(r);}
}
调用静态方法
如果获取到的Method表示一个静态方法,调用静态方法时,由于无需指定实例对象,所以invoke方法传入的第一个参数永远为null。我们以Integer.parseInt(String)为例:
public class Main {
public static void main(String[] args) throws Exception { // 获取Integer.parseInt(String)方法,参数为String: Method m = Integer.class.getMethod("parseInt", String.class); // 调用该静态方法并获取结果: Integer n = (Integer) m.invoke(null, "12345"); // 打印调用结果: System.out.println(n); } }
调用非public方法
和Field类似,对于非public方法,我们虽然可以通过Class.getDeclaredMethod()获取该方法实例,但直接对其调用将得到一个IllegalAccessException。为了调用非public方法,我们通过Method.setAccessible(true)允许其调用
public class Main {public static void main(String[] args) throws Exception {Person p = new Person();Method m = p.getClass().getDeclaredMethod("setName", String.class);m.setAccessible(true);m.invoke(p, "Bob");System.out.println(p.name);}
}class Person {String name;private void setName(String name) {this.name = name;}
}
多态
我们来考察这样一种情况:一个Person类定义了hello()方法,并且它的子类Student也重写了hello()方法,那么,从Person.class获取的Method,作用于Student实例时,调用的方法到底是哪个?
public class Main {public static void main(String[] args) throws Exception {// 获取Person的hello方法:Method h = Person.class.getMethod("hello");// 对Student实例调用hello方法:h.invoke(new Student());}
}class Person {public void hello() {System.out.println("Person:hello");}
}class Student extends Person {public void hello() {System.out.println("Student:hello");}
}
运行上述代码,发现打印出的是Student:hello,因此,使用反射调用方法时,仍然遵循多态原则:即总是调用实际类型的重写方法(如果存在)