本文从Class类、访问Field变量、调用方法、调用构造方法与获取继承关系来阐述Java反射原理与实现。
Class类
1.除了int
等基本类型外,Java的其他类型全部都是class
(包括interface
)。
class
是由JVM在执行过程中动态加载的。JVM在第一次读取到一种class
类型时,将其加载进内存。
2.每加载一种class
,JVM就为其创建一个Class
类型的实例,并关联起来。注意:这里的Class
类型是一个名叫Class
的class
。它长这样:
public final class Class {
private Class() {}
}
以String
类为例,当JVM加载String
类时,它首先读取String.class
文件到内存,然后,为String
类创建一个Class
实例并关联起来:
Class cls = new Class(String);
3.反射
通过Class
实例获取class
信息的方法称为反射(Reflection)。 反射的目的是为了获得某个实例的信息。
4.获取Class实例的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");
5.JVM在执行Java程序的时候,并不是一次性把所有用到的class全部加载到内存,而是第一次需要用到class时才加载。
// Main.java
public class Main {
public static void main(String[] args) {
if (args.length > 0) {
create(args[0]);
}
}
static void create(String name) {
Person p = new Person(name);
}
}
当执行Main.java
时,由于用到了Main
,因此,JVM首先会把Main.class
加载到内存。然而,并不会加载Person.class
,除非程序执行到create()
方法,JVM发现需要加载Person
类时,才会首次加载Person.class
。如果没有执行create()
方法,那么Person.class
根本就不会被加载。
这就是JVM动态加载class
的特性。
访问Field成员变量
1.获取field成员变量实例
- Field getField(name):根据字段名获取某个public的field(包括父类)
- Field getDeclaredField(name):根据字段名获取当前类的某个field(不包括父类)
- Field[] getFields():获取所有public的field(包括父类)
- Field[] getDeclaredFields():获取当前类的所有field(不包括父类)
public class ReflectionFindField {
public static void main(String[] args) throws NoSuchFieldException {
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;
}
输出:
public int Java_basic.reflection.Student.score
public java.lang.String Java_basic.reflection.Person.name
private int Java_basic.reflection.Student.grade
2.Field实例对象
一个Field
对象包含了一个字段的所有信息:
getName()
:返回字段名称,例如,"name"
;getType()
:返回字段类型,也是一个Class
实例,例如String.class
;getModifiers()
:返回字段的修饰符,它是一个int
,不同的bit表示不同的含义。
以String
类的value
字段为例,它的定义是:
public final class String {
private final byte[] value;
}
我们用反射获取该字段的信息,代码如下:
Field f = String.class.getDeclaredField("value");
f.getName(); // "value"
f.getType(); // class [B 表示byte[]类型
int m = f.getModifiers();
Modifier.isFinal(m); // true
Modifier.isPublic(m); // false
Modifier.isProtected(m); // false
Modifier.isPrivate(m); // true
Modifier.isStatic(m); // false
3.获取字段值
利用反射拿到字段的一个Field
实例只是第一步,我们还可以拿到一个实例对应的该字段的值。通过Field.get(Object)实现。
4.设置字段值
设置字段值是通过Field.set(Object, Object)
实现的,其中第一个Object
参数是指定的实例,第二个Object
参数是待修改的值。
public class Main {
public static void main(String[] args) throws Exception {
Person p = new Person("Xiao Ming");
System.out.println(p.getName()); // "Xiao Ming"
Class c = p.getClass();
Field f = c.getDeclaredField("name");
//修改非public字段,需要首先调用setAccessible(true)。
f.setAccessible(true);
f.set(p, "Xiao Hong");
System.out.println(p.getName()); // "Xiao Hong"
}
}
class Person {
private String name;
public Person(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
}
调用方法
1.Class
类提供了以下几个方法来获取Method
:
Method getMethod(name, Class...)
:获取某个public
的Method
(包括父类)Method getDeclaredMethod(name, Class...)
:获取当前类的某个Method
(不包括父类)Method[] getMethods()
:获取所有public
的Method
(包括父类)Method[] getDeclaredMethods()
:获取当前类的所有Method
(不包括父类)
public class ReflectMethod {
public static void main(String[] args) throws NoSuchMethodException {
Class stdClass = Student_Method.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_Method extends Person_Method {
public int getScore(String type) {
return 99;
}
private int getGrade(int year) {
return 1;
}
}
class Person_Method {
public String getName() {
return "Person";
}
}
输出:
public int Student_Method.getScore(java.lang.String)
public java.lang.String Person_Method.getName()
private int Student_Method.getGrade(int)
2.Method对象
一个Method
对象包含一个方法的所有信息:
getName()
:返回方法名称,例如:"getScore"
;getReturnType()
:返回方法返回值类型,也是一个Class实例,例如:String.class
;getParameterTypes()
:返回方法的参数类型,是一个Class数组,例如:{String.class, int.class}
;getModifiers()
:返回方法的修饰符,它是一个int
,不同的bit表示不同的含义。
3.调用方法
(1)当我们获取到一个Method
对象时,就可以对它进行调用。我们以下面的代码为例:
String s = "Hello world";
String r = s.substring(6); // "world"
如果用反射来调用substring
方法,需要以下代码:
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);
}
}
(2)当调用的方法是静态方法时,invoke的第一个参数永远为null
调用构造方法
1.如果通过反射来创建新的实例,可以调用Class提供的newInstance()方法:
Person p = Person.class.newInstance();
注:调用Class.newInstance()的局限是,它只能调用该类的public无参数构造方法。如果构造方法带有参数,或者不是public,就无法直接通过Class.newInstance()来调用。
2.调用任意构造方法
Java的反射API提供了Constructor对象,它包含一个构造方法的所有信息,可以创建一个实例。Constructor对象和Method非常类似,不同之处仅在于它是一个构造方法,并且,调用结果总是返回实例。
public class Constructor {
public static void main(String[] args) throws Exception {
//获取构造方法Integer(int):
java.lang.reflect.Constructor<Integer> cons1 = Integer.class.getConstructor(int.class);
//调用构造方法
Integer n1=(Integer)cons1.newInstance(123);
System.out.println(n1);
// 获取构造方法Integer(String)
java.lang.reflect.Constructor<Integer> cons2 = Integer.class.getConstructor(String.class);
Integer n2 = (Integer) cons2.newInstance("456");
System.out.println(n2);
}
}
3.通过Class实例获取Constructor的方法如下:
getConstructor(Class...)
:获取某个public
的Constructor
;getDeclaredConstructor(Class...)
:获取某个Constructor
;getConstructors()
:获取所有public
的Constructor
;getDeclaredConstructors()
:获取所有Constructor
。
注:Constructor
总是当前类定义的构造方法,和父类无关,因此不存在多态的问题。
获取继承关系
1.获取父类的Class
Class i = Integer.class;
Class n = i.getSuperclass(); //n= class java.lang.Number
2.获取interface
由于一个类可能实现一个或多个接口,通过Class
我们就可以查询到实现的接口类型。
注:getInterfaces()`只返回当前类直接实现的接口类型,并不包括其父类实现的接口类型!
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);
}
}
}
输出:
interface java.lang.Comparable
interface java.lang.constant.Constable
interface java.lang.constant.ConstantDesc
3.继承关系
(1) 当我们判断一个实例是否是某个类型时,正常情况下,使用instanceof
操作符
Object n = Integer.valueOf(123);
boolean isDouble = n instanceof Integer; // true
(2) 如果是两个Class
实例,要判断一个向上转型是否成立,可以调用isAssignableFrom()
// 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
4.动态代理(Dynamic Proxy)的机制:可以在运行期动态创建某个interface
的实例。