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

一、简单说明 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 方法
    @Override
    public 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” 之差!!

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

  • 8
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值