Java反射相关知识回顾

一、反射是什么?

反射是Java语言的一个特性,它允许程序再运行时,进行自我检查并且对内部的成员进行操作。

反射是指在运行时检查、获取和操作类、方法、字段等程序元素的能力。简而言之,它让我们能够检查和修改代码的结构,而不仅仅是执行代码。反射使得Java程序能够在运行时了解自身的结构,并动态地创建、操作和销毁对象,以及调用对象的方法。

反射就是在程序运行时才知道要操作的类是什么,并且可以在运行时获取类的完整构造,并调用对应的方法。

二、为什么要用反射?

Java Reflection功能非常强大,并且非常有用,比如:

  • 获取任意类的名称、package信息、所有属性、方法、注解、类型、类加载器等
  • 获取任意对象的属性,并且能改变对象的属性
  • 调用任意对象的方法
  • 判断任意一个对象所属的类
  • 实例化任意一个类的对象
  • 通过反射我们可以实现动态装配,降低代码的耦合度,动态代理等。

三、反射相关API

Java中的反射功能主要通过下面的类和接口实现:

  • Class :用于获取类的信息
  • Field:用于获取和设置类的字段
  • Method:用于获取类的方法
  • Constructor:用于获取类的构造函数
  • Array:用于操作数组
  • Modifier:用于获取字段,方法和类的修饰符。

四、反射的使用

Class类常用方法
  1. 获取类的方法
方法名称功能描述
public Method[] getMethods()获取该类的所有公有的方法(public 修饰的)
public Method getMethod(String name, Class<?>… parameterTypes)获取该类的某个公有方法(public 修饰的)
public Method[] getDeclaredMethods()获取该类的所有方法
public Method getDeclaredMethod(String name, Class<?>… parameterTypes)获取该类的某个方法
  1. 获取构造器
方法名称功能描述
public Constructor getConstructor(Class<?>… parameterTypes)获得该类中与参数类型匹配的公有构造方法
public Constructor<?>[] getConstructors()获得该类的所有公有构造方法
public Constructor getDeclaredConstructor(Class<?>… parameterTypes)获得该类中与参数类型匹配的构造方法
public Constructor<?>[] getDeclaredConstructors()获得该类的所有构造方法
  1. 获取字段
方法名称功能描述
public Field getField(String name)获得该类中名为参数相同的公共字段
public Field[] getFields()获得该类中所有的公共字段
public Field getDeclaredField(String name)获取类中字段名和参数相同的字段
public Field[] getDeclaredFields()获取类中所有的字段属性
  1. Class Field Method Constructor
Class类代表类的实体,在允许的Java应用程序中表示类和接口
Field类代表类的成员变量(类的属性)
Method类代表类的方法
Constructor类代表类的构造器

提示:在反射中,方法名命名都是大差不差的,而且归根结底也就是获取和类相关的东西,比如:字段属性,方法,构造器,泛型,注解,实现的接口,继承的父类…本文仅仅展示了一些 Constructor Method Field的冰山一角

常见操作

在Java中,每个类都有一个关联的 Class 对象,该对象包含了有关该类的信息。Class 类提供了许多方法,可以用来获取关于类的信息,例如类的名称、超类、实现的接口、构造函数、字段和方法等。

获取Class对象
  1. 使用Class.forName()方法获取
Class<?> clazz1 = Class.forName("com.robin._reflect.Student");
  1. 使用.class获取
Class<?> clazz2 = Student.class;
  1. 使用对象的.getClass()获取
Class<?> clazz3 = new Student().getClass();
获取类的信息
// 通过Class类对象获取类的信息
String className = clazz1.getName();
System.out.println("类名:"+className);
获取超类的信息
Class<?> superclass = clazz1.getSuperclass();
System.out.println("超类:"+superclass.getName()); // 打印超类名称
获取实现的接口
Class<?>[] interfaces = clazz1.getInterfaces();
for (Class<?> iface : interfaces) {
    System.out.println("实现的接口:" + iface.getName());
}
创建对象
  1. 通过Class对象的newInstance()创建对象
