什么是反射?
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
反射的作用
在运行时,通过反射可以判断任意一个对象所属的类、构造任意一个类的对象、判断任意一个类所具有的成员变量和方法、调用任意一个对象的方法以及给任意一个类的属性设值等等。
Class类
在Java中,除了int
等基本数据类型以外,其他的类型全部都是class
(包括interface
)。
class
是由JVM在运行状态中动态加载的,JVM第一次读取到class
类型时,就将它加入到内存。每加载一种class
,JVM就为它创建一个Class
类型的实例,并关联起来。
例如String
类型,JVM加载String
类型时,先读取String.class
字节码文件到内存,并创建一个与String
类关联的Class
实例。
通过查看源码可以知道,Class
类型的构造方法是用了private
修饰,因此,只有JVM才能够创建实例,自己的程序是无法创建的。
由于JVM为每个加载的class
创建了对应的Class
实例,并在实例中保存了该class
的所有信息,包括类名、包名、父类、实现的接口、所有方法、字段等,因此,如果获取了某个Class
实例,我们就可以通过这个Class
实例获取到该实例对应的class
的所有信息。
获取Class
实例的三种方法
- 通过一个类的静态变量
class
获取:
Class cls = String.class;
- 通过实例变量提供的getClass()方法获取:
String s = "Hello";
Class cls = s.getClass();
- 知道完整类名,通过静态方法Class.forName()获取
Class cls = Class.forName("java.lang.String");
当获取到Class实例,就可以通过该实例获取对应类型的实例:
// 获取String的Class实例
Class cls = String.class;
// 创建一个String实例
//相当于 new String()
String s = (String) cls.newInstance();
注意:通过Class.newInstance()
可以创建类实例,它的局限是:只能调用public
的无参数构造方法。带参数的构造方法,或者非public的构造方法都无法通过Class.newInstance()
被调用。
动态加载
JVM在执行Java程序的时候,并不是一次性把所有用到的class
全部加载到内存,而是第一次需要用到class
时才加载。利用JVM动态加载class
的特性,我们可以在运行期间根据条件加载不同的实现类。
访问字段
Class
类提供以下几个方法来获取相应的字段:
Field getField(name)
:根据字段名获取某个public的field(包括父类)Field getDeclaredField(name)
:根据字段名获取当前类的某个field(不包括父类)Field[] getFields()
:获取所有public的field(包括父类)Field[] getDeclaredFields()
:获取当前类的所有field(不包括父类)
package edu.gzc;
public class Main {
public static void main(String[] args) throws NoSuchFieldException {
// write your code here
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 Persion{
public int score;
private int grade;
}
class Persion{
public String name;
}
输出:
一个Field
对象包含了一个字段的所有信息:
getName()
:返回字段名称,例如,“name”;getType()
:返回字段类型,也是一个Class实例,例如,String.class;getModifiers()
:返回字段的修饰符,它是一个int,不同的bit表示不同的含义。
通过Field
对象获取字段信息
Field score = stdClass.getDeclaredField("score");
System.out.println(score.getName()); //输出 score
System.out.println(score.getType()); ///输出 int
//获取字段修饰符
int m =score.getModifiers();
System.out.println(Modifier.isPublic(m)); // 输出 true
获取字段值和设置字段值
先获取Class
实例,再获取Field
实例:
- 用
Field.get(Object)
获取指定实例的指定字段的值。 - 用
Field.set(Object, Object)
实现设置字段值,其中第一个Object
参数是指定的实例,第二个Object
参数是待修改的值。
public class Main {
public static void main(String[] args) throws Exception {
// write your code here
//获取字段值
Persion p = new Persion("xiaoming");
Class c = p.getClass();
Field f = c.getDeclaredField("name");
//不管字段是public,还是private,都允许访问
f.setAccessible(true);
Object value = f.get(p);
System.out.println(value); // 输出 xiaoming
//设置字段值
f.set(p, "zhangsan");
System.out.println(p.getName()); //输出 zhangsan
}
}
class Persion {
public String name;
public Persion(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
如果获取非public
字段,会抛出IllegalAccessException
异常,可以将private
修改为public
,或者在得到Field
对象后,设置访问权限:f.setAccessible(true);
。
但该方法也可能会失败,如果JVM运行期存在SecurityManager
,那么它会根据规则进行检查,有可能阻止setAccessible(true)
。
调用方法
通过Class
实例获取所有Method
信息。Class
类提供了以下几个方法来获取Method
:
Method getMethod(name, Class...)
:获取某个public
的Method
(包括父类)Method getDeclaredMethod(name, Class...)
:获取当前类的某个Method(不包括父类)Method[] getMethods()
:获取所有public的Method(包括父类)Method[] getDeclaredMethods()
:获取当前类的所有Method(不包括父类)
package edu.gzc;
public class Main {
public static void main(String[] args) throws Exception {
Class stuClass=Student.class;
//获取public 方法setScore,参数为int
System.out.println(stuClass.getDeclaredMethod("setScore", int.class));;
//获取继承的public方法getName,无参数
System.out.println(stuClass.getMethod("getName"));
}
}
class Student extends Persion {
private int score;
private int grade;
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
}
class Persion {
private String name;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
输出:
一个Method
对象包含一个方法的所有信息:
getName()
:返回方法名称,例如:”getScore
“;getReturnType()
:返回方法返回值类型,也是一个Class
实例,例如:String.class;getParameterTypes()
:返回方法的参数类型,是一个Class
数组,例如:{String.class, int.class}
;getModifiers()
:返回方法的修饰符,它是一个int
,不同的bit表示不同的含义。
调用方法
对Method
实例调用invoke
就相当于调用该方法,invoke
的第一个参数是对象实例,即在哪个实例上调用该方法,后面的可变参数要与方法参数一致,否则将报错。
方法:Object invoke(Object instance, Object... parameters)
import java.lang.reflect.Method;
public class Test {
public static void main(String[] args) throws Exception {
String str = "hello world";
// 获取String substring(int)方法,参数为int:
Method m =String.class.getDeclaredMethod("substring", int.class);
// 在str对象上调用该方法并获取结果:
String result= (String) m.invoke(str, 6);
// 打印调用结果:
System.out.println(result); //输出 world
}
}
调用静态方法
如果获取到的Method
表示一个静态方法,调用静态方法时,由于无需指定实例对象,所以invoke
方法传入的第一个参数永远为null
。
例如 :
Integer n = (Integer) m.invoke(null, "12345");
调用非public方法
通过设置setAccessible(true)
来访问非public
方法。setAccessible(true)
可能会失败。
注意:使用反射调用方法时,仍然遵循多态原则。
调用构造方法
使用反射来创建实例,则需要调用 Class
提供的newInstance()
方法。
Person p = Person.class.newInstance();
要注意的是,newInstance()
只能调用该类无参的public的无参构造方法。
那么如何调用任意构造方法呢???
为了调用任意的构造方法,Java的反射API提供了Constructor
对象,它包含一个构造方法的所有信息,可以创建一个实例。
通过Class实例获取Constructor
的方法如下:
getConstructor(Class...)
:获取某个public
的Constructor
;getDeclaredConstructor(Class...)
:获取某个Constructor
;getConstructors()
:获取所有public
的Constructor
;getDeclaredConstructors()
:获取所有Constructor
。
调用非public的Constructor时,必须首先通过setAccessible(true)设置允许访问。setAccessible(true)可能会失败。
import java.lang.reflect.Constructor;
public class Test {
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); // 输出 123
// 获取构造方法Integer(String)
Constructor cons2 = Integer.class.getConstructor(String.class);
Integer n2 = (Integer) cons2.newInstance("456");
System.out.println(n2); // 输出 456
}
获取继承关系
获取父类的Class:
通过Class
提供的Class getSuperclass()
方法可以获取父类的Class
。
获取interface:
- 通过
Class
提供的Class[] getInterfaces()
方法可以获取该类实现的接口的Class
。 getInterfaces()
只返回当前类直接实现的接口类型,并不包括其父类实现的接口类型。- 对所有
interface
的Class
调用getSuperclass()
返回的是null
,获取接口的父接口要用getInterfaces()
。 - 如果一个类没有实现任何
interface
,那么getInterfaces()
返回空数组。 - 两个
Class
实例,要判断一个向上转型是否成立,可以调用isAssignableFrom()
。
参考文章:https://www.liaoxuefeng.com/wiki/1252599548343744/1264799402020448