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

相关文章

JVM 的类初始化机制

前言 当你在 Java 程序中new对象时,有没有考虑过 JVM 是如何把静态的字节码(byte code)转化为运行时对象的呢,这个问题看似简单,但清楚的同学相信也不会太多,这篇文章首先介绍 JVM 类初始化的机制,然后给出几个易出错的实例来分析,帮助大家更好理解这个知识点。 JVM 将字节码转化为运行时对象分为三个阶段,分别是:loading 、Linking、initialization

Spring Security 基于表达式的权限控制

前言 spring security 3.0已经可以使用spring el表达式来控制授权,允许在表达式中使用复杂的布尔逻辑来控制访问的权限。 常见的表达式 Spring Security可用表达式对象的基类是SecurityExpressionRoot。 表达式描述hasRole([role])用户拥有制定的角色时返回true (Spring security默认会带有ROLE_前缀),去

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti

Spring Security--Architecture Overview

1 核心组件 这一节主要介绍一些在Spring Security中常见且核心的Java类,它们之间的依赖,构建起了整个框架。想要理解整个架构,最起码得对这些类眼熟。 1.1 SecurityContextHolder SecurityContextHolder用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限…这些都被保

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

Java架构师知识体认识

源码分析 常用设计模式 Proxy代理模式Factory工厂模式Singleton单例模式Delegate委派模式Strategy策略模式Prototype原型模式Template模板模式 Spring5 beans 接口实例化代理Bean操作 Context Ioc容器设计原理及高级特性Aop设计原理Factorybean与Beanfactory Transaction 声明式事物

性能测试介绍

性能测试是一种测试方法,旨在评估系统、应用程序或组件在现实场景中的性能表现和可靠性。它通常用于衡量系统在不同负载条件下的响应时间、吞吐量、资源利用率、稳定性和可扩展性等关键指标。 为什么要进行性能测试 通过性能测试,可以确定系统是否能够满足预期的性能要求,找出性能瓶颈和潜在的问题,并进行优化和调整。 发现性能瓶颈:性能测试可以帮助发现系统的性能瓶颈,即系统在高负载或高并发情况下可能出现的问题

水位雨量在线监测系统概述及应用介绍

在当今社会,随着科技的飞速发展,各种智能监测系统已成为保障公共安全、促进资源管理和环境保护的重要工具。其中,水位雨量在线监测系统作为自然灾害预警、水资源管理及水利工程运行的关键技术,其重要性不言而喻。 一、水位雨量在线监测系统的基本原理 水位雨量在线监测系统主要由数据采集单元、数据传输网络、数据处理中心及用户终端四大部分构成,形成了一个完整的闭环系统。 数据采集单元:这是系统的“眼睛”,

Hadoop数据压缩使用介绍

一、压缩原则 (1)运算密集型的Job,少用压缩 (2)IO密集型的Job,多用压缩 二、压缩算法比较 三、压缩位置选择 四、压缩参数配置 1)为了支持多种压缩/解压缩算法,Hadoop引入了编码/解码器 2)要在Hadoop中启用压缩,可以配置如下参数