JavaSE初学——简单介绍什么是反射,以及反射中最常见的基本用法

2024-08-27 18:12

本文主要是介绍JavaSE初学——简单介绍什么是反射,以及反射中最常见的基本用法,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 一、简单说明 Java 中反射的基本概念
  • 二、简单介绍反射中常用的几个类
  • 三、通过代码实现常见的反射
    • 1、代码解释如何获取 Class 类对象
    • 2、获取 Class 中的构造方法
    • 3、获取 Class 中的单个字段
    • 4、针对 Class 中的 main 方法通过反射获取调用
    • 5、针对 Class 类中的成员方法的获取调用
    • 6、实现通过反射运行 txt 配置文件的内容
    • 7、实现通过 反射 越过泛型检查
  • 三、总结

一、简单说明 Java 中反射的基本概念

Java 的反射机制是指,在运行状态中
对于任何一个 都能知道这个类中的所有属性和方法。
对于任意一个对象,都能 调用 其中的任意一个属性和方法。
这种 动态的获取信息 和 动态的调用对象方法 的功能被称之为 Java 的反射。

简而言之,通过反射,可以在运行时获取类的信息,创建类的实例,调用类的方法,访问和修改类的字段等。

二、简单介绍反射中常用的几个类

  • java.lang.Class: 代表类的元数据。每一个类都有与之对应的 Class 对象,可以通过 .class 语法、Class.forName() 方法或对象的 getClass() 方法来获取。

  • java.lang.reflect.Method: 代表类的获取方法。

  • java.lang.reflect.Field: 代表类的字段。

  • java.lang.reflect.Constructor: 代表类的构造函数。

三、通过代码实现常见的反射

这里需要做一些前置的准备工作:
创建出一个被反射的类,这里创建了一个 Student 类:

public class Student {//    ------在这里定义出一些字段------public String name;public String name02;protected int age;char sex;private String phoneNUM;// 这里重写一下 toString 方法@Overridepublic String toString() {return "Student [name=" + name + ", age=" + age + ", sex=" + sex+ ", phoneNum=" + phoneNUM + "]";}//    ------这里创建出一个 Student 类来便于反射获取并使用------// 没有属性的带有一个 String 参数的构造方法Student (String str) {System.out.println("没有属性的带有 String 参数的构造方法 str = " + str);}// 公共类型的构造方法public Student() {System.out.println("没有参数的构造方法(public 类型)");}// 包含一个参数的构造方法public Student(char name) {System.out.println("(包含参数的 public 类型方法)姓名:" + name);}// 有多个参数的构造方法public Student(String name, int age) {System.out.println("(公有的包含多个参数的构造方法)姓名:" + name + "年龄:" + age);}// 收到保护的构造方法protected Student(boolean n) {System.out.println("这是收到保护的构造方法 n = " + n);}// 私有的构造方法private Student(int age) {System.out.println("这是私有的构造方法 年龄:" + age);}//      创建 Student 类中的成员方法public void show1(String s) {System.out.println("调用了:共有的,包含 String 类型的 show1() 方法 s: " + s);}protected void show2() {System.out.println("调用了:受保护的,无参数的 show2() 方法");}void show3() {System.out.println("调用了:默认的,没有参数的 show3()");}private String show4(int age) {System.out.println("调用了:私有的,带返回值的,int 参数的 show4(): age = " + age);return "return show04()";}//    ------通过反射运行配置文件内容------public void show() {System.out.println("this is show()");}//     ------设置一个 main 方法------public static void main(String[] args) {System.out.println("Student 中的 main 方法执行了!!");}
}

这个类中的所有方法都是用来辅助后续所有的代码解释的!!!

1、代码解释如何获取 Class 类对象

首先先来解释一下如何获取 Class 类对象。

这里用不到这个 Student 类中的任何方法,在这里 Student 类的主要作用就是被 Class 类反射调用,获得到当前的类对象。