Object obj = clazz1.newInstance();
System.out.println("obj:"+obj.toString());
  1. 通过Constructor对象的newInstance()创建对象
Constructor<?> constructor = clazz1.getConstructor(null); // 传入构造器需要的参数
Object newInstance = constructor.newInstance(args);// 使用构造函数创建对象
获取和设置字段的值
Field field = clazz1.getDeclaredField("name");
field.setAccessible(true); // 设置允许访问私有字段
Object fieldValue = field.get(obj); // 获取字段的值
System.out.println("字段值:"+fieldValue);
field.set(obj,"zhangsan");
Object newFieldValue = field.get(obj); // 获取字段新设的值
System.out.println("字段值:"+newFieldValue);
调用方法
Method toStringMethod = clazz1.getMethod("toString", null);
Object result = toStringMethod.invoke(obj, args);// 调用执行方法
System.out.println("方法结果:"+result);
获取和使用构造函数
Constructor<?> constructor = clazz1.getConstructor(null);
Object newInstance = constructor.newInstance(args);// 使用构造函数创建对象
System.out.println("新对象创建了:"+newInstance.toString());
练习操作

首先准备一个用于被反射的类,以Student为例

public class Student {

    private String name; // 私有成员变量
    private String phone;// 私有成员变量
    public String sex; // 公有成员变量
    public String address;// 公有成员变量

    // 公有构造器
    public Student() {
    }

    // 私有构造器
    private Student(String name, String phone) {
        this.name = name;
        this.phone = phone;
    }

    // 保护构造器
    protected Student(String name){
        this.name = name;
    }

    // 公有的getter setter 方法
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    // 公有的toString 方法
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", sex='" + sex + '\'' +
                ", address='" + address + '\'' +
                ", phone='" + phone + '\'' +
                '}';
    }
}

练习的代码:由于步骤较多我就不一一摘出来说明了,统一放到代码注释中说明

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class Demo2 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchFieldException {
        // 1.获取Class类对象
        Class<?> clazz = Class.forName("com.robin._reflect.Student");

        // 2. 获取构造器
        //  2.1 获取public的构造器
        Constructor<?>[] pbConsturctors = clazz.getConstructors();
        for (Constructor<?> pbConsturctor : pbConsturctors) {
            System.out.println(pbConsturctor.getName()+" 访问控制权限:"+pbConsturctor.getModifiers());
        }

        System.out.println("=========================");
        // 2.2 获取所有的构造器
        Constructor<?>[] declaredConstructors = clazz.getDeclaredConstructors();
        for (Constructor<?> declaredConstructor : declaredConstructors) {
            System.out.println(declaredConstructor.getName()+" 访问控制权限:"+declaredConstructor.getModifiers());
        }

        // 3.创建对象
        Object obj1 = clazz.newInstance();
        Constructor<?> constructor = clazz.getConstructor(null); // 获取空参的构造器对象
        Object obj2 = constructor.newInstance();

        Constructor<?> declaredConstructor = clazz.getDeclaredConstructor(String.class);
        Object obj3 = declaredConstructor.newInstance("张三");


        // 4.获取方法
        Method toStringMethod = clazz.getMethod("toString");
        // 执行方法 invoke(对象)
        Object invokeMethod1 = toStringMethod.invoke(obj1);
        System.out.println(invokeMethod1.toString());

        Object invokeMethod2 = toStringMethod.invoke(obj2);
        System.out.println(invokeMethod2.toString());

        Object invokeMethod3 = toStringMethod.invoke(obj3);
        System.out.println(invokeMethod3.toString());

        System.out.println("==========================");
        // 5.获取字段属性
        Field[] declaredFields = clazz.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            System.out.println(declaredField.getName());
        }

        Field field = clazz.getDeclaredField("phone");
        // 更改非public属性需要提供权限
        field.setAccessible(true);
        // 5.1 获取字段属性值
        Object fieldValue = field.get(obj1);// null
        System.out.println(fieldValue==null? "null" : fieldValue.toString());

        // 5.2 更改属性值

        field.set(obj1,"15547182477");

        Object newFieldValue = field.get(obj1);
        System.out.println(newFieldValue.toString());// 15547182477
    }
}

