小二,来客人了
客官有请:
文章目录
1. 什么是Java反射机制?
反射机制是Java中一种很强大的功能,它允许程序在运行时动态地获取和操作类的信息,通过反射,可以在运行时通过获取类的名称、方法、字段等信息。动态地创建对象,调用方法,访问和修改字段,以及执行其他与类相关的操作。
反射是Java的动态机制,允许程序在运行期间进行如下操作:
- 获取类的信息
- 创建对象实例
- 调用方法
- 访问和修改字段
- 获取和操作注解
这些操作都可以在不知道类的具体名称的情况下完成,使得程序具有极高的灵活性和通用性。
2. 反射的核心类有哪些?
Java反射主要涉及以下核心类,它们都位于java.lang.reflect包中:
- Class:表示类或接口的类型信息
- Constructor:表示类的构造方法
- Method:表示类的方法
- Field:表示类的字段
- Array:提供了动态创建和访问Java数组的方法
- Modifier:提供了访问修饰符信息的方法
3. 使用反射前的准备👉获取Class对象
在使用反射之前,我们首先需要获取一个类的Class对象。有三种主要方式可以获取Class对象:
-
通过
类名.class:
Class cls = String.class;
-
通过对象的
getClass()
方法:String str = "Hello"; Class cls = str.getClass();
-
通过
Class.forName()
方法:Class cls = Class.forName("java.lang.String");
3.1 如何通过反射创建对象实例?
通过反射创建对象实例有两种主要方式:
-
使用Class对象的newInstance()方法(已过时,不推荐使用):
Class cls = Class.forName("java.lang.String"); Object obj = cls.newInstance();
-
使用Constructor对象的newInstance()方法:
Class cls = Class.forName("java.lang.String"); Constructor constructor = cls.getConstructor(); Object obj = constructor.newInstance();
如果需要调用带参数的构造方法,可以这样做:
Class cls = Class.forName("java.lang.String");
Constructor constructor = cls.getConstructor(String.class);
Object obj = constructor.newInstance("Hello, Reflection!");
3.2 通过对象实例调用方法
通过反射调用方法的步骤如下:
- 获取Method对象
- 使用invoke()方法调用
示例代码:
Class cls = Class.forName("java.lang.String");
Object obj = cls.getConstructor(String.class).newInstance("Hello, Reflection!");
Method method = cls.getMethod("length");
int length = (int) method.invoke(obj);
System.out.println("String length: " + length);
如果方法有参数,可以在getMethod()和invoke()中传入相应的参数:
Method method = cls.getMethod("substring", int.class, int.class);
String result = (String) method.invoke(obj, 0, 5);
System.out.println("Substring: " + result);
3.3 访问对象字段
反射可以用来获取和修改对象的字段值,即使是私有字段:
Class cls = Class.forName("com.example.MyClass");
Object obj = cls.newInstance();
Field field = cls.getDeclaredField("privateField");
field.setAccessible(true); // 允许访问私有字段
field.set(obj, "New Value");
String value = (String) field.get(obj);
System.out.println("Field value: " + value);
3.4 通过反射查看注解信息(拓展)
反射机制与注解结合使用非常强大,可以在运行时获取和处理注解信息:
Class cls = Class.forName("com.example.MyClass");
if (cls.isAnnotationPresent(MyAnnotation.class)) {
MyAnnotation annotation = cls.getAnnotation(MyAnnotation.class);
System.out.println("Annotation value: " + annotation.value());
}
4. 反射的实际应用
反射在很多场景下都有应用,例如:
- ORM(对象关系映射)框架 比如👉MyBatis
- 依赖注入容器
- 单元测试框架
- 序列化和反序列化
- 动态代理
5. 反射的优缺点
优点:
- 提高了程序的灵活性和扩展性
- 允许在运行时动态加载和使用类
- 是很多Java高级特性和框架的基础
缺点:
- 性能开销较大,反射调用比直接调用慢
- 可能破坏封装性,允许访问私有成员
- 代码可读性可能下降
6. BeanUtils.copyProperties的反射实现
**Apache Commons BeanUtils库中的copyProperties方法是一个广泛使用的工具,**用于在JavaBean之间复制属性。这个方法的底层实现就是通过反射来完成的。让我们来看看它的核心实现原理:
/**
* 将给定源bean的属性值复制到目标bean中。
*
* @param source 源bean
* @param target 目标bean
* @param editable 用于限制属性设置的类(或接口)
* @param ignoreProperties 要忽略的属性名称数组
* @throws BeansException 如果bean属性复制失败
*/
private static void copyProperties(Object source, Object target, @Nullable Class editable,
@Nullable String... ignoreProperties) throws BeansException {
// 步骤1: 验证输入参数
Assert.notNull(source, "Source must not be null");
Assert.notNull(target, "Target must not be null");
// 步骤2: 确定实际的可编辑类
Class actualEditable = target.getClass();
if (editable != null) {
if (!editable.isInstance(target)) {
throw new IllegalArgumentException("Target class [" + target.getClass().getName() +
"] not assignable to Editable class [" + editable.getName() + "]");
}
actualEditable = editable;
}
// 步骤3: 获取目标类的属性描述符
PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);
List ignoreList = (ignoreProperties != null ? Arrays.asList(ignoreProperties) : null);
// 步骤4: 遍历目标的每个属性描述符
for (PropertyDescriptor targetPd : targetPds) {
Method writeMethod = targetPd.getWriteMethod();
if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) {
// 步骤5: 从源中获取相应的属性描述符
PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());
if (sourcePd != null) {
Method readMethod = sourcePd.getReadMethod();
if (readMethod != null) {
// 步骤6: 检查属性类型是否可赋值
ResolvableType sourceResolvableType = ResolvableType.forMethodReturnType(readMethod);
ResolvableType targetResolvableType = ResolvableType.forMethodParameter(writeMethod, 0);
// 如果任一ResolvableType具有无法解析的泛型,则在可赋值检查中忽略泛型类型
boolean isAssignable =
(sourceResolvableType.hasUnresolvableGenerics() || targetResolvableType.hasUnresolvableGenerics() ?
ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType()) :
targetResolvableType.isAssignableFrom(sourceResolvableType));
if (isAssignable) {
try {
// 步骤7: 如果需要,设置方法为可访问
if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
readMethod.setAccessible(true);
}
// 步骤8: 从源对象读取属性值
Object value = readMethod.invoke(source);
if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
writeMethod.setAccessible(true);
}
// 步骤9: 将属性值写入目标对象
writeMethod.invoke(target, value);
}
catch (Throwable ex) {
throw new FatalBeanException(
"Could not copy property '" + targetPd.getName() + "' from source to target", ex);
}
}
}
}
}
}
}
步骤说明:
-
验证输入参数:确保源对象和目标对象不为空。
-
确定实际的可编辑类:如果提供了editable参数,验证目标对象是否为该类的实例,并将actualEditable设置为editable或目标对象的类。
-
获取目标类的属性描述符:使用getPropertyDescriptors方法获取目标类的所有属性描述符。
-
遍历目标的每个属性描述符:对每个非忽略的、具有写方法的属性进行处理。
-
从源中获取相应的属性描述符:根据属性名在源对象中查找对应的属性描述符。
-
检查属性类型是否可赋值:使用ResolvableType来处理泛型情况,确保源属性类型可以赋值给目标属性类型。
-
如果需要,设置方法为可访问:对于非公共方法,设置其可访问性。
-
从源对象读取属性值:使用反射调用源对象的读方法获取属性值。
-
将属性值写入目标对象:使用反射调用目标对象的写方法,将属性值设置到目标对象中。
这个方法的主要目的是实现对象之间的属性复制,同时考虑了类型兼容性、访问控制和异常处理等因素。
这里的反射主要体现在以下几个方面:
- 使用PropertyDescriptor获取属性的读写方法(getter和setter)。
- 使用Method.invoke()方法来调用getter和setter方法。
- 使用属性的Class对象进行类型检查和转换。
通过反射,BeanUtils.copyProperties可以在不知道具体类型的情况下,动态地复制对象属性,这使得它能够适用于各种不同的JavaBean类。
然而,正如前面提到的,反射操作的性能开销较大。因此,在频繁调用或处理大量数据时,使用反射实现的BeanUtils.copyProperties可能会成为性能瓶颈。在这种情况下,可以考虑使用基于字节码生成的工具(如Cglib的BeanCopier)来提高性能。
谈谈你对反射的理解(高频面试题)
反射机制是Java中一种很强大的功能,它允许程序在运行时动态地获取和操作类的信息,通过反射,可以在运行时通过获取类的名称、方法、字段等信息。动态地创建对象,调用方法,访问和修改字段,以及执行其他与类相关的操作。
它的核心时java.lang.reflect包中的一组类和接口。常用的反射类和接口:
- Class类:代表一个类或者接口,在运行时可以通过它获取和操作类的信息,通过class类的
newInstance()
方法可以创建类的实例,相当于调用该类的无参构造函数。 - Constructors类:代表类的构造函数,在运行时可以通过它创建对象
- Method类:代表类的方法,在运行时可以通过它调用方法。通过调用它的invoke()方法,可以传递参数并获取返回值。
- Filed类:代表类的字段,在运行时可以通过它访问和修改字段。
使用反射技术可以在运行时动态的创建对象、调用方法、访问和修改字段,反射技术使得程序具有更高的灵活性和扩展性。它也带来了一定的性能开销,在性能敏感的场景下需要谨慎使用。
如果问你用过哪些反射的工具类?
BeanUtils.copyProperties(Object o,Object o1),前面提到的