Java进阶之反射
我们知道Java在编译期的时候,代码中new对象时候,对象的类型就确定了下来,然后编译后的代码进行运行。而要在代码运行期的时候动态的去操作对象,就需要用Java的反射。
在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性。这种在代码运行中动态获取信息以及动态调用对象的方法的功能称为java语言的反射机制。
那么我们今天来细细研究下到底什么是反射呢?首先先看名字,反射,有反肯定对应就有正。
正就是我们正常的Java代码开发过程:导入类所在的包、通过类中的构造方法实例化对象、再使用实例化的对象去调用类中的方法。
那反的过程就是通过实例化的对象,反向推算出该对象的类型。
正: 类---new--->对象
反: 对象-getClass()->类
这里的getClass()方法是在Object中存在,要知道万物皆是对象,Java中的所有类都是默认继承自Object的,所以Object是所有类的父类,所有类都有这个方法。
Object类位于java.lang包中,这个包在编译Java程序时会自动导入。因此,Object类的方法可以被所有的Java类使用。
Object类提供了一些基本的方法,包括:
hashCode():返回对象的哈希码值,这个值通常用于哈希表中,比如 HashMap。
equals(Object obj):用于比较两个对象是否相等。
toString():返回对象的字符串表示形式。
getClass():返回对象的运行时类。
clone():创建并返回对象的一个副本。
finalize():当垃圾收集器确定不再有对该对象的引用时,由垃圾收集器在对象上调用此方法。
这些方法可以被其他类覆盖,以提供特定的实现。
所以这里我们就可以通过getClass()来反射获取到对象的运行时类。
反射所有的技术支持都来源于Class类
Java反射机制可以让我们在代码运行时动态地获取类的信息,创建对象,调用方法,访问和修改字段等。也就是说不需要new关键字,也能实例化类对象!
反射的原理
Java反射机制的实现主要依赖于Java类库中的一组类和接口,这些类和接口位于java.lang.reflect包中:
Class类:反射的核心类,可以获取对象的属性、方法等信息。
Field类:表示类的成员变量,可以用来获取和设置类之中的字段信息。
Method类:代表类的方法,可以用来获取类中的方法信息或者执行类和对象的方法。
Constructor类:代表类的构造方法。
当我们编写并编译一个Java程序时,Java编译器会将.java文件转换成.class文件,这些.class文件包含了类的所有信息,如字段、方法、构造函数等。
在运行时,JVM会加载这些.class文件,并通过反射机制将这些信息暴露给我们。
Java反射机制主要提供以下功能:
获得Class对象
方法有三种:
1.使用Class类的forName静态方法:
Class<?> clazz = Class.forName("java.lang.String");
Object obj = clazz.newInstance();
2.直接获取某一个对象的 class
Class<?> clazz = String.class;
Object obj = clazz.newInstance();
3.调用某个对象的getClass()方法,比如:
StringBuilder str = new StringBuilder("123");
Class<?> clazz = str.getClass();
Object obj = clazz.newInstance();
在运行时判断任意一个对象所属的类。
Object obj = new String("Hello, World!");
Class<?> clazz = obj.getClass();
System.out.println("输出类名: " + clazz.getName());
// 使用isInstance()方法判断obj是否是String类的实例
boolean isInstanceOfString = clazz.isInstance(obj);
在运行时构造任意一个类的对象。
创建对象有两种方法:
1.使用Class对象的newInstance()方法
Class<?> clazz = Class.forName("java.lang.String");
Object obj = clazz.newInstance();
2.先通过Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来构造
//获取String所对应的Class对象
Class<?> c = String.class;
//获取String类带一个String参数的构造器
Constructor constructor = c.getConstructor(String.class);
//根据构造器创建实例
Object obj = constructor.newInstance("创建对象");
System.out.println(obj);
在运行时判断任意一个类所具有的成员变量和方法。
Class<?> clazz = Class.forName("java.lang.String");
Field[] fields = clazz.getDeclaredFields();
Method[] methods = clazz.getDeclaredMethods();
System.out.println("Fields:");
for (Field field : fields) {
System.out.println(field);
}
System.out.println("Methods:");
for (Method method : methods) {
System.out.println(method);
}
在运行时获取泛型信息。
Class<?> clazz = Class.forName("java.util.ArrayList");
Type genericType = clazz.getGenericSuperclass();
if (genericType instanceof ParameterizedType) {
Type[] actualTypeArguments = ((ParameterizedType) genericType).getActualTypeArguments();
System.out.println("The actual type argument is: " + actualTypeArguments[0]);
}
在运行时调用任意一个对象的成员变量和方法。
Object obj = new String("Hello, World!");
Class<?> clazz = obj.getClass();
// 调用方法
Method method = clazz.getDeclaredMethod("toUpperCase");
String result = (String) method.invoke(obj);
System.out.println("Uppercase: " + result);
// 访问字段
Field field = clazz.getDeclaredField("value");
field.setAccessible(true);
char[] value = (char[]) field.get(obj);
System.out.println("Value: " + new String(value));
在运行时处理注解。
Class<?> clazz = Class.forName("MyClass");
Annotation[] annotations = clazz.getAnnotations();
for (Annotation annotation : annotations) {
if (annotation instanceof MyCustomAnnotation) {
MyCustomAnnotation myAnnotation = (MyCustomAnnotation) annotation;
System.out.println("Name: " + myAnnotation.name());
System.out.println("Value: " + myAnnotation.value());
}
}
生成动态代理。
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 在这里添加预处理和后处理逻辑
System.out.println("Method called: " + method.getName());
return method.invoke(target, args);
}
};
MyInterface proxy = (MyInterface) Proxy.newProxyInstance(
MyInterface.class.getClassLoader(),
new Class<?>[]{MyInterface.class},
handler
);
proxy.myMethod();
上面示例创建一个实现了MyInterface接口的代理对象,当调用myMethod方法时,会执行InvocationHandler中的逻辑。
单元测试
在单元测试中,通过反射模拟外部依赖的行为,从而实现单元测试。
// 使用反射模拟外部依赖的方法
Class<?> clazz = Class.forName("MyClass");
Method method = clazz.getDeclaredMethod("externalMethod");
method.setAccessible(true);
method.invoke(obj);
序列化和反序列化
反射也可以访问和修改对象的私有字段,比如在序列化和反序列化过程中。
ObjectInputStream in = new ObjectInputStream(new FileInputStream("obj.ser"));
Class<?> clazz = in.readObject().getClass();
Field field = clazz.getDeclaredField("name");
field.setAccessible(true);
Object obj = in.readObject();
String name = (String) field.get(obj);
如果全类名或者方法错误,可能出现ClassNotFoundException、NoSuchMethodException等异常,注意捕捉处理。
私有字段和方法尽量不访问和修改。而且反射会影响性能。
通过反射,我们可以突破Java语言的封装性,实现一些常规编程无法实现的功能。但是还是优先使用常规编程,最终不行再考虑反射!
日常开发中,ide软件通过.就可以提示出可调用的方法,这也是反射的应用。
以后web框架中的通过配置文件去定义Java bean,根据不同的配置文件就可以构建出不同的对象,调用对应的方法等等,都是反射的应用。
还有其他一些运行时动态加载需要加载的对象,都是反射实现的。
END