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 语言的访问控制机制,访问私有字段和方法。这可能导致代码的安全性问题,需要谨慎使用
相关实践学习
消息队列+Serverless+Tablestore:实现高弹性的电商订单系统
基于消息队列以及函数计算,快速部署一个高弹性的商品订单系统,能够应对抢购场景下的高并发情况。
云安全基础课 - 访问控制概述
课程大纲 课程目标和内容介绍视频时长 访问控制概述视频时长 身份标识和认证技术视频时长 授权机制视频时长 访问控制的常见攻击视频时长
相关文章
|
7月前
|
Java
浅谈Java反射2
浅谈Java反射2
26 2
|
7月前
|
Java
浅谈Java反射1
浅谈Java反射
33 1
|
7月前
|
安全 Java
java反射篇
java反射篇
|
Java
什么是java反射
什么是java反射
142 0
|
7月前
|
存储 前端开发 Java
Java反射(1)
Java反射(1)
53 0
|
设计模式 Java 数据安全/隐私保护
Java反射
Java反射
69 0
|
Java 数据库连接 测试技术
Java反射详解
Java反射详解
83 0
|
Java
Java反射使用
Java反射使用
97 0
Java反射使用
|
安全 Java API
详解JAVA反射
1.概述 反射,JAVA提供的一种在运行时获取类的信息并动态操作类的能力。JAVA反射允许我们在运行时获取类的属性、方法、构造函数等信息,并能够动态地操作它们。 2.获取Class对象
135 0
|
XML Java 数据格式
Java反射介绍
Java的反射(reflection)机制是在**运行**状态中,对于任意一个类,都能够知道这个类的**所有属性和方法**;对于任意一个对象,都能够调用它的任意方法和属性,既然能拿到那么,我们就可以修改部分类型信息;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射(reflection)机制。
316 0

热门文章

最新文章