Java中的反射(20分钟带你了解Java中的反射)

类加载器

1、类加载

当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过:类的加载、类的连接、类的初始化这三个步骤来对类进行初始化。如果不出现意外,JVM将会连续完成这三个步骤,所以有时也把这三个步骤称为:类加载或类的初始化。

类的加载

  • 就是指将 class文件读入内存,并为之创建一个 java.lang.Class 对象;
  • 任何类被使用时,系统都会为之建立一个 java.lang.Class 对象。

类的连接

  • 验证阶段:用于检验被加载的类是否有正确的内部结构,并和其他类协调一致;
  • 准备阶段:负责为类的类变量分配内存,并设置默认初始值;
  • 解析阶段:将类的二进制数据中的符号引用替换为直接引用。

类的初始化

  • 在该阶段,主要就是对类变量进行初始化
类的初始化步骤:
1. 假如类还未被加载和连接,则程序先加载并连接该类;
2. 假如该类的直接父类还未被初始化,则先初始化其直接父类;
3. 假如类中有初始化语句,则系统依次执行这些初始化语句。

注意:在执行第2步的时候,系统对直接父类的初始化步骤也遵循初始化步骤1~3
所以JVM最先初始化的总是Object类
类的初始化时机:
创建类的实例;
调用类的类方法(静态方法);
访问类或者接口的类变量,或者为该类变量赋值;
调用反射方式来强制创建某个类或接口对应的 java.lang.Class 对象;
初始化某个类的子类;
直接调用 java.exe命令运行某个主类。

2、类加载器

类加载器的作用

作用:
负责将 .class 文件加载到内存,并为之生成对应的 java.lang.Class 对象

反射

1、反射概述

现有 Student和Teacher两个类
这两个类若想使用,首先就得通过类加载器加载对应的 .class 文件到内存,每一个 .class 文件中都应该包含:成员变量、构造方法、成员方法等信息。而每一个 .class 文件都包含这些信息,也就是说所有的 .class文件都有这样的信息,那么我们有没有一个类来描述这些信息呢?肯定是有的,这个类就是 Class类,所以这个类就是所有 .class对象所对应的类型。我们通过Class类里面的对象去使用这些成员变量、成员方法等,而不再通过 Student、Teacher去直接使用。

总结:
Java反射机制是指在运行时去获取一个类的变量和方法信息,然后通过获取到的信息来创建对象,调用方法的一种机制。由于这种动态性,可以极大地增强程序的灵活性,程序不用在编译期就完成确定,在运行期仍可以扩展。

2、开发步骤

1、创建 Student.java

package test;

/**
 * @author Zeng Li
 * @version 1.0
 * @date 2022/3/2 0:30
 */
public class Student {

    // 一个私有、一个默认、一个公共
    private String name;
    int age;
    public String address;


    public Student() {

    }
    private Student(String name) {
        this.name = name;
    }
    Student(String name,int age){
        this.name = name;
        this.age = age;
    }

    public Student(String name, int age, String address) {
        this.name = name;
        this.age = age;
        this.address = address;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public void method1() {
        System.out.println("method1()执行了...");
    }

    public void method2(String s) {
        System.out.println("method2():" + s);
    }

    public String method3(String s, int i) {
        return s + "," + i;
    }

    private void function() {
        System.out.println("function()执行了...");
    }



    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", address='" + address + '\'' +
                '}';
    }
}

2、获取Class类的对象

我们要想通过反射去使用一个类,首先我们要获取到该类的字节码文件对象,也就是类型为Class类型的对象
@Test
public void test() throws ClassNotFoundException {
    // 第一种方式获取Class对象
    Class<Student> s1 = Student.class;
    System.out.println(s1);
    Class<Student> s2 = Student.class;
    // 一个类在内存中只有一个字节码文件对象
    System.out.println(s1 == s2); //true

    // 第二种方式获取Class对象
    Student student = new Student();
    Class<? extends Student> s3 = student.getClass();
    System.out.println(s1 == s3); //true

    // 第三种方式获取Class对象
    Class<?> s4 = Class.forName("test.Student"); // 推荐写法
    System.out.println(s1 == s4); //true
}

