Java反射相关知识回顾

简介: 本文回顾了Java反射机制的概念、用途、相关API、使用方法以及注意事项。反射允许程序在运行时检查和操作类的成员,如字段、方法和构造函数。文章详细介绍了如何通过Class对象获取类信息、创建对象、访问和修改字段、调用方法,以及使用构造函数。同时,指出了反射在安全性、性能和编译时检查方面的潜在问题,并讨论了反射在动态加载类、调用方法、操作属性、获取类信息和注解处理等场景下的应用。最后,总结了反射的优缺点,强调了其在提供动态性和灵活性的同时可能带来的性能开销和安全性问题。

一、反射是什么?

反射是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 语言的访问控制机制,访问私有字段和方法。这可能导致代码的安全性问题,需要谨慎使用
相关文章
|
10天前
|
弹性计算 人工智能 架构师
阿里云携手Altair共拓云上工业仿真新机遇
2024年9月12日,「2024 Altair 技术大会杭州站」成功召开,阿里云弹性计算产品运营与生态负责人何川,与Altair中国技术总监赵阳在会上联合发布了最新的“云上CAE一体机”。
阿里云携手Altair共拓云上工业仿真新机遇
|
7天前
|
机器学习/深度学习 算法 大数据
【BetterBench博士】2024 “华为杯”第二十一届中国研究生数学建模竞赛 选题分析
2024“华为杯”数学建模竞赛,对ABCDEF每个题进行详细的分析,涵盖风电场功率优化、WLAN网络吞吐量、磁性元件损耗建模、地理环境问题、高速公路应急车道启用和X射线脉冲星建模等多领域问题,解析了问题类型、专业和技能的需要。
2513 16
【BetterBench博士】2024 “华为杯”第二十一届中国研究生数学建模竞赛 选题分析
|
7天前
|
机器学习/深度学习 算法 数据可视化
【BetterBench博士】2024年中国研究生数学建模竞赛 C题:数据驱动下磁性元件的磁芯损耗建模 问题分析、数学模型、python 代码
2024年中国研究生数学建模竞赛C题聚焦磁性元件磁芯损耗建模。题目背景介绍了电能变换技术的发展与应用,强调磁性元件在功率变换器中的重要性。磁芯损耗受多种因素影响,现有模型难以精确预测。题目要求通过数据分析建立高精度磁芯损耗模型。具体任务包括励磁波形分类、修正斯坦麦茨方程、分析影响因素、构建预测模型及优化设计条件。涉及数据预处理、特征提取、机器学习及优化算法等技术。适合电气、材料、计算机等多个专业学生参与。
1520 14
【BetterBench博士】2024年中国研究生数学建模竞赛 C题:数据驱动下磁性元件的磁芯损耗建模 问题分析、数学模型、python 代码
|
3天前
|
存储 关系型数据库 分布式数据库
GraphRAG:基于PolarDB+通义千问+LangChain的知识图谱+大模型最佳实践
本文介绍了如何使用PolarDB、通义千问和LangChain搭建GraphRAG系统,结合知识图谱和向量检索提升问答质量。通过实例展示了单独使用向量检索和图检索的局限性,并通过图+向量联合搜索增强了问答准确性。PolarDB支持AGE图引擎和pgvector插件,实现图数据和向量数据的统一存储与检索,提升了RAG系统的性能和效果。
|
9天前
|
编解码 JSON 自然语言处理
通义千问重磅开源Qwen2.5,性能超越Llama
击败Meta,阿里Qwen2.5再登全球开源大模型王座
545 14
|
1月前
|
运维 Cloud Native Devops
一线实战:运维人少,我们从 0 到 1 实践 DevOps 和云原生
上海经证科技有限公司为有效推进软件项目管理和开发工作,选择了阿里云云效作为 DevOps 解决方案。通过云效,实现了从 0 开始,到现在近百个微服务、数百条流水线与应用交付的全面覆盖,有效支撑了敏捷开发流程。
19282 30
|
9天前
|
人工智能 自动驾驶 机器人
吴泳铭:AI最大的想象力不在手机屏幕,而是改变物理世界
过去22个月,AI发展速度超过任何历史时期,但我们依然还处于AGI变革的早期。生成式AI最大的想象力,绝不是在手机屏幕上做一两个新的超级app,而是接管数字世界,改变物理世界。
464 48
吴泳铭:AI最大的想象力不在手机屏幕,而是改变物理世界
|
1月前
|
人工智能 自然语言处理 搜索推荐
阿里云Elasticsearch AI搜索实践
本文介绍了阿里云 Elasticsearch 在AI 搜索方面的技术实践与探索。
18838 20
|
1月前
|
Rust Apache 对象存储
Apache Paimon V0.9最新进展
Apache Paimon V0.9 版本即将发布,此版本带来了多项新特性并解决了关键挑战。Paimon自2022年从Flink社区诞生以来迅速成长,已成为Apache顶级项目,并广泛应用于阿里集团内外的多家企业。
17527 13
Apache Paimon V0.9最新进展
|
1天前
|
云安全 存储 运维
叮咚!您有一份六大必做安全操作清单,请查收
云安全态势管理(CSPM)开启免费试用
360 4
叮咚!您有一份六大必做安全操作清单,请查收