【Java】反射机制的技术解析和应用指南

您所在的位置:网站首页 java私有化单词 【Java】反射机制的技术解析和应用指南

【Java】反射机制的技术解析和应用指南

2024-04-13 15:51| 来源: 网络整理| 查看: 265

一、前言

反射是Java的特征之一,它允许程序在运行过程中获取类的全部信息,并可以操作类以及类的成员变量和成员方法。

本文,我们将学习Java反射机制的基本概念和基础用法。

image-20231030130940346.png

二、内容 2.1 概述 (1)什么是反射

反射,其实是一种机制,该机制允许程序在运行时可以获取、分析和操作一个对象的所有信息。

也就是说,在运行状态下,对于任意一个类,都能够知道该类的所有属性和方法,并允许调用。

有了反射机制,我们就无需在编译时知道这些元素的具体名称和类型,从而动态地检查和操作类的结构、实例化对象、调用对象的方法,获取和修改字段的值等等。

可能不太好理解,我们再来解释一下。比如正常情况下,我们在源代码阶段,会通过 new 的方式创建一个个类的实例,然后设置属性或者调用方法等,再编译为class字节码文件,最后通过 JVM 加载到内存进行解释执行。

注意,这种情况下代码中实例化的对象,可以说在源代码阶段或者说编译时就已经确定的。

但,如果说我现在无法在编码阶段通过 new 的方式去创建实例,但又需要去创建类的实例并调用其中的方法,需要在运行时根据一些条件或配置来创建不同类的实例来完成我们的工作,此时就可以用反射来解决这个需求。

(2)反射的例子

举个例子,假设你有一个未知类型的对象,你想在它上面调用一个名为'doSomething'的方法(如果存在的话)。Java的静态类型系统通常无法直接支持这种操作,除非该对象符合已知接口的规范。但使用反射,你的代码可以检查这个对象,查看它是否有一个名为'doSomething'的方法,然后如果需要的话进行调用。

再举一个例子,当我们使用IDEA时,如果想调用一个类的属性或方法,IDEA就会给出智能提示,这一功能就是通过反射技术实现的。

再来看一个代码示例,正常情况下我们要调用一个对象的方法,比如getName(),通常需要获取该对象实例:

public String getInfo(Student stu) { return stu.getName(); }

但是,如果无法获取 Student 类,也就说,我们有一个未知类型的对象:

public String getName(Object obj) { return ?; }

那么在这种情况下,通过反射可以解决这个问题。

备注:即使使用类型强转,我们也需要编写 import 语句,引用 Student 类。但我们的前提是无法获取该类。

因此,简单来说,通过反射创建的对象,可以无视权限修饰符,调用类里面的所有内容。对于任何一个对象,都能够调用它的任意属性和方法,这种动态获取信息以及动态调用对象方法的功能,就称为反射。

(3)主要组成部分

Java反射的主要组成部分包括以下几个关键类:

Class类:Class类是Java反射的核心,它代表一个类的元信息。我们可以使用Class类来获取类的名称、包名、父类、实现的接口、方法、字段等信息。 Constructor类:Constructor类用于创建新对象的实例,它允许你动态实例化类并传递参数。 Method类:Method类允许你调用类的方法,包括公有和私有方法。 Field类:Field类用于获取和设置类的字段的值,包括公有和私有字段。

反射,都是从class 字节码文件中获取内容的。通常来说,我们会利用获取字节码文件对象,然后利用反射来获取构造方法,进而创建对象;获取成员变量,进而赋值或获取值;获取成员方法,从而运行方法。

2.2 Class (1)简介

我们先来了解一下 Class 对象(又称为Java类的字节码文件对象)。

Class对象是Java中用来表示类的元信息的对象。

每个类在运行时都有一个关联的Class对象,它包含了类的名称、方法、字段、注解等信息。

简单来说,Class对象是对类的抽象和集合,Class对象的创建是在类加载阶段自动完成的,由Java虚拟机和类加载器来加载内的字节码文件并构造Class对象。

类加载是懒加载的,只有在第一次使用该类时才会加载到内存中,以减少内存占用和提高性能。

在 Java 中,对象可以分为两种:

一种是我们 new 出来的实例对象(Instance Objects)。实例对象是类的具体实例,它们包含了类定义的属性和方法,并在程序运行时进行创建和操作。 另一种是 JVM 生成的用来保存对应类的信息的 Class 对象(Class Objects)。包括类的结构、方法、字段、注解等。每个类都对应一个Class对象,它是JVM生成的,用来在运行时表示类的元信息。Class对象使得你可以在运行时检查和操作类,实现反射、运行时类型识别(RTTI)以及动态类加载等功能。 (2)获取Class对象

由于 Class类 没有public构造函数,它的对象只能由JVM创建:

image-20231029233227287.png

因此,我们无法通过 new 的方式来创建 Class 对象。

如果要获取Class对象,则可以通过以下三种方式:

通过Class.forName("全类名"):

