反射
一、反射
通过Class实例获取class
信息的方法称为反射(Reflection)
-
JVM为每个加载的
class
及interface
创建了对应的Class
实例来保存class
及interface
的所有信息 -
获取一个
class
对应的Class
实例后,就可以获取该class
的所有信息;
二、Class类及其实例
1、Class类
Java程序在运行时,Java运行时系统一直对所有的对象进行所谓的运行时类型标识,即所谓的RTTI。这项信息纪录了每个对象所属的类。虚拟机通常使用运行时类型信息选准正确方法去执行,用来保存这些类型信息的类是Class类。Class类封装一个对象和接口运行时的状态,当装载类时,Class类型的对象自动创建。
简单解释上面一段话:
1、Class类也是类的一种,只是名字和class关键字高度相似。Java是大小写敏感的语言。
2、Class类的对象内容是你创建的类的类型信息,比如你创建一个shapes类,那么,Java会生成一个内容是shapes的Class类的对象。
3、Class类的对象不能像普通类一样,以 new shapes() 的方式创建,它的对象只能由JVM创建,因为这个类没有public构造函数。
4、Class类的作用是运行时提供或获得某个对象的类型信息。
2、获取Class实例
1、Class类的静态方法forName
// 如果知道一个class的完整类名,可以通过静态方法Class.forName()获取
Class cls = Class.forName( String className);
Class cls = Class.forName("java.lang.String");
2、对象的getClass()方法
// 如果我们有一个实例变量,可以通过该实例变量提供的getClass()方法获取
Student student = new Student();
Class<? extends Student> aClass1 = student.getClass();
3、使用类名加.class
// 直接通过一个class的静态变量class获取
Class classes = Student.class;
3、获取基本信息
public class Main {
public static void main(String[] args) {
printClassInfo("".getClass());
printClassInfo(Runnable.class);
printClassInfo(java.time.Month.class);
printClassInfo(String[].class);
printClassInfo(int.class);
}
static void printClassInfo(Class cls) {
System.out.println("Class name: " + cls.getName());
System.out.println("Simple name: " + cls.getSimpleName());
if (cls.getPackage() != null) {
System.out.println("Package name: " + cls.getPackage().getName());
}
System.out.println("is interface: " + cls.isInterface());
System.out.println("is enum: " + cls.isEnum());
System.out.println("is array: " + cls.isArray());
System.out.println("is primitive: " + cls.isPrimitive());
System.out.println("---------------------------------------------");
}
}
三、Class实例操作class的成员变量
public class Student{
private String name;
private int age;
public Student(){}
public Student(String name) {
this.name = name;
}
}
public class Main{
public static void main(String[] args){
...
}
}
1、获取类的变量
注意:
getField(name)
与getDeclaredField(name)
中 name 可能不存在,源码中用了throws来抛出可能出现的异常。所以在使用时,必须用try...catch...
来捕获异常
Field getField(name) // 根据字段名获取包括父类字段在内的某个public的field()
Field getDeclaredField(name) // 根据字段名获取当前类的某个field(不包括父类)
Field[] getFields() // 获取包括父类在内的所有public的field,返回值为数组
Field[] getDeclaredFields() // 获取当前类的所有field(不包括父类),返回值为数组
Class cls = Student.class;
try{
Field f = cls.getDeclaredField("name");
}catch(Exception e){
if (e.equals('NoSuch...')){
......
}
}
2、获取变量的属性
getName() //返回字段名称
getType() //返回字段类型,也是一个Class实例
getModifiers() // 返回字段的修饰符,它是一个int,不同的bit表示不同的含义。
Class cls = Student.class;
try{
Field f = cls.getDeclaredField("name");
f.getName(); // name
f.getType(); // String
f.getModifiers();
}catch(Exception e){
if (e.equals('NoSuch...')){
......
}
}
3、获取、修改变量的值
get(obj); // 获取obj对象xx变量值
set(obj, value); // 设置obj对象的xx变量的值为value
// 获取s对象name的值
Object s = new Student("Xiao Ming");
Class c = s.getClass();
try{
Field f = c.getDeclaredField("name");
f.setAccessible(true); // 由于name为私有变量,外部无法访问,通过setAccessible(true)可以允许访问任何权限变量
Object value = f.get(s); // 获取值
System.out.println(value); // "Xiao Ming"
}catch(Exception e){
if (e.equals('NoSuch...')){
......
}
}
// 设置s对象的age变量的值
Object s = new Student("Xiao Ming");
Class c = s.getClass();
Field f = c.getDeclaredField("age");
f.setAccessible(true);
f.set(s, 18) // 设置值
四、Class实例操作class的方法
public class Student{
private String name;
private int age;
public Student(){}
public Student(String name) {
this.name = name;
}
public void setName(String name){
this.name = name
}
public static void add(int a){
a += 1;
}
private void check(){
...
}
}
public class Main{
public static void main(String[] args){
...
}
}
1、获取类的方法
注意:
getMethod(name)
与getDeclaredMethod(name)
中 name 可能不存在,源码中用了throws来抛出可能出现的异常,所以在使用时,必须用try...catch...
来捕获异常
Method getMethod(name, Class...) // 获取包括父类方法在内的某个public的Method
Method getDeclaredMethod(name, Class...) // 获取当前类的某个Method(不包括父类)
Method[] getMethods() // 获取包括父类方法在内的所有public的Method,返回一个数组
Method[] getDeclaredMethods() // 获取当前类的所有Method(不包括父类),返回一个数组
Class cls = Student.class;
try {
Method method = cls1.getDeclaredMethod("setName", String.class);
}catch (Exception e){
...
}
2、获取方法的属性
getName() // 返回方法名称
getReturnType() // 返回方法返回值类型,也是一个Class实例
getParameterTypes() // 返回方法的参数类型,是一个Class数组
getModifiers() // 返回方法的修饰符,它是一个int,不同的bit表示不同的含义。
Class cls = Student.class;
try {
// 获取setName方法,参数为String
Method method = cls1.getDeclaredMethod("setName", String.class);
System.out.println(method.getName()); // setName
}catch (Exception e){
...
}
3、调用方法
invoke() // 调用执行方法
(1)调用public非静态方法
invoke(obj, 参数) // 对Method实例调用invoke就相当于调用该方法,
// invoke的第一个参数是对象实例,即在哪个实例上调用该方法,后面的可变参数要与方法参数一致,否则将报错。
Student s = new Student("tom", 18)
Class cls = s.getClass();
try {
// 获取setName方法,参数为String
Method method = cls1.getDeclaredMethod("setName", String.class);
method.invoke(s, "john")
}catch (Exception e){
...
}
(2)调用public静态方法
调用静态方法时,由于无需指定实例对象,所以invoke
方法传入的第一个参数永远为null
Student s = new Student("tom", 18)
Class cls = s.getClass();
try {
// 获取setName方法,参数为String
Method method = cls1.getDeclaredMethod("add", int.class);
method.invoke(null, 5)
}catch (Exception e){
...
}
(3)调用 非public 方法
对于非public方法,我们虽然可以通过Class.getDeclaredMethod()
获取该方法实例,但直接对其调用将得到一个IllegalAccessException
。为了调用非public方法,我们通过Method.setAccessible(true)
允许其调用。
注意:
setAccessible(true)
可能会失败。如果JVM运行期存在SecurityManager
,那么它会根据规则进行检查,有可能阻止setAccessible(true)
。例如,某个SecurityManager
可能不允许对java
和javax
开头的package
的类调用setAccessible(true)
,这样可以保证JVM核心库的安全
Student s = new Student("tom", 18)
Class cls = s.getClass();
try {
// 获取setName方法,参数为String
Method method = cls1.getDeclaredMethod("check");
Method.setAccessible(true)
method.invoke(s)
}catch (Exception e){
...
}
4、多态
使用反射调用方法时,仍然遵循多态原则:即总是调用实际类型的覆写方法(如果存在)。
即先从子类中找有无方法,没有再去父类
五、调用构造方法
注意:
getConstructor(name)
与getDeclaredConstructor(name)
中 name 可能不存在,源码中用了throws
来抛出可能出现的异常,所以在使用时,必须用try...catch...
来捕获异常
getConstructor(Class...) // 获取某个public的Constructor
getDeclaredConstructor(Class...) // 获取某个Constructor
getConstructors() // 获取所有public的Constructor
getDeclaredConstructors() // 获取所有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 i = Integer.class;
// 获取class的父类
Class n = i.getSuperclass();
// 获取class的所有接口
Class[] is = s.getInterfaces();
// 通过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