五、反射的注意事项

  1. 安全性:反射可以绕过访问控制,因此在使用反射时要格外小心,确保只访问允许的成员和方法。如果不小心访问了私有成员或调用了不安全的方法,可能会导致应用程序不稳定或不安全。
  2. 性能:反射操作通常比直接调用方法或访问字段的方式要慢。因此,在性能敏感的应用程序中,要谨慎使用反射,尽量选择其他更高效的方法。
  3. 编译时检查:反射可以绕过编译时类型检查,因此如果在使用反射时传递了错误的类型或方法名称,可能会导致运行时异常。要特别小心避免这种情况。
  4. 类加载:反射可能会触发类的加载,这可能会导致不希望加载的类被加载到内存中。要注意控制类加载的时机。
    总之,反射是一项强大的功能,但需要小心谨慎地使用。只有在必要的情况下才应该使用反射,确保安全性和性能。在日常开发中,应优先考虑使用普通的方法调用和字段访问,只有在没有其他选择时才考虑使用反射。

六、反射的应用场景

反射在 Java 中有广泛的应用场景,以下是几个常见的应用场景:

① 动态加载类和创建对象

通过反射,我们可以在运行时动态地加载类,并创建其实例。这样就可以根据配置文件或用户输入来决定要加载的类,从而实现灵活的代码扩展性。

String className = "com.example.MyClass";
Class<?> clazz = Class.forName(className);
Object obj = clazz.newInstance();
② 调用对象的方法

通过反射,我们可以在运行时动态地调用对象的方法。这样就可以根据不同的条件来选择调用不同的方法,实现更加灵活的业务逻辑。

Method method = obj.getClass().getMethod("methodName", parameterTypes);
method.invoke(obj, args);
③ 操作对象的属性

通过反射,我们可以在运行时动态地操作对象的属性。这样就可以读取或修改对象的私有字段,实现对对象状态的灵活控制。

Field field = obj.getClass().getDeclaredField("fieldName");
field.setAccessible(true);
Object value = field.get(obj);
field.set(obj, newValue);
④ 获取类的信息

通过反射,我们可以在运行时动态地获取类的信息。这样就可以获取类的构造方法、字段、方法等信息,并进行相应的操作。

Class<?> clazz = obj.getClass();
Constructor<?>[] constructors = clazz.getConstructors();
Field[] fields = clazz.getDeclaredFields();
Method[] methods = clazz.getDeclaredMethods();
⑤ 注解处理器

通过反射,我们可以编写注解处理器来处理自定义注解。这样就可以在编译期间或运行时对注解进行解析和处理,实现一些特定的功能。

Class<?> clazz = MyClass.class;
Annotation[] annotations = clazz.getAnnotations();
for (Annotation annotation : annotations) {
    if (annotation instanceof MyAnnotation) {
        // 处理自定义注解逻辑
    }
}

七、反射的优缺点

反射的优点
  • 动态性:通过反射,我们可以在运行时动态地加载类、创建对象、调用方法和操作属性,使得代码更加灵活和可扩展。
  • 配置性:通过反射,我们可以根据配置文件或用户输入来决定要加载的类、调用的方法和操作的属性,实现了代码的配置化。
  • 框架支持:许多 Java 框架(如 Spring)都广泛使用了反射机制,通过反射来实现依赖注入、AOP 等功能。
反射的缺点
  • 性能开销:反射操作相比直接调用方法和访问属性,会有一定的性能开销。因此,在性能要求较高的场景下,应慎重使用反射。
  • 安全性问题:通过反射可以绕过 Java 语言的访问控制机制,访问私有字段和方法。这可能导致代码的安全性问题,需要谨慎使用
  • 23
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

杀死一只知更鸟debug

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值