Java 框架基础技术之 01-反射
AI 摘要
反射概述
反射简介
在正常的 Java 编程中,通常会实例化一个类来创建一个对象并调用其方法。
public class Main {
public static void main(String[] args) {
// 实例化Person对象
Person person = new Person();
// 调用Person对象的方法
person.sayHello();
}
}这里在程序编译时就知道要创建 Person 类的对象,并且直接通过 new 关键字来创建,然后直接调用 sayHello 方法,这种在编译时就确定了类和操作的方式就是非反射的编程方式。
反射(Reflection)机制是 Java 语言的特性之一,在编译时不需要知道类是否存在,而在运行时再加载、探知、使用那些编译时完全未知的类。
Java 反射机制主要提供了以下的一些功能:
- 在运行时判断任意一个对象所属的类。
- 在运行时构造任意一个类的对象。
- 在运行时判断任意一个类所具有的属性和方法。
- 在运行时调用任意一个对象的方法。
Java 反射 API
Java 反射技术的基本 API:
- java.lang.Class 类可获取类和类的成员信息。
- java.lang.reflect.Constructor 类可调用类的构造方法。
- java.lang.reflect.Field 类可访问类的属性。
- java.lang.reflect.Method 类可调用类的方法。
反射应用
使用反射的基本步骤
在 Java 程序中使用反射的基本步骤:
- 导入 java.lang.reflect.* 包。
- 获得需要操作的类的 java.lang.Class 对象。
- 调用 Class 的方法获取 Field、Method 等对象。
- 使用反射 API 操作实例成员。
获取 Class 实例
一个类或接口被加载后,从系统中都能获得一个代表该类或接口的 Class 类型的实例,其包含了关于该类或接口的各种信息,如类的名称、成员变量、方法、父类、实现的接口等。
Class 类是 Java 反射机制的起源和入口。
在 Java 程序中获得 Class 实例的方式:
- 调用对象的 getClass 方法。
- 调用类或接口的 class 属性。
- 调用 Class.forName 方法。
在获得了某个类型对应的 Class 实例后,就可以调用 Class 实例的方法来获得该类型的信息,如构造函数(Constructor)、属性(Field)、方法(Method)等信息。
示例:获取 Class 实例
用户类(cn.duozai.entity.Person):
public class Person implements Serializable {
/**
* 私有属性-姓名
*/
private String name;
/**
* 静态属性-年龄
*/
static final int age = 30;
/**
* 保护属性-地址
*/
protected String address;
/**
* 公有属性-消息
*/
public String message;
/**
* 公有方法-getter
*
* @return java.lang.String
*/
public String getName() {
return name;
}
/**
* 公有方法-setter
*
* @param name
* @return void
*/
public void setName(String name) {
this.name = name;
}
/**
* 静态方法-getter
*/
static int getAge() {
return age;
}
/**
* 保护方法-getter
*/
protected String getAddress() {
return address;
}
/**
* 私有方法-说悄悄话
*/
private void silentMethod() {
System.out.println("这是悄悄话");
}
/**
* 公有方法-无参构造
*/
public Person() {}
/**
* 私有方法-有参构造
*/
private Person(String name) {
this.name = name;
}
/**
* 保护方法-有参构造
*/
protected Person(String name, String address, String message) {
this.name = name;
this.address = address;
this.message = message;
}
/**
* 公有方法-toString
*/
@Override
public String toString() {
return "{name:" + name + ", age:" + age + ", address:" + address
+ ", message:" + message + "}";
}
}测试类(cn.duozai.TestMain):
public class TestMain {
public static void main(String[] args) throws ClassNotFoundException {
// 方法1:调用对象的getClass方法
Person person = new Person();
Class clazz1 = person.getClass();
// 方法2:调用类或接口的 class 属性
Class clazz2 = Person.class;
// 方法3:调用 Class.forName 方法
Class clazz3 = Class.forName("cn.duozai.entity.Person");
// 判断是否相等
System.out.println("clazz1==clazz2 = " + (clazz1 == clazz2));
System.out.println("clazz2==clazz3 = " + (clazz2 == clazz3));
}
}示例效果:
从 Class 实例获取基本信息
通过 Class 实例获取对应类型的基本信息的基本方法:
| 名称 | 描述 |
|---|---|
| String getName() | 以字符串的形式返回该类型的名称(包 + 类名) |
| String getSimpleName() | 以字符串的形式返回该类型的简称(类名) |
| Package getPackage() | 获取该类型所在的包 |
| Class getSuperClass() | 返回该类型的父类的 Class 实例 |
| Class[] getInterfaces() | 返回该类型所实现的全部接口的 Class 实例 |
| int getModifiers() | 返回该类型的所有修饰符 其返回值由 public、protected、private 等对应的 int 常量组成 返回值应使用 Modifier 工具类来解码,才能判断修饰符的构成 |
| Class[] getDeclaredClasses() | 返回该类型中包含的全部内部类的 Class 实例 |
| class getDeclaringClass() | 返回该类型所在的外部类的 Class 实例 |
示例:从 Class 实例获取基本信息
基类(cn.duozai.entity.BaseClass):
public class BaseClass {}用户类(cn.duozai.entity.Person):
/**
* 用户类继承基类BaseClass,并实现序列化接口Serializable
*/
public class Person implements Serializable {
// ...
}测试类(cn.duozai.TestMain):
public class TestMain {
public static void main(String[] args) throws ClassNotFoundException {
// 获取Person用户类的Class实例
Class clz = Class.forName("cn.duozai.entity.Person");
// 获取类的全名
System.out.println("类的名称:" + clz.getName());
// 获取类的简称
System.out.println("类的简称:" + clz.getSimpleName());
// 获取类所在的包
Package pkg = clz.getPackage();
System.out.println("类所在的包:" + pkg.getName());
// 获取父类
Class superClz = clz.getSuperclass();
System.out.println("父类的名称:" + superClz.getName());
// 获取类实现的所有接口
Class[] interfaces = clz.getInterfaces();
for (Class anInterface : interfaces) {
System.out.println("类实现的接口:" + anInterface.getName());
}
// 获取类的修饰符
System.out.println("类的修饰符(解码前):" + clz.getModifiers());
System.out.println("类的修饰符(解码后):" + Modifier.toString(clz.getModifiers()));
}
}示例效果:
从 Class 实例获取构造函数
通过 Class 实例获取对应类型的所含构造函数的基本方法:
| 名称 | 描述 |
|---|---|
| Constructor getConstructor(Class...params) | 返回该类型指定参数列表的 public 公有的构造方法 构造方法的参数列表与 params 所指定的类型列表所匹配 |
| Constructor[] getConstructors() | 返回该类型的所有 public 公有的构造方法 |
| Constructor getDeclaredConstructor(Class...params) | 返回该类型的指定参数列表的构造方法,访问级别不限 |
| Constructor[] getDeclaredConstructors() | 返回该类型的所有构造方法,访问级别不限 |
示例:从 Class 实例获取构造函数
测试类(cn.duozai.TestMain):
public class TestMain {
public static void main(String[] args) throws ClassNotFoundException {
// ...
// 获取类的所有的构造函数
Constructor[] allConstructors = clz.getDeclaredConstructors();
// 遍历所有的构造函数
for (Constructor aConstructor : allConstructors) {
System.out.println("类的构造函数:");
System.out.println("构造函数的名称:" + aConstructor.getName());
System.out.println("构造函数的参数:" + Arrays.toString(aConstructor.getParameterTypes()));
System.out.println("构造函数的修饰符:" + Modifier.toString(aConstructor.getModifiers()));
}
}
}示例效果:
从 Class 实例获取所含属性
通过 Class 实例获取对应类型的所含属性的基本方法:
| 名称 | 描述 |
|---|---|
| Field getField(String name) | 返回该类型中指定名称的 public 公有的属性 name:指定属性名称 |
| Field[] getFields() | 返回该类型中所有 public 公有的属性 |
| Field getDeclaredField(String name) | 返回该类型中指定名称的属性,访问级别不限 |
| Field[] getDeclaredFields() | 返回该类型中的全部属性,访问级别不限 |
示例:从 Class 实例获取所含属性
测试类(cn.duozai.TestMain):
public class TestMain {
public static void main(String[] args) throws ClassNotFoundException {
// ...
// 获取类的所有的属性
Field[] allFields = clz.getDeclaredFields();
for (Field field : allFields) {
System.out.println("类的属性:");
System.out.println("属性的名称:" + field.getName());
System.out.println("属性的类型:" + field.getType().getName());
System.out.println("属性的修饰符:" + Modifier.toString(field.getModifiers()));
}
}
}示例效果:
从 Class 实例获取所含方法
通过 Class 实例获取对应类型的所含方法的基本方法:
| 名称 | 描述 |
|---|---|
| Method getMethod(String name, Class... params) | 返回该实例中指定的 public 公有的方法 name:指定方法名称 params:指定参数列表 |
| Method[] getMethods() | 返回该实例中所有 public 公有的方法 |
| Method getDeclaredMethod(String name, Class... params) | 返回该实例中指定的方法,访问级别不限 |
| Method[] getDeclaredMethods() | 返回该实例中的全部方法,访问级别不限 |
示例:从 Class 实例获取所含方法
测试类(cn.duozai.TestMain):
public class TestMain {
public static void main(String[] args) throws ClassNotFoundException {
// ...
// 获取类的所有的方法
Method[] allMethods = clz.getDeclaredMethods();
for (Method method : allMethods) {
System.out.println("类的方法:");
System.out.println("方法的名称:" + method.getName());
System.out.println("方法的返回值:" + method.getReturnType().getName());
System.out.println("方法的修饰符:" + Modifier.toString(method.getModifiers()));
System.out.println("方法的异常:" + Arrays.toString(method.getExceptionTypes()));
}
}
}示例效果:
创建类的实例
通过反射来创建 Java 类型的实例的方式:
- 使用 Class 实例的 newInstance 方法创建相关类型的实例。
- 使用 Constructor 实例创建相关类型的实例。
应用实例:创建类的实例
测试类(cn.duozai.TestMain):
public class TestMain {
public static void main(String[] args) throws Exception {
// 获取Person用户类的Class实例
Class clz = Class.forName("cn.duozai.entity.Person");
// new的方式实例化Person对象
Person person1 = new Person();
System.out.println("person1 = " + person1);
// 使用Class实例的newInstance方法实例化Person对象
Object person2 = clz.newInstance();
System.out.println("person2 = " + person1);
// 使用Constructor实例创建Person对象
// 通过Class实例获取指定的构造函数
Constructor constructor1 = clz.getDeclaredConstructor(String.class);
// 允许访问私有构造函数 private Person(String name){...}
constructor1.setAccessible(true);
// 通过构造函数创建实例
Object person3 = constructor1.newInstance("多仔");
System.out.println("person3 = " + person3);
}
}示例效果:
访问类的属性
使用 Field 实例可以对属性进行取值或赋值操作。
Field 实例访问属性的基本方法:
| 名称 | 描述 |
|---|---|
| xxx getXxx(Object obj) | 以 xxx 类型返回 obj 中相关属性的值 xxx:8 种基本数据类型之一 若 Field 实例表示的是一个静态属性,则 obj 可以设置为 null |
| Object get(Object obj) | 以 Object 类型返回 obj 中相关属性的值 |
| void setXxx(Object obj, xxx val) | 将 obj 中相关属性的值设置为 val xxx:8 种基本数据类型之一 |
| void set(Object obj, Object val) | 将 obj 中相关属性的值设置为 val |
| void setAccessible(boolean flag) | 对相关属性设置访问权限 true:禁止 Java 语言访问检查 |
示例:访问类的属性
测试类(cn.duozai.TestMain):
public class TestMain {
public static void main(String[] args) throws Exception {
// 获取Person用户类的Class实例
Class clz = Class.forName("cn.duozai.entity.Person");
// 使用Class实例的newInstance方法实例化Person对象
Object person = clz.newInstance();
// 获取Person对象的name属性
Field nameField = clz.getDeclaredField("name");
// 设置name属性的访问权限为true
nameField.setAccessible(true);
// 获取Person对象的name属性
System.out.println("修改前的name:" + nameField.get(person));
// 修改Person对象的name属性的值
nameField.set(person, "多仔");
System.out.println("修改后的name:" + nameField.get(person));
// 获取Person对象的age属性的值
Field ageField = clz.getDeclaredField("age");
ageField.setAccessible(true);
// 静态属性可以通过get(null)获取,不需要传递具体的对象
System.out.println("age:" + ageField.get(null));
}
}示例效果:
调用类的方法
Method 类中包含一个 invoke 方法,通过该方法可以调用 Java 类的实例方法和静态方法。
invoke 方法的基本用法:
Object invoke(Object obj, Object...args);invoke 方法的基本参数:
| 名称 | 描述 |
|---|---|
| obj | 执行该方法的对象 若 Method 实例表示的是一个静态实例,则 obj 可以为 null |
| args | 执行该方法时传入的参数 |
示例:调用类的方法
测试类(cn.duozai.TestMain):
public class TestMain {
public static void main(String[] args) throws Exception {
// 获取Person用户类的Class实例
Class clz = Class.forName("cn.duozai.entity.Person");
// 使用Class实例的newInstance方法实例化Person对象
Object person = clz.newInstance();
// 获取Person对象的silentMethod方法
Method method = clz.getDeclaredMethod("silentMethod");
// 设置silentMethod方法为可访问
method.setAccessible(true);
// 调用Person对象的silentMethod方法
method.invoke(person);
}
}示例效果:
反射总结
反射的优缺点
Java 反射机制允许程序创建和控制任何类的对象,无需提前硬编码目标类,提高了 Java 程序的灵活性和扩展性,降低了耦合性,提高自适应能力。
反射机制主要应用在对灵活性和扩展性要求很高的系统框架上,如 MyBatis、Spring 等开源框架中都有大量运用,但反射会模糊程序内部逻辑,可读性较差,且反射可能会破坏封装,实际上只有当程序需要动态创建类的实例时才会考虑使用反射。