反射原理
反射
1. Class 类
除了int
等基本类型外,Java的其他类型全部都是class
(包括interface
)。例如:
String
Object
Runnable
Exception
- ...
以String
类为例,当JVM加载String
类时,它首先读取String.class
文件到内存,然后,为String
类创建一个Class
实例并关联起来:
Class cls = new Class(String);
VM持有的每个Class
实例都指向一个数据类型(class
或interface
):
一个Class
实例包含了该class
的所有完整信息:
通过Class
实例获取class
信息的方法称为反射(Reflection)。一共有三种方法可获取到 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
实例,我们就可以通过该Class
实例来创建对应类型的实例:
// 获取String的Class实例:
Class cls = String.class;
// 创建一个String实例:
String s = (String) cls.newInstance();
上述代码相当于new String()
。通过Class.newInstance()
可以创建类实例,它的局限是:只能调用public
的无参数构造方法。带参数的构造方法,或者非public
的构造方法都无法通过Class.newInstance()
被调用。
instance 与 class 实例的区别
Integer n = new Integer(123);
boolean b1 = n instanceof Integer; // true,因为n是Integer类型
boolean b2 = n instanceof Number; // true,因为n是Number类型的子类
boolean b3 = n.getClass() == Integer.class; // true,因为n.getClass()返回Integer.class
boolean b4 = n.getClass() == Number.class; // false,因为Integer.class!=Number.class
用instanceof
不但匹配指定类型,还匹配指定类型的子类。而用==
判断class
实例可以精确地判断数据类型,但不能作子类型比较。
动态加载
JVM在执行Java程序的时候,并不是一次性把所有用到的class全部加载到内存,而是第一次需要用到class时才加载。
2. 访问字段
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表示不同的含义。
总结:
-
通过
Class
实例的方法可以获取Field
实例:getField()
,getFields()
,getDeclaredField()
,getDeclaredFields()
; -
通过Field实例可以获取字段信息:
getName()
,getType()
,getModifiers()
; -
通过Field实例可以读取或设置某个对象的字段,如果存在访问限制,要首先调用
setAccessible(true)
来访问非public
字段。 -
通过反射读写字段是一种非常规方法,它会破坏对象的封装。
3. 调用方法
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
,不同的bit表示不同的含义。
- 调用方法
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);
}
}
- 调用静态方法
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);
}
}
总结:
-
通过
Class
实例的方法可以获取Method
实例:getMethod()
,getMethods()
,getDeclaredMethod()
,getDeclaredMethods()
; -
通过
Method
实例可以获取方法信息:getName()
,getReturnType()
,getParameterTypes()
,getModifiers()
; -
通过
Method
实例可以调用某个对象的方法:Object invoke(Object instance, Object... parameters)
; -
通过设置
setAccessible(true)
来访问非public
方法; -
通过反射调用方法时,仍然遵循多态原则。
4. 调用构造方法
为了调用任意的构造方法,Java的反射API提供了Constructor对象,它包含一个构造方法的所有信息,可以创建一个实例。Constructor对象和Method非常类似,不同之处仅在于它是一个构造方法,并且,调用结果总是返回实例:
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
。
总结:
-
通过
Class
实例的方法可以获取Constructor
实例:getConstructor()
,getConstructors()
,getDeclaredConstructor()
,getDeclaredConstructors()
; -
通过
Constructor
实例可以创建一个实例对象:newInstance(Object... parameters)
; -
通过设置
setAccessible(true)
来访问非public
构造方法。
5. 获取继承关系
- 获取父类 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());
}
}
- 获取 interface
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);
}
}
}
总结:
通过Class
对象可以获取继承关系:
Class getSuperclass()
:获取父类类型;Class[] getInterfaces()
:获取当前类实现的所有接口。
通过Class
对象的isAssignableFrom()
方法可以判断一个向上转型是否可以实现。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· winform 绘制太阳,地球,月球 运作规律
· 上周热点回顾(3.3-3.9)
2021-07-01 Kafka Range RoundRobin 和Sticky 三种 分区分配策略