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 语言的访问控制机制,访问私有字段和方法。这可能导致代码的安全性问题,需要谨慎使用
相关文章
|
安全 Java 应用服务中间件
打破Tomcat中的双亲委派机制:探讨与实践
打破Tomcat中的双亲委派机制:探讨与实践
|
XML Java 数据库连接
Mybatis的mapper.xml 映射文件没有提示?
1、先来看一下Mapper.xml的头文件 ——1-1、从标文件中可以看到标黄的是Mapper.xml的命名空间,不提示就是该网络的地址获取不到信息 或 没有配置目录的mybatis-3-mapper.dtd
Mybatis的mapper.xml 映射文件没有提示?
|
11月前
|
监控 JavaScript 算法
如何使用内存监控工具来定位和解决Node.js应用中的性能问题?
总之,利用内存监控工具结合代码分析和业务理解,能够逐步定位和解决 Node.js 应用中的性能问题,提高应用的运行效率和稳定性。需要耐心和细致地进行排查和优化,不断提升应用的性能表现。
379 77
|
6月前
|
人工智能 Java 程序员
Java程序员在AI时代必会的技术:Spring AI
在AI时代,Java程序员需掌握Spring AI技术以提升竞争力。Spring AI是Spring框架在AI领域的延伸,支持自然语言处理、机器学习集成与自动化决策等场景。它简化开发流程,无缝集成Spring生态,并提供对多种AI服务(如OpenAI、阿里云通义千问)的支持。本文介绍Spring AI核心概念、应用场景及开发步骤,含代码示例,助你快速入门并构建智能化应用,把握AI时代的机遇。
1256 61
|
5月前
|
设计模式 消息中间件 Java
【设计模式】【行为型模式】命令模式(Command)
一、入门 什么是命令模式? 命令模式是一种行为设计模式,它将请求或操作封装为对象,从而使你可以用不同的请求对客户进行参数化,并支持请求的排队、记录、撤销等操作。 命令模式的核心是将“请求”封装为独立的
186 15
|
7月前
|
Java C++
JVM之符号引用和直接引用
本文介绍了Java中直接引用和符号引用的概念及其区别。直接引用与虚拟机布局相关,包括指向目标的指针、相对偏移量或间接句柄,通常在目标已被加载到内存时使用。符号引用则涉及编译原理,包含类/接口全限定名、字段及方法的名称和描述符,在Class文件中保存,需在运行时解析为具体内存地址。文中还详细说明了描述符规则以及数组类型的表示方式,帮助理解Java虚拟机的动态链接过程。
143 2
|
存储 人工智能 关系型数据库
PolarDB 与 AI/ML 集成的应用案例
【8月更文第27天】随着大数据和人工智能技术的发展,越来越多的企业开始探索将关系型数据库与 AI/ML 技术相结合的方式,以提高数据分析效率和业务智能化水平。阿里云的 PolarDB 是一款高性能的关系型数据库服务,支持多种数据库引擎,如 MySQL、PostgreSQL 和 Oracle。通过与阿里云的其他 AI/ML 服务集成,PolarDB 能够为企业提供端到端的数据处理和分析解决方案。
431 0
|
消息中间件 Kafka 测试技术
Kafka常用命令大全及kafka-console-consumer.sh及参数说明
该文章汇总了Kafka常用命令,包括集群管理、Topic操作、生产者与消费者的命令行工具使用方法等,适用于Kafka的日常运维和开发需求。
3522 3
|
数据安全/隐私保护 开发者 索引
Python 简易图形界面库easygui 对话框大全(续)
Python 简易图形界面库easygui 对话框大全(续)
286 1
Python 简易图形界面库easygui 对话框大全(续)
|
存储 网络协议 Java
Tomcat详解(十)——Tomcat性能调优
Tomcat详解(十)——Tomcat性能调优
437 1