30天学Java第十天——反射机制
反射机制
反射机制是 Java 语言中的一个重要特性,它允许程序在运行时动态地获取类的信息(如类的属性、方法和构造器等),并且可以操作这些信息。
反射机制在某些情况下非常有用,例如开发框架、库,或者需要进行动态类加载和方法调用的场景。
许多 Java 框架(如 Spring 和 Hibernate)使用反射来实现依赖注入和 AOP 切面编程。
什么是反射机制
反射机制可以理解为“运行时类型信息”,它提供了一种访问和操作对象属性与方法的能力,而无需在编译时知道对象的具体类型。通过反射,开发者可以:
- 检查类的构造方法、方法和属性。
- 创建对象实例。
- 获取或设置属性的值。
- 动态调用方法。
Java 的反射机制核心类:
反射机制的核心类在 java.lang.reflect 包中,主要包括以下几个类:
- Class:表示类的对象,可以获取类的结构信息(属性、方法等)。
- Field:表示类的字段,可以获取或修改该字段的值。
- Method:表示类的方法,可以通过反射调用方法。
- Constructor:表示类的构造器,可以通过反射创建对象实例。
如何使用反射
以下是使用 Java 反射机制的的几种应用方式:
- 获取 Class 对象四种方式:
- 第一种:可以通过 Class.forName(“完全限定类名”) 获取。
完全限定类名要带包名,用双引号括起来作为字符串
这个方法的执行会导致类加载动作发生Class<?> clazz = Class.forName("com.example.MyClass");
- 第二种:可以通过实例调用 getClass() 方法获取。
注意:下面两个 clazz1 与 clazz2 是一样的,都表示 MyClass 这个类,即Java中的同一个类的类型只存在一份,反射得到的都是同一个类型MyClass obj1 = new MyClass(); MyClass obj2 = new MyClass(); Class<?> clazz1 = obj1.getClass(); Class<?> clazz2 = obj2.getClass(); System.out.print(clazz1 == clazz2); // 结果是ture
- 第三种:可以通过类名直接访问 ClassName.class 获取。
Class<?> clazz = MyClass.class;
- 第四种:直接通过系统 / 应用类加载器的 getSystemClassLoader() 方法获取
这种方法的类加载不会进行初始化,只有当该类第一次使用的时候才会初始化ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader(); Class<?> clazz = systemClassLoader .loadClass("com.example.MyClass");
- 创建对象
MyClass obj = (MyClass) clazz.newInstance(); // 注意:此方法在 Java 9 以后被弃用,推荐使用 Constructor //使用 Constructor 创建对象 Constructor<?> constructor = clazz.getConstructor(); MyClass obj = (MyClass) constructor.newInstance(); // 使用 Constructor 创建对象
- 获取和操作字段、方法和构造器的信息:
- 使用getField(), getFields() ,getDeclaredField() 方法获取字段信息。
getField(), getFields()只能获取 public 修饰的字段
getDeclaredField() 可以获取所有权限字段- 获取属性名字:getName()
- 获取属性的类型:Class getType()
得到类型后就可以使用getName()获取类型的名字,通过getSimpleName()获取类型的简短名字 - 获取修饰符:int getModifiers()
返回值是 int 类型,是修饰符的 int 类型表示,可以使用 Modifier 类的 toString() 方法将 int 转换为对于的修饰符
- 使用 Field 类的 set() 和 get() 方法操作字段的值。
Field field = clazz.getDeclaredField("fieldName"); // 获取字段 field.setAccessible(true); // 如果是私有字段,需要设置为可访问 field.set(obj, "newValue"); // 设置字段值 String value = (String) field.get(obj); // 获取字段值
- 使用 getMethod(), getDeclaredMethod() 方法获取方法信息。使用 Method 类的 invoke() 方法动态调用方法。
Method method = clazz.getDeclaredMethod("methodName", String.class); // 根据方法名和参数类型获取方法 method.setAccessible(true); // 如果是私有方法,需要设置为可访问 method.invoke(obj, "argument"); // 调用方法
- 使用 getConstructor(), getDeclaredConstructor() 方法获取构造器信息。
- 反射获取父类泛型
通过 getGenericSuperclass() 方法获取当前类的父类泛型,返回值是类型 ParameterizedType(参数化类型) ,属于 Type(这是java.lang.reflect下的接口,表示一种类型)。
然后通过 ParameterizedType.getActualTyoeArguments() 方法获取到父类泛型的类型集合
- 获取接口、属性、方法参数、方法返回值、构造方法参数的泛型与获取父类泛型类似
- 接口:getGenericInterfaces()
- 属性:先获取属性,然后通过 getGenericType() 方法获取属性的泛型类型
- 方法参数:先获取方法,然后通过 getGenericParameterTypes() 方法获取方法参数的泛型类型
- 方法返回值:先获取方法,然后通过 getGenericReturnType() 方法获方法返回值的泛型类型
- 构造方法参数:先获取构造方法,然后通过 getGenericParameterTypes() 方法获取构造方法参数的泛型类型
反射的缺点
尽管反射机制提供了强大的灵活性,但它也有一些缺点:
- 性能开销: 反射操作通常比直接代码调用慢,因为它涉及到多层间接调用和更复杂的安全检查。
- 安全性问题: 反射绕过了一些访问控制检查,可能导致安全隐患。
- 代码可读性: 使用反射会使代码变得不太清晰和可读,尤其是当涉及复杂的反射逻辑时。
反射的示例
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
class Person {
private String name;
public Person() { }
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
public class ReflectionExample {
public static void main(String[] args) {
try {
// 1. 获取 Class 对象
Class<?> clazz = Class.forName("Person");
// 2. 创建实例
Constructor<?> constructor = clazz.getConstructor();
Object personInstance = constructor.newInstance();
// 3. 调用方法设置属性
Method setNameMethod = clazz.getDeclaredMethod("setName", String.class);
setNameMethod.invoke(personInstance, "John Doe");
// 4. 访问字段
Field nameField = clazz.getDeclaredField("name");
nameField.setAccessible(true); // 访问私有字段
String nameValue = (String) nameField.get(personInstance);
// 5. 输出结果
System.out.println("Name: " + nameValue); // 输出: Name: John Doe
} catch (Exception e) {
e.printStackTrace();
}
}
}
注:本文章源于学习动力节点老杜
的java教程视频后的笔记整理,方便自己复习的同时,也希望能给csdn的朋友们提供一点帮助。