十分钟搞清 Java 反射
前言
因为后续在开发 Xposed
模块的时候,发现需要 Java
反射的概念,这几年没怎么接触反射的概念,就花点时间回忆下反射基础内容。
反射是 Java 的一种机制,可以在运行时获取类的信息作用,通过反射可以在程序运行时动态创建对象,还能获取到类的所有信息,比如它的属性、构造器、方法、注解等。
正射和反射
如果有反射那肯定有所谓的“正射”,正射就是在平常开发中知道的类,并且去创建实例化它:
public class Apple {
private int price;
public int publicPrice;
private int privatePrice;
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
}
比如上面的 Apple
类直接实例化:
public class Main {
public static void main(String[] args) throws Exception {
Apple apple = new Apple();
apple.setPrice(4);
System.out.println("正射:" + apple.getPrice());
}
}
这个过程就是所谓的正射,那反射指的是在运行情况下通过字符串值知道要运行的类,然后获取类的构造且调用对应的方法:
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
public class Main {
public static void main(String[] args) throws Exception {
// 获取类的 Class 对象实例,根据包名路径走,例如(com.iiong.Apple)
Class<?> clazz = Class.forName("Apple");
// 根据 Class 对象实例获取 Constructor 对象
Constructor<?> appleConstructor = clazz.getConstructor();
// 使用 Constructor 对象的 newInstance 方法获取反射类对象
Object appleObj = appleConstructor.newInstance();
// 获取方法的 Method 对象
Method setPriceMethod = clazz.getMethod("setPrice", int.class);
// 利用 invoke 方法调用方法
setPriceMethod.invoke(appleObj, 5);
// 获取方法的 Method 对象
Method getPriceMethod = clazz.getMethod("getPrice");
// 输出利用 invoke 方法调用方法的数据
System.out.println("反射:" + getPriceMethod.invoke(appleObj));
}
}
最后输出的结果和正射代码是一样的,到此为止对反射掌握基本使用了,反射的应用场景:
- Spring 实例化对象:当程序启动时,Spring 会读取配置文件applicationContext.xml并解析出里面所有的标签实例化到IOC容器中。
- 反射 + 工厂模式:通过反射消除工厂中的多个分支,如果需要生产新的类,无需关注工厂类,工厂类可以应对各种新增的类,反射可以使得程序更加健壮。
- JDBC连接数据库:使用JDBC连接数据库时,指定连接数据库的驱动类时用到反射加载驱动类。
- ...
扩展
获取 Class
对象有三种方式,分别如下:
- 根据类名:
Apple.class
Class clazz = Apple.class;
- 根据对象:
new Apple().getClass()
Apple apple = new Apple();
Class clazz = apple.getClass();
- 根据全限定类名:
Class.forName("com.iiong.Apple")
,也是本案例使用的方法
通过反射创建类对象主要通过俩个方式:
- 通过
Class
对象的newInstance()
方法
Class clazz = Apple.class;
Apple apple = (Apple)clazz.getDeclaredConstructor().newInstance();
- 通过
Constructor
对象的newInstance()
方法,也是本案例使用的方法
Class clazz = Apple.class;
Constructor<?> appleConstructor = clazz.getConstructor();
Apple apple = appleConstructor.newInstance();
同样我们可以通过 Class
对象的 getFields()
方法获取 Class
类的属性,但无法获取私有属性。
Class clazz = Apple.class;
Field[] fields = clazz.getFields();
for (Field field : fields) {
System.out.println("Apple 类属性:" + field.getName());
}
打印的时候发现 private
声明的属性不会输出,所以这时候需要使用 getDeclaredFields
方法:
Field[] privateFields = clazz.getDeclaredFields();
for (Field field : privateFields) {
System.out.println("Apple 类属性:" + field.getName());
}
与获取类属性一样,当我们去获取类方法、类构造器时,如果要获取私有方法或私有构造器,则必须使用有 declared
关键字的方法。
方法名 | 说明 |
---|---|
forName() | - 获取 Class 对象的一个引用,但引用的类还没有加载(该类的第一个对象没有生成)就加载了这个类- 为了产生 Class 引用,forName() 立即就进行了初始化 |
Object-getClass() | 获取Class对象的一个引用,返回表示该对象的实际类型的Class引用 |
getName() | 取全限定的类名(包括包名),即类的完整名字 |
getSimpleName() | 获取类名(不包括包名) |
getCanonicalName() | 获取全限定的类名(包括包名) |
isInterface() | 判断 Class 对象是否是表示一个接口 |
getInterfaces() | 返回 Class 对象数组,表示Class对象所引用的类所实现的所有接口 |
getSupercalss() | 返回 Class 对象,表示 Class 对象所引用的类所继承的直接基类,应用该方法可在运行时发现一个对象完整的继承结构 |
newInstance() | 返回一个Oject对象,是实现“虚拟构造器”的一种途径。使用该方法创建的类,必须带有无参的构造器 |
getFields() | 获得某个类的所有的公共 public 的字段,包括继承自父类的所有公共字段。 类似的还有 getMethods 和 getConstructors |
getDeclaredFields | 获得某个类的自己声明的字段,即包括 public 、private 和 proteced ,默认但是不包括父类声明的任何字段。类似的还有 getDeclaredMethods 和getDeclaredConstructors |