在这里,有三种方式可以获取到 Student 类对象,下面通过代码分别进行简单的解释:

  • 第一种,通过 Object 中的 getClass() 进行获取:
    在这种获取方式中,需要先通过 new 来生产出一个 Class 对象,才能被获取。
    代码如下:
   // 第一种:通过 Object ——> getClass()Student stu1 = new Student();   // 通过 new 来产生一个 Student 对象,一个 Class 对象Class stuClass = stu1.getClass();   // 这里则是通过这个对象中的 get 操作来获取到 Class 对象 (当然也要被 Class 类型进行接受)System.out.println(stuClass.getName());     // 打印当前 Class(类) 的类名称

运行结果如下:

在这里插入图片描述

这里的 getClass() 方法获取到的元素对应的返回类型是 Class 类型。
需要注意的是,没有参数的构造方法,在该类被创建的同时也会被创建出来。

  • 第二种,通过 类 的“静态” Class 属性获取
    这里直接通过 类名.class 的形式即可获取。
    代码如下:
        // 第二种:通过 类 的 “静态” class 属性来获取Class ints = Integer.class;     // Integer 类型的 “静态” class 属性也是可以被获取的Class stuClass2 = Student.class;System.out.println("这里是 Integer : " + ints);System.out.println("这里是 stuClass2: " + stuClass2);  // 可以看到这里获取到的是 包名 + 类名

运行结果如下:

在这里插入图片描述