3、反射获取构造方法

  • 方法1

    aClass.getConstructors();
    // 返回一个包含 Constructor对象的数组,反映由该Class对象表示的类的所有 公共 构造函数
    

    测试

    @Test
    public void test1() throws ClassNotFoundException {
        // 获取Class对象
        Class<?> aClass = Class.forName("test.Student");
        // 返回一个包含 Constructor对象的数组,反映由该Class对象表示的类的所有 公共 构造函数
        Constructor<?>[] c1 = aClass.getConstructors();
        for (Constructor<?> constructor : c1) {
            System.out.println(constructor);
        }
    }
    

    image-20220302005543281

  • 方法2

     aClass.getDeclaredConstructors()
    // 返回由该 Class对象表示的类声明的所有构造函数的 Constructor对象的数组
    
    @Test
    public void test1() throws ClassNotFoundException {
        // 获取Class对象
        Class<?> aClass = Class.forName("test.Student");
        // aClass.getDeclaredConstructors()
        // 返回由该 Class对象表示的类声明的所有构造函数的 Constructor对象的数组
        Constructor<?>[] c1 = aClass.getDeclaredConstructors();
        for (Constructor<?> constructor : c1) {
            System.out.println(constructor);
        }
    }
    

    image-20220302005853926

  • 方法3

    aClass.getConstructor()
    // 返回一个 Constructor对象,该对象反映由该Class对象表示的类的指定 公共 构造函数
    // 参数:要获取的构造方法的参数的个数及数据类型对应的字节码文件对象
    constructor.newInstance();
    
    @Test
    public void test1() throws Exception {
        // 获取Class对象
        Class<?> aClass = Class.forName("test.Student");
        // aClass.getConstructor()
        // 返回一个 Constructor对象,该对象反映由该Class对象表示的类的指定 公共 构造函数
        // 参数:要获取的构造方法的参数的个数及数据类型对应的字节码文件对象
        Constructor<?> constructor = aClass.getConstructor();
        // 通过构造方法对象里面的对象来创建对象,这才叫反射
        // 使用此 Constructor对象表示的构造函数,使用指定的初始化参数来创建和初始化构造函数的声明类的新实例。
        Object obj = constructor.newInstance();//获取无参构造函数
        System.out.println(obj);
    }
    

    image-20220302010821642

  • 测试:访问 公共 的带参构造方法

    @Test
    public void test2() throws Exception {
        // 获取Class对象
        Class<?> aClass = Class.forName("test.Student");
        //  public Student(String name, int age, String address) {
        // 获取单个的构造方法
        Constructor<?> constructor = aClass.getConstructor(String.class,int.class,String.class);
        // 通过构造方法对象里面的对象来创建对象,这才叫反射
        // 使用此 Constructor对象表示的构造函数,使用指定的初始化参数来创建和初始化构造函数的声明类的新实例。
        Object obj = constructor.newInstance("rabbit",22,"上海");
        System.out.println(obj);
    }
    

    image-20220302011733081

  • 测试:访问 私有的带参的构造方法

    @Test
    public void test3() throws Exception {
        // 获取Class对象
        Class<?> aClass = Class.forName("test.Student");
        //  public Student(String name, int age, String address) {
        // 获取单个的构造方法
        Constructor<?> constructor = aClass.getDeclaredConstructor(String.class);
        // 我们不能通过私有的构造方法创建对象,但在反射中却可以,需要进行暴力反射
        // 取消访问检查
        constructor.setAccessible(true);
        Object obj = constructor.newInstance("rabbit");
        System.out.println(obj);
    }
    

    image-20220302162002837

4、反射获取成员变量并使用

  • 方式一:Field[] getFields();

    Field[] getFields();
    //返回一个包含Field对象的数组,Field对象反映由该Class对象表示的类或接口的所有可访问的 公共 的成员变量
    
    @Test
    public void test4() throws Exception {
        // 获取Class对象
        Class<?> aClass = Class.forName("test.Student");
        Field[] fields = aClass.getFields();
        for (Field field : fields) {
            System.out.println(field);
        }
    }
    

    image-20220302164350338

  • 方式二:aClass.getDeclaredFields();

    Field[] getDeclaredFields();
    //返回一个包含Field对象的数组,Field对象反映由该Class对象表示的类或接口的所有成员变量
    
    @Test
    public void test5() throws Exception {
        // 获取Class对象
        Class<?> aClass = Class.forName("test.Student");
        Field[] fields = aClass.getDeclaredFields();
        for (Field field : fields) {
            System.out.println(field);
        }
    }
    

    image-20220302164503344

  • 方法三:为成员变量赋值

    	Field getField(String name);
    // 返回一个 Field对象,该对象反映由该Class对象表示的类或接口的指定 公共 成员字段
    
    /**
     * Field getField(String name);
     * 返回一个 Field对象,该对象反映由该Class对象表示的类或接口的指定 公共 成员字段
     * 步骤:
     *      1. 首先获取Class对象;
     *      2. 按照Class对象按照指定的成员变量得到成员变量对象;
     *      3. 最后通过成员变量对象调用set(obj1,obj2)方法
     *      4. field.set(obj1,obj2):为obj1对象的成员变量field赋值为obj2
     */
    @Test
    public void test6() throws Exception {
        // 获取Class对象
        Class<?> aClass = Class.forName("test.Student");
        Field addressField = aClass.getDeclaredField("address");
        // 通过反射,为成员变量赋值:student.address = "上海"
        // 获取无参构造方法构造对象
        Constructor<?> constructor = aClass.getConstructor();
        Object obj = constructor.newInstance();
        // 给obj的成员变量addressField赋值为上海
        addressField.set(obj,"上海");
        System.out.println(obj);
    }
    

    image-20220302165912447

  • 练习

    /**
     * 练习:
     * Student student = new Student();
     * student.name = "rabbit"
     * student.age = 22
     * student.address = "上海"
     */
    
    @Test
    public void test7() throws Exception {
        // 获取Class对象
        Class<?> aClass = Class.forName("test.Student");
        // 先获取无参构造方法
        Constructor<?> constructor = aClass.getConstructor();
        Object obj = constructor.newInstance();
        Field nameField = aClass.getDeclaredField("name");
        Field ageField = aClass.getDeclaredField("age");
        Field addressField = aClass.getDeclaredField("address");
        // 私有的成员变量,使用前也要暴力反射
        nameField.setAccessible(true);
        ageField.setAccessible(true);
        nameField.set(obj,"rabbit");
        ageField.set(obj,22);
        addressField.set(obj,"上海");
        System.out.println(obj);
    }
    

    image-20220302170955668