通过提供类的全限定名(包括包名和类名)来获取类的字节码文件对象,这是最常用的方式,适合在源代码阶段获取类的Class对象。比如: Class clazz1 = Class.forName("com.example.reflectdemo.Student");

通过类的class属性:

在Java中,每个类都有一个特殊的class属性,它可以直接用于获取该类的Class对象。例如,如果你有一个类Student,那么可以这样: Class clazz2 = Student.class;

通过对象.getClass():

可以使用一个类的实例对象调用 getClass() 方法来获取该类的字节码文件对象。比如: Student s = new Student(); Class clazz3 = s.getClass();

需要注意的是,由于Class实例对象在 JVM 中是唯一的,所以,上述三种方法获取到的 Class 实例都是同一个实例对象。

反射的目的就是获取每个实例对象的信息。当我们拿到某个实例时,就可以通过反射来获取该对象的Class信息。从某种意义上讲,Class类是反射机制的入口,反射就是在运行期间通过 Class 对象访问类的构造器、属性、方法等一切信息。

下面举一些例子,用于演示从Class实例获取基本信息:

public class Main { public static void main(String[] args) { printClassInfo(int.class); printClassInfo(String.class); printClassInfo(String[].class); } static void printClassInfo(Class clazz) { System.out.println("类的名称: " + clazz.getName()); // 检查包信息是否为空 Package pkg = clazz.getPackage(); if (pkg != null) { System.out.println("类的包信息: " + pkg.getName()); } else { System.out.println("该类的包信息为空"); } // 检查父类信息是否为空 Class superclass = clazz.getSuperclass(); if (superclass != null) { System.out.println("类的父类信息: " + superclass.getName()); } else { System.out.println("该类的父类信息为空"); } // 检查实现的接口信息是否为空 Class[] interfaces = clazz.getInterfaces(); if (interfaces.length > 0) { System.out.println("实现的接口:"); for (Class iface : interfaces) { System.out.println(iface.getName()); } } else { System.out.println("该类的接口实现信息为空"); } System.out.println("----------------------------------------------"); } } (3)创建对象

我们可以使用Class实例来创建对应类型的对象。

具体的,我们会先获取构造方法,接着使用 newInstance() 方法来创建对象。

举一个例子:

import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; public class Main { public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { Class clazz = String.class; // 获取 String 类的构造函数,这里选择无参构造函数 Constructor constructor = clazz.getConstructor(); // 使用构造函数创建对象 Object obj = constructor.newInstance(); } }

在之前,我们可以使用Class的newInstance方法来创建对象,但是这种方法不推荐使用。从Java 9开始,newInstance方法已过时,而更推荐使用构造函数来创建对象,因为它提供了更多的灵活性,尤其是当类具有多个构造函数时。

2.3 反射的基本运用 (1)构造方法

Java反射机制中用于获取类的构造方法信息的方法如下所示:

Constructor[] getConstructors() 说明:这个方法用于获取类的所有公有构造方法的数组。 访问权限:只能获取公有构造方法,不能获取私有构造方法。 Constructor[] getDeclaredConstructors() 说明:这个方法用于获取类的所有构造方法的数组,不考虑访问权限,包括公有和私有构造方法。 访问权限:能够获取所有构造方法,包括私有构造方法。 Constructor getConstructor(Class... parameterTypes) 说明:这个方法用于获取类中的特定公有构造方法,需要提供构造方法的参数类型。 访问权限:只能获取公有构造方法,不能获取私有构造方法。 Constructor getDeclaredConstructor(Class... parameterTypes) 说明:这个方法用于获取类中的特定构造方法,需要提供构造方法的参数类型,不考虑访问权限,包括公有和私有构造方法。 访问权限:能够获取所有构造方法,包括私有构造方法。

需要注意的是,如果获取了私有构造方法,必须在使用之前临时修改其访问权限,否则将无法使用。这可以通过setAccessible(true)方法来实现。

代码示例:

比如,现在有一个类:

image-20231030093742768.png

我们来看代码:

package org.example; import java.lang.reflect.Constructor; public class ReflectDemo { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException { // 1. 获取类的Class对象 Class clazz = Class.forName("org.example.MyClass"); // 2. 获取构造方法对象 // 获取所有公有构造方法(public) Constructor[] constructors1 = clazz.getConstructors(); System.out.println("所有公共的构造方法:"); for (Constructor con : constructors1) { System.out.println(con); } System.out.println("======================="); // 获取所有构造方法,包括私有的 Constructor[] constructors2 = clazz.getDeclaredConstructors(); System.out.println("所有构造方法,包括私有的:"); for (Constructor con : constructors2) { System.out.println(con); } System.out.println("======================="); // 获取指定的空参构造方法 Constructor con1 = clazz.getConstructor(); System.out.println("获取指定的空参构造方法:"); System.out.println(con1); System.out.println("======================="); // 获取指定参数类型的构造方法 Constructor con2 = clazz.getConstructor(int.class); System.out.println("获取指定int参数类型的构造方法:"); System.out.println(con2); System.out.println("======================="); // 获取指定的构造方法,包括公有和私有的 Constructor con3 = clazz.getDeclaredConstructor(String.class); System.out.println("获取指定String类型参数的构造方法,包括私有的:"); System.out.println(con3); // 每次获取构造方法对象都会创建一个新的实例 // 了解 System.out.println(con3 == con1); } }

运行结果:

image-20231030093920903.png

(2)创建对象

首先,写一个Javabean:

image-20231030094449263.png

接着,我们来写测试代码。

需求一:获取空参构造方法,并创建对象。

image-20231030094901581.png

需求二:获取带参构造方法,并创建对象。

image-20231030095214878.png

(3)成员变量

Java反射机制中用于获取类的字段(成员变量)信息的方法如下所示:

Field[] getFields() 说明:这个方法用于获取类的所有公有成员变量对象的数组。 访问权限:只能获取公有字段,不能获取私有字段。 Field[] getDeclaredFields() 说明:这个方法用于获取类的所有成员变量对象的数组,不考虑访问权限,包括公有和私有字段。 访问权限:能够获取所有字段,包括私有字段。 Field getField(String name) 说明:这个方法用于获取类中的特定公有字段对象,需要提供字段的名称。 访问权限:只能获取公有字段,不能获取私有字段。 Field getDeclaredField(String name) 说明:这个方法用于获取类中的特定字段对象,需要提供字段的名称,不考虑访问权限,包括公有和私有字段。 访问权限:能够获取所有字段,包括私有字段。

我们来看一个简单的示例:

首先依然是这个 JavaBean类:

image-20231030095827071.png

示例代码:

image-20231030100421451.png

获取成员变量后,我们可以获取成员变量的值,获取修改值。需要注意的是,如果如果成员变量是私有的,那么在访问前需要设置其可访问性,可以使用 setAccessible(true) 方法来实现:

Field field = yourClass.getDeclaredField("fieldName"); field.setAccessible(true);

这样,你就可以在访问私有成员变量时绕过访问权限检查。

这里有两个主要的方法,用于获取成员变量后进行获取值和修改值。即使用 set 方法来设置变量的值,以及 get 方法来获取变量的值。

获取成员变量的值: Field field = yourClass.getDeclaredField("fieldName"); field.setAccessible(true); // 如果是私有成员变量,需要设置为可访问 Object value = field.get(yourObject); 修改成员变量的值: Field field = yourClass.getDeclaredField("fieldName"); field.setAccessible(true); // 如果是私有成员变量,需要设置为可访问 field.set(yourObject, newValue); (4)成员方法

在反射中,我们可以回去类的方法信息,为了获取类的成员方法,我们可以使用以下语句:

Method[] getMethods() 说明:这个方法用于获取类的所有公有成员方法对象的数组。 访问权限:只能获取公有方法,不能获取私有方法。 示例用法:用于获取一个类中的所有公有方法。 Method[] getDeclaredMethods() 说明:这个方法用于获取类的所有成员方法对象的数组,不考虑访问权限,包括公有和私有方法。 访问权限:能够获取所有方法,包括私有方法。 示例用法:用于获取一个类中的所有方法,包括私有方法。 Method getMethod(String name, Class... parameterTypes) 说明:这个方法用于获取类中的特定公有方法对象,需要提供方法名和参数类型。 访问权限:只能获取公有方法,不能获取私有方法。 示例用法:用于获取一个类中指定名称和参数类型的公有方法。 Method getDeclaredMethod(String name, Class... parameterTypes) 说明:这个方法用于获取类中的特定方法对象,需要提供方法名和参数类型,不考虑访问权限,包括公有和私有方法。 访问权限:能够获取所有方法,包括私有方法。 示例用法:用于获取一个类中指定名称和参数类型的方法,包括私有方法。

我们来看这个例子:

image-20231030104534669.png

测试代码如下:

image-20231030105818638.png

当我们获取到对应的成员方法后,就可以运行该方法。

反射机制为我们提供了 invoke() 方法,用于动态调用对象。

Object invoke(Object obj, Object... args);

注意事项如下:

参数一 (obj):表示要调用方法的对象实例,即用哪个对象来调用方法。这通常是一个已经实例化的对象。 参数二 (args):是一个可变参数,用于传递给方法的参数。这里可以传递任意数量和类型的参数,以满足方法的参数要求。如果方法不需要参数,可以传递空参数列表(new Object[] {})。 返回值:这个方法的返回值是被调用方法的返回值,如果被调用方法没有返回值(void),则 invoke 方法返回 null。

我们同样需要注意访问权限。如果调用的方法是私有的,需要在 Method 对象上设置 setAccessible(true) 来临时修改访问权限以允许调用。

示例代码如下:

image-20231030111519677.png

三、总结

总的来说,Java反射的核心就是在运行时动态加载类或调用类的属性或方法。

反射的用途有很多,比如开发通用框架,通过 XML 文件来配置Bean,为了保证框架的通用性,我们需要根据配置文件来加载不同的类对象,调用不同的方法,这个时候就需要用到反射,在运行时动态加载需要的类。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3