观察结果,不只是自己创建的类,jar 包中自带的类属性同样可以被获取到。

  • 第三种,通过 Class 类中的静态方法 forName() 获取
    这种获取方式是最常用也是相对最容易理解的一种方式,(在之后的代码中如果需要获取某个类,本人都会使用这种方式获取!!)
        try {Class stuClass3 = Class.forName("Student");Class Int = Class.forName("java.lang.Integer");     // 这里的 String 类型字符串是要通过包名开始写到详细的类名System.out.println("这是 Student 类: " + stuClass3);System.out.println("这是 Integer 类: " + Int);} catch (ClassNotFoundException e) {e.printStackTrace();}

运行结果如下:
在这里插入图片描述

2、获取 Class 中的构造方法

这里的构造方法,在前面的前置代码中已经实现,如图:
在这里插入图片描述
所谓构造方法就是面相对象编程中的一个特殊方法,在 类实例化 时会被自动调用 构造方法不能像其他方法那样被直接调用。它们只能在新对象创建时由Java虚拟机(JVM)自动调用。
还需要注意的一点是,每一个属性的构造方法都只能创建一次。


对于构造方法获取构造方法,这里要知道一个关键字:
Constructor

(1)获取所有的 public 类型的构造方法

        // 1.首先需要加载 Class 类对象// 这里通过最常用的 forName 方法来获取到类对象Class stu = Class.forName("Student");// 创建 Construct 类型的数组,用于接受后续获取到的实例化对象Constructor[] conArray = null;// 获取所有的 public 类型的构造方法System.out.println("********所有的公有的构造方法********");conArray = stu.getConstructors();for (Constructor c: conArray) {System.out.println(c);}

观察运行结果:
在这里插入图片描述

(2)获取所有属性的构造方法

        System.out.println("********获取多有的构造方法(包括:私有、受保护、默认、公有)********");conArray = stu.getDeclaredConstructors();for (Constructor c: conArray) {System.out.println(c);}

观察运行结果:
在这里插入图片描述

(3)获取单个的没有参数的 public 属性构造方法

        // 获取公有的,没有参数的构造方法System.out.println("********获取公有的,没有参数的构造方法********");Constructor con = stu.getConstructor(null);System.out.println(con);

观察运行结果:

在这里插入图片描述

(4)尝试获取单个的包含参数的 private 属性构造方法

        // 尝试获取私有的包含参数的构造方法System.out.println("********获取私有的构造方法,并调用********");Constructor con2 = stu.getDeclaredConstructor(int.class);System.out.println("私有的构造方法" + con2);con2.setAccessible(true);   // 暴力访问,直接忽略访问修饰符// 这就相当于单独的 创建并且调用了通过getDeclaredConstructor获取的构造方法con2.newInstance(100);

观察运行结果:

在这里插入图片描述

总的来讲,可以将这里获取构造方法的形式总结为下面的几点:

  • 要获取 多个 public 属性的构造方法使用 —— getConstructors()
  • 要获取 多个 “各种” 属性的构造方法使用 —— getDeclaredConstructors()

(这两点需要注意的是要使用 Construct 类型的数组来接受)

  • 要获取 单个 public 属性的构造方法使用 —— getConstructor(类型.class)
  • 要获取 单个 “其他” 属性的构造方法使用 —— getDeclaredConstructor(类型.class)
    (这两点获取 单个 构造方法的情况,需要注意的是方法与多个之间的一个细小差别 “s”

3、获取 Class 中的单个字段

同样的,这些字段也在之前的前置代码中进行了实现,如图:
在这里插入图片描述
为了方便后续代码观察结果,这里重写了一下 toString 方法。


对于 获取字段 这里需要知道一个关键字:
Field

(1)获取所有公共类型的字段

        // 首先获取 class 对象Class stuClass = Class.forName("Student");// 定义出一个 Field 类型的数组,在这里接受从 student 中返回的值Field[] fieldArray = null;// 获取所有的公共类型字段System.out.println("********获取所有的公共字段********");fieldArray = stuClass.getFields();for (Field f : fieldArray) {System.out.println(f);}

观察运行结果:

在这里插入图片描述

(2)获取 “所有” 类型的字段

        System.out.println("********获取所有的字段(包括私有、受保护、默认)********");fieldArray = stuClass.getDeclaredFields();for (Field f : fieldArray) {System.out.println(f);}

观察运行结果:

在这里插入图片描述

(3)尝试获取 “公共” 字段并 调用

        // 获取公共字段并且尝试进行调用System.out.println("********获取公共字段并且调用********");Field f1 = stuClass.getField("name");Field f2 = stuClass.getField("name02");// 这里展现已经获取到了两个 public 类型的 参数System.out.println("这是 name :" + f1);System.out.println("这是 name02:" + f2);// 创建出一个 Student 对象 ==> new Student();Object obj1 = stuClass.getConstructor().newInstance();// 这里的 set 就是赋值操作即 ==> stu.name = "名称1"f1.set(obj1, "名称1");f2.set(obj1, "名称2");// 进行验证,这里的操作是将 obj 进行强制类型转换,转换为 Student 之后进行验证Student stu = (Student) obj1;System.out.println("验证姓名:" + stu.name);System.out.println("验证姓名:" + stu.name02);

观察运行结果:
在这里插入图片描述

在这里简单解释一下上面的代码
第一部分:通过 getField 获取到公共类型的字段,并且通过 “打印” 出来演示
第二部分:通过 反射 new 出来一个 Student 对象。之后通过 set 进行赋值操作。

(4)尝试获取 “私有” 字段并 调用

        // 尝试获取私有字段并且调用System.out.println("********获取所有的字段(包括私有、受保护、默认)并且调用********");Field pf1 = stuClass.getDeclaredField("phoneNUM");Field pf2 = stuClass.getDeclaredField("age");System.out.println(pf1);System.out.println(pf2);// 暴力反射,解除私有设定pf1.setAccessible(true);pf2.setAccessible(true);// 这里的操作类似于 Student stu = new Student()Object obj2 = stuClass.getConstructor().newInstance();pf1.set(obj2, "1539999999999");pf2.set(obj2, 11);Student stu2 = (Student) obj2;System.out.println("获取到所有类型字段后的 toString 方法" + stu2.toString());

观察运行结果:

在这里插入图片描述

总的来讲,字段的获取和上面解释过的构造方法获取有着异曲同工之意:
同样可以总结为下面几点:

  • 要获取 多个 public 属性的参数需要 —— getFields()
  • 要获取 多个 “各种” 属性的参数需要 —— getDeclaredFields()

(这两种获取需要注意的是,得到的元素需要通过 Field 类型的数组来进行接受

  • 要获取 单个 public 属性的参数需要 —— getField(“参数名称”)
  • 要获取 单个 “其他” 属性的参数需要 —— getDeclaredField(“参数名称”)

(这两种获取需要注意的是,与 “多个” 获取相比方法缺少了 “s”

至于在这之中对于 参数值 的设置则在 newInstance() 之后,通过 set() 直接插入即可。


4、针对 Class 中的 main 方法通过反射获取调用

这是在 Student 类中的 main 方法

在这里插入图片描述

在这里,对于不是构造方法的调用,这里需要认识到一个关键字:Method

        // 首先获取到 Student 类Class stu = Class.forName("Student");// 获取 main 方法Method getMain = stu.getMethod("main", String[].class);System.out.println(getMain);// 调用 main 方法getMain.invoke(null, (Object) new String[]{});

观察运行结果:

在这里插入图片描述
可以看到,通过 getMethod() 获取到的 main 方法。

虽然 main 方法比较特殊,但是获取方式仍然根据 参数对应的形式进行获取

最后通过 invoke() 方法调用 main 方法,在这里调用的形式就比较特殊了,因为参数类型是一个数组,这里要触发调用,就需要传递进去一个数组参数。
(实际上这里不管数组中有没有参数都 OK ,哪怕只是一个 {} 都行!)

5、针对 Class 类中的成员方法的获取调用

同样,这里的方法也是在前置操作中实现过的,如图:
在这里插入图片描述
在这里,各种情况的方法都进行了创建。


这里获取成员方法需要用到的关键字和 main 方法使用的相同:Method

(1)获取所有 公有 的成员方法

        // 首先,通过反射获取到 student 类Class stuClass = Class.forName("Student");// 创建出一个数组Method[] methodArray = null;// 首先获取所有的公有的成员方法System.out.println("********获取所有的“公有”方法********");methodArray = stuClass.getMethods();for (Method m : methodArray) {System.out.println(m);}

观察运行结果:

在这里插入图片描述

在这里,很多隐式的方法都会被获取到后罗列出来,
红色范围 是我们自己所创建出的方法。(其中包含了 main 和 toString)
黄色范围 才是我们创建的成员方法。(是我们在这里需要的答案)。

(2)获取 “所有属性” 的成员方法

        // 获取所有的方法,包括 私有方法 在内System.out.println("********获取所有类型的 成员方法,包括私有类型********");methodArray = stuClass.getDeclaredMethods();for (Method m : methodArray) {System.out.println(m);}

观察运行结果:

在这里插入图片描述

在这里,红色区域 是我们创建出的成员方法。(是这里我们想要的结果)

(3)获取 “公有” 的成员方法并尝试调用

        // 获取公有的 show1 方法System.out.println("********获取所有的“公有”的 show1 方法********");Method m = stuClass.getMethod("show1", String.class);System.out.println("这是 show1 方法: " + m);// 实例化 student 类Object obj1 = stuClass.getConstructor().newInstance();// 为 show1 方法添加上参数m.invoke(obj1,"这是成员方法 String 参数的测试");

观察运行结果:

在这里插入图片描述

这里的获取、调用的方式和之前的 main 方法的调用和获取是相同的,这里就不过多赘述了。

(4)获取 私有 的成员方法并尝试调用

        // 获取私有的 show2 方法System.out.println("********获取所有的“私有”方法 show4 ********");Method pm = stuClass.getDeclaredMethod("show4", int.class);System.out.println(pm);// 要调用这个方法就需要解除私有限定pm.setAccessible(true);Object obj2 = stuClass.getConstructor().newInstance();Object re = pm.invoke(obj2, 100);System.out.println("私有方法 String 的返回值: " + re);

观察运行结果:

在这里插入图片描述

总的来讲,这里同样的可以分为四点:

  • 要获取 所有 的 “公有” 的成员方法 —— getMethods()
  • 要获取 所有 的 “各种属性” 的成员方法 —— getDeclaredMethods()

这两种获取需要注意的是,得到的元素需要通过 Field 类型的数组来进行接受)

  • 要获取 一个 “公有” 的成员方法 —— getMethod(“方法名称”, 参数类型.class)
    要获取 一个 “私有” 的成员方法 —— getDeclaredMethod(“方法名称”, 参数类型.class)

这里针对成员方法的调用流程在进行一下简单的描述:
首先通过 getConstructor().newInstance() 实例化一个对象。
之后通过 invoke(obj2, 100) 添加参数来调用方法。

6、实现通过反射运行 txt 配置文件的内容

要实现这个操作,首先需要做前置工作。也就是创建相应的软件包以及相应的 txt 文件,如图:
在这里插入图片描述
在这里的 txt 文件中,我们存放的是键值对类型的信息,如图:
在这里插入图片描述
前面的 key 名字可以随便起,但是后面的 value 必须是 方法名

//    ------通过反射运行配置文件内容------public void show(int a) {System.out.println("this is show()" + a);}

实现核心的反射代码如下:

    // 首先实现一个获取 test.txt 文件的方法public static String getValue(String key) throws Exception{Properties pro = new Properties();      // 获取配置文件的对象FileReader in = new FileReader("src/txtpackage/test.txt");     // 获取输入流pro.load(in);   // 将流加载到配置文件对象中in.close();return pro.getProperty(key);    // 根据 key 返回 value 的值}//    通过反射运行配置文件内容public static void main(String[] args) throws Exception{// 首先通过反射获取 Class 对象Class stu = Class.forName("Student");// 获取到  stu 中的 show 方法Method m = stu.getMethod(getValue("methodName"), int.class);   // 这里的 value 值对应的就是 show// 调用 show 方法m.invoke(stu.getConstructor().newInstance(), 1);}

这里的 getValue() 方法主要是实现对于 txt 文件中的数据流进行获取,通过 main 方法中传递过来的 key 找到对应的 value 进行返回。
这里 main 方法中的实现就很好解释:
就是获取 成员方法 的基本操作
(没有参数的方法更好获取,只需要不填写后面的 int.class 以及 对应的 参数 1 值 即可)

运行结果如下:
在这里插入图片描述

7、实现通过 反射 越过泛型检查

直接代码如下:

//    这个类实现的是通过反射翻越泛型检查public static void main(String[] args) throws Exception{ArrayList<String> str = new ArrayList<>();str.add("aaa");str.add("bbb");//str.add(100);// 在这里的 str 中只能插入 String 类型的数据,但是通过反射,就可以将 100 这个 int 类型的元素添加进去// 这样就做到了跳过 泛型检查Class strClass = str.getClass();    // 这里是要找到 str 的字节码Method m = strClass.getMethod("add", Object.class);// 调用其中的 add 方法,并且向其中添加整形类型的数据m.invoke(str, 100);// 遍历参数for (Object o : str) {System.out.println(o);}}

可以看到,这里我们创建的是一个 String 类型的顺序表。要添加元素使用 .add() 方法进行添加即可。
在这里插入图片描述
如图我们要添加 整形 元素,是不能直接添加的。

先来观察运行结果:
在这里插入图片描述
可以看到,这里尽然将 100 这个整形元素添加了进去!!!

这里是引用
重点就是在这里,通过反射的形式调用其 add 方法。直接就越过了泛型的类型检查操作!

三、总结

反射的实际运用对于初学者可能并不多,但是也是必须要有简单的了解的。

本篇文章主要解释了反射中对于类中的 (构造方法、成员方法、单个字段) 的 获取、调用 操作。总的来说,也就是三个最核心的关键字 Constructor、Method、Field

最后还需要注意一点,获取 单个 和 多个方法的关键 API 非常相似,但是也并不容易搞错就是一个 “s” 之差!!

码子不易,您小小的点赞是对我最大的鼓励!!!

这篇关于JavaSE初学——简单介绍什么是反射,以及反射中最常见的基本用法的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1112405

相关文章

Java数组初始化的五种方式

《Java数组初始化的五种方式》数组是Java中最基础且常用的数据结构之一,其初始化方式多样且各具特点,本文详细讲解Java数组初始化的五种方式,分析其适用场景、优劣势对比及注意事项,帮助避免常见陷阱... 目录1. 静态初始化:简洁但固定代码示例核心特点适用场景注意事项2. 动态初始化:灵活但需手动管理代

python中各种常见文件的读写操作与类型转换详细指南

《python中各种常见文件的读写操作与类型转换详细指南》这篇文章主要为大家详细介绍了python中各种常见文件(txt,xls,csv,sql,二进制文件)的读写操作与类型转换,感兴趣的小伙伴可以跟... 目录1.文件txt读写标准用法1.1写入文件1.2读取文件2. 二进制文件读取3. 大文件读取3.1

Java使用SLF4J记录不同级别日志的示例详解

《Java使用SLF4J记录不同级别日志的示例详解》SLF4J是一个简单的日志门面,它允许在运行时选择不同的日志实现,这篇文章主要为大家详细介绍了如何使用SLF4J记录不同级别日志,感兴趣的可以了解下... 目录一、SLF4J简介二、添加依赖三、配置Logback四、记录不同级别的日志五、总结一、SLF4J

将Java项目提交到云服务器的流程步骤

《将Java项目提交到云服务器的流程步骤》所谓将项目提交到云服务器即将你的项目打成一个jar包然后提交到云服务器即可,因此我们需要准备服务器环境为:Linux+JDK+MariDB(MySQL)+Gi... 目录1. 安装 jdk1.1 查看 jdk 版本1.2 下载 jdk2. 安装 mariadb(my

SpringBoot中配置Redis连接池的完整指南

《SpringBoot中配置Redis连接池的完整指南》这篇文章主要为大家详细介绍了SpringBoot中配置Redis连接池的完整指南,文中的示例代码讲解详细,具有一定的借鉴价值,感兴趣的小伙伴可以... 目录一、添加依赖二、配置 Redis 连接池三、测试 Redis 操作四、完整示例代码(一)pom.

Java 正则表达式URL 匹配与源码全解析

《Java正则表达式URL匹配与源码全解析》在Web应用开发中,我们经常需要对URL进行格式验证,今天我们结合Java的Pattern和Matcher类,深入理解正则表达式在实际应用中... 目录1.正则表达式分解:2. 添加域名匹配 (2)3. 添加路径和查询参数匹配 (3) 4. 最终优化版本5.设计思

Java使用ANTLR4对Lua脚本语法校验详解

《Java使用ANTLR4对Lua脚本语法校验详解》ANTLR是一个强大的解析器生成器,用于读取、处理、执行或翻译结构化文本或二进制文件,下面就跟随小编一起看看Java如何使用ANTLR4对Lua脚本... 目录什么是ANTLR?第一个例子ANTLR4 的工作流程Lua脚本语法校验准备一个Lua Gramm

Java字符串操作技巧之语法、示例与应用场景分析

《Java字符串操作技巧之语法、示例与应用场景分析》在Java算法题和日常开发中,字符串处理是必备的核心技能,本文全面梳理Java中字符串的常用操作语法,结合代码示例、应用场景和避坑指南,可快速掌握字... 目录引言1. 基础操作1.1 创建字符串1.2 获取长度1.3 访问字符2. 字符串处理2.1 子字

Java Optional的使用技巧与最佳实践

《JavaOptional的使用技巧与最佳实践》在Java中,Optional是用于优雅处理null的容器类,其核心目标是显式提醒开发者处理空值场景,避免NullPointerExce... 目录一、Optional 的核心用途二、使用技巧与最佳实践三、常见误区与反模式四、替代方案与扩展五、总结在 Java

基于Java实现回调监听工具类

《基于Java实现回调监听工具类》这篇文章主要为大家详细介绍了如何基于Java实现一个回调监听工具类,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录监听接口类 Listenable实际用法打印结果首先,会用到 函数式接口 Consumer, 通过这个可以解耦回调方法,下面先写一个