5、反射获取成员方法并使用

  • 测试一:获取所有公共方法,包括超类

    Method[] getMethods();
    返回一个包含方法对象的数组,方法对象反映由该Class对象表示的类或接口的所有 公共方法
    包括由类或接口声明的对象以及从超类和超级接口继承的类
    
    @Test
    public void test8() throws Exception {
        // 获取Class对象
        Class<?> aClass = Class.forName("test.Student");
        Method[] methods = aClass.getMethods();
        for (Method method : methods) {
            System.out.println(method);
        }
    }
    

    image-20220302171858664

  • 测试二:获取所有方法,不包括超类

    /**
     * Method[] getDeclaredMethods();
     * 返回一个包含方法对象的数组,方法对象反映由该Class对象表示的类或接口的所有声明方法
     * 包括:public\protected\default(package)访问和私有方法,但不包括继承方法
     */
    
    @Test
    public void test9() throws Exception {
        // 获取Class对象
        Class<?> aClass = Class.forName("test.Student");
        Method[] methods = aClass.getDeclaredMethods();
        for (Method method : methods) {
            System.out.println(method);
        }
    }
    

    image-20220302172055605

  • 测试三:返回一个 方法对象,该对象反映由该 Class对象表示的类或接口的指定公共成员方法

    /**
     * Method getMethod(String name,Class<?>... parameterTypes );
     * 返回一个 方法对象,该对象反映由该 Class对象表示的类或接口的指定公共成员方法
     */
    
    @Test
    public void test10() throws Exception {
        // 获取Class对象
        Class<?> aClass = Class.forName("test.Student");
        Method method = aClass.getMethod("method1");
        // 获取无参构造方法创建对象调用方法
        Constructor<?> constructor = aClass.getConstructor();
        Object obj = constructor.newInstance();
        // Object invoke(Object obj, Object... args)
        // 在具有指定参数的指定对象上调用此方法的对象表示的基础方法
        // obj:调用方法的对象
        // args:方法需要的参数
        method.invoke(obj);
    
    }
    

    image-20220302173521343

image-20220302173606669

  • 练习4:通过反射实现如下操作

    /**
     * 练习:通过反射实现如下操作
     * Student student = new Student();
     * student.method1();
     * student.method2("rabbit");
     * String str = student.method("rabbit",22);
     * System.out.println(str);
     * str.function();
     */
    
    @Test
    public void test12() throws Exception {
        // 获取Class对象
        Class<?> aClass = Class.forName("test.Student");
    
        // 获取无参构造方法创建对象调用方法
        Constructor<?> constructor = aClass.getConstructor();
        Object obj = constructor.newInstance();
        // student.method1()
        Method method1 = aClass.getMethod("method1");
        method1.invoke(obj);
    
        Method method2 = aClass.getMethod("method2", String.class);
        method2.invoke(obj,"rabbit");
    
        Method method3 = aClass.getMethod("method3", String.class, int.class);
        Object o = method3.invoke(obj, "rabbit", 22);
        System.out.println(o);
    
        Method function = aClass.getDeclaredMethod("function");
        function.setAccessible(true);
        function.invoke(obj);
    
    }
    

    image-20220302175615693

  • 练习:通过反射越过泛型检查

    /**
     * 练习:越过泛型检查
     *  往 ArrayList<Integer> 中添加字符串对象
     */
    
    @Test
    public void test13() throws Exception {
        ArrayList<Integer> list = new ArrayList<>();
        Class<? extends ArrayList> aClass = list.getClass();
        Method method = aClass.getMethod("add", Object.class);
        method.invoke(list,"hello world");
        System.out.println(list);
    }
    

    image-20220302180228980

  • 4
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

rabbit_zli

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值