反射【零基础】

什么是反射?
在这里插入图片描述

一、学前先思

在学习反射之前,我们先了解类加载和类加载器,可以让我们更好的去理解反射机制。

1.类加载

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


类的加载

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

类的连接

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

类的初始化

  • 假如类还未被加载和连接,则程序先加载并连接该类
  • 假如该类的直接父类还未被初始化,则先初始化其直接父类
  • 假如类中有初始化语句,则系统会一次执行这些初始化语句
    注意:在执行第2个步骤时,系统对直接父类的初始化步骤也遵循初始化步骤1-3

类的初始化时机:

  • 创建类的实例
  • 调用类的类方法
  • 访问类或者接口的类变量,或者为该类变量赋值
  • 使用反射机制来强制创建某个类或接口对应的java.lang.Class对象
  • 初始化某个子类对象
  • 直接使用java.exe命令来运行某个主类

2.类加载器

类加载器的作用

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

虽然我们不用过分关心类加载机制,但是了解这个机制后我们就能更好的理解程序的运行,以下是三种类加载机制

JVM的类加载机制

  • 全盘负责 :就是当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将有该类加载器负责载入,除非显示使用另外一个类加载器载入
  • 父类委托:当一个类加载器负责加载某个Class时,先让父类加载器试图加载该Class,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类
  • 缓存机制:它保证所有加载过该类的Class都会被缓存,当程序需要使用某个Class对象时,类加载器先从缓存区中搜索该Class,只有当缓存区中不存在该Class对象时,系统才会读取该类的二进制数据,并将其转换成Class对象,存储到缓存区

ClassLoader: 负责加载类的对象
在这里插入图片描述

ClassLoader中的两个方法

类型 方法 描述
static ClassLoader getSystemClassLoader​() 返回用于委派的系统类加载器。
ClassLoader getParent​() 返回父类加载器进行委派。

下面通过代码来实现上述方法

public class ClassLoaderDemo {
    
    
    public static void main(String[] args) {
    
    
        // static ClassLoader getSystemClassLoader()返回用于委派的系统类加载器。
        ClassLoader c =  ClassLoader.getSystemClassLoader();
        System.out.println(c);

        //getParent()获得父类加载器进行委派
        ClassLoader c2 = c.getParent();
        System.out.println(c2);
    }
}

打印结果

1、通过getSystemClassLoader获得的系统类加载器
在这里插入图片描述

2、系统类加载器的父类(平台类加载器)

在这里插入图片描述


二、反射

2.1 反射概述

![在这里插入图片描述](https://img-blog.csdnimg.cn/fef5ab524da14319b0673b4e934bc775.png

如上图,有两个类,当我们要用着两个类时,需要通过类加载器加载对应的.class文件。在每一个.class文件中,都包含了有成员变量,构造方法、成员方法等。所有的class文件都会包含这些,因此我们可以用一个类去描述这些信息,这个类就是Class类,这个类是所有.class所对应的类型,所有类的影像。我们可以通过该类去使用这些成员变量,构造方法等。这个过程就是类的反射

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

图片来自张维鹏
图片来自:张维鹏


2.2 反射获取Class类对象

我们想要通过反射去获取一个类,首先要获取该类的字节码文件对象,也就是类型为Class类型的对象

三种方式获取Class类型的对象

  • 使用类的class属性来获取该类对象的Class对象。举例:Student.class将会返回Student类对应的Class对象
  • 调用对象的getClass()方法,返回该对象所属类对应的Class对象,该方法是Object类中的方法,所有的java对象都可以调用该方法
  • 使用Class类中的静态方法forName(String className) ,该方法需要传入字符串参数,该字符串参数的值是某个类的全路径,也就是完整包名路径

代码测试

Student类

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;
    }

    //成员方法:一个私有,四个公共
    private void function(){
    
    
        System.out.println("function");
    }
    
    public void method1(){
    
    
        System.out.println("function");
    }
    
    public void method2(String s){
    
    
        System.out.println("method2"+s);
    }
    public String method3(String s, int i){
    
    
        return s+","+i;
    }

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

测试类

public class ReflectDemo {
    
    
    public static void main(String[] args) throws ClassNotFoundException {
    
    
        //1、用类的class属性来获取该类对象的Class对象。
        Class<Student> c1 = Student.class;
        System.out.println(c1);
        System.out.println("---------");

        //2、调用对象的getClass()方法
        Student s = new Student();
        Class<? extends Student> c2 = s.getClass();
        System.out.println("c2与c1是否相对:"+(c1==c2));
        System.out.println("---------");

        //3、使用Class类中的静态方法forName(String className)
        Class<?> c3 = Class.forName("demo1.Student");
        System.out.println("c3与c1是否相对:"+(c1==c3));
    }
}

输出结果


2.3 反射获取构造方法并使用

通过来加载获取构造器对象数组

    public static void main(String[] args) throws ClassNotFoundException {
    
    
        //1、获取Class对象
        Class<?> c = Class.forName("demo1.Student");

        //Constructor<?>[] getConstructors() 返回一个包含 Constructor对象的数组, Constructor对象反映了由该 Class对象表示的类的所有公共构造函数。
        Constructor<?>[] cons = c.getConstructors();
        for (Constructor<?> con : cons) {
    
    
            System.out.println(con);
        }
    }

返回结果:
在这里插入图片描述
我们可以看到,Student里面有四个构造方法,但是这里只获取了两个,那是因为这两个两个公共的,在该方法,只能拿到公共的。

下面的方法可以获取全部的构造器对象

        //getDeclaredConstructors()返回反映由该 Class对象表示的类声明的所有构造函数的 Constructor对象的数组。
        Constructor<?>[] cons = c.getDeclaredConstructors();

返回结果
在这里插入图片描述

2.3.1用反射获取构造器并创建对象

下面的方法可以获取单个公共的构造器对象,并利用反射创建一个对象

        /**
         *获取一个公共构造器,并创建一个对象
         */
         Class<?> c = Class.forName("demo1.Student");
        // getConstructor(Class<?>... parameterTypes)返回一个 Constructor对象,该对象反映由该 Class对象表示的类的指定公共构造函数。 
        //参数:你要获取的构造方法的参数个数和数据类型对应的字节码文件对象
        Constructor<?> con = c.getConstructor();
        //Constructor提供了一个类的单个构造函数的信息和访问权限。
        //T newInstance(Object... initargs)使用由此 Constructor对象表示的构造函数,使用指定的初始化参数来创建和初始化构造函数的声明类的新实例。
        Object obj = con.newInstance();
        System.out.println(obj);

测试结果:
在这里插入图片描述


2.3.2 用反射获取构造器并创建对象并赋值

实操一、

1、通过反射实现如下操作
Sudent s = new Student("蔡徐坤",30,"NBA")
System.out.println(s);

Student类还是上面的那个

测试类

    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
    
    
        /**
         * 1、通过反射实现如下操作
         * Sudent s = new Student("蔡徐坤",30,"NBA")
         * System.out.println(s);
         */
        //获取Class对象
        Class<?> c = Class.forName("demo1.Student");
        //获取带三个参数的构造器方法
        //public Student(String name, int age, String address)
        //Constructor<T> 提供了一个类的单个构造函数的信息和访问权限。
        Constructor<?> con = c.getConstructor(String.class, int.class, String.class);
        //基本数据类型也可以通过.class得到对应的Class类型
        //T newInstance(Object... initargs)使用由此 Constructor对象表示的构造函数,使用指定的初始化参数来创建和初始化构造函数的声明类的新实例。
        Object obj = con.newInstance("蔡徐坤", 30, "NBA");
        System.out.println(obj);
    }
}

测试结果:
在这里插入图片描述
实操二、

 * 通过反射实现如下操作
 *       Student s = new Student("蔡徐坤");
 *        System.out.println(s);
 */

测试类:

public class ReflectDemo3 {
    
    
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
    
    
        //获取Class对象
        Class<?> s = Class.forName("demo1.Student");
        //获取带一个参数的构造器方法,由于该构造是私有,因此需要使用getDeclaredConstructor(Class<?>... parameterTypes)
        //getDeclaredConstructor(Class<?>... parameterTypes) 返回一个 Constructor对象,该对象反映由此 Class对象表示的类或接口的指定构造函数。
        //参数:你要获取的构造方法的参数个数和数据类型对应的字节码文件对象
        Constructor<?> scon = s.getDeclaredConstructor(String.class);
        //T newInstance(Object... initargs)使用由此 Constructor对象表示的构造函数,使用指定的初始化参数来创建和初始化构造函数的声明类的新实例。
        Object obj = scon.newInstance("蔡徐坤");
        System.out.println(obj);
    }
}

测试结果

在这里插入图片描述

从上面我们可以看出,异常被抛出了,提示是一个非法的访问,是因为该构造方法是私有的。在反射中怎么实现呢?

答案就是通过暴力反射

暴力反射

void setAccessible​(boolean flag)

  • 将此反射对象的 accessible标志设置为指示的布尔值。
  • 将此反射对象的accessible标志设置为指示的布尔值。 值为true表示反射对象应该在使用Java语言访问控制时抑制检查。
  • 值为false表示反射对象应该在使用Java语言访问控制时执行检查,并在类描述中指出变体。

简单来说使用该方法并将值设置为true,就可以通过私有构造方法来创建对象

测试类:

    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
    
    
        //获取Class对象
        Class<?> s = Class.forName("demo1.Student");
        //获取带一个参数的构造器方法,由于该构造是私有,因此需要使用getDeclaredConstructor(Class<?>... parameterTypes)
        //getDeclaredConstructor(Class<?>... parameterTypes) 返回一个 Constructor对象,该对象反映由此 Class对象表示的类或接口的指定构造函数。
        //参数:你要获取的构造方法的参数个数和数据类型对应的字节码文件对象
        Constructor<?> scon = s.getDeclaredConstructor(String.class);

        //暴力反射
        //void setAccessible(boolean flag)将此反射对象的 accessible标志设置为指示的布尔值。
        scon.setAccessible(true);

        //T newInstance(Object... initargs)使用由此 Constructor对象表示的构造函数,使用指定的初始化参数来创建和初始化构造函数的声明类的新实例。
        Object obj = scon.newInstance("蔡徐坤");
        System.out.println(obj);
    }
}

测试结果:
在这里插入图片描述


小结

在这里插入图片描述

2.4 反射获取成员变量并赋值

1、获取公共的所有变量

    public static void main(String[] args) throws ClassNotFoundException {
    
    
        //获取Class对象
        Class<?> c = Class.forName("demo1.Student");
         //获取公共字段(变量)
        /**Field[] getFields() 返回一个包含 Field对象的数组, Field对象反映由该 Class对象表示的类或接口的所有可访问的公共字段。
        Field getField(String name) 返回一个 Field对象,该对象反映由该 Class对象表示的类或接口的指定公共成员字段。
         */
        Field[] fields = c.getFields();
        for (Field field : fields) {
    
    
            System.out.println(field);
        }
    }
}

测试结果(一个公共的成员变量):
在这里插入图片描述


2、获取所有的所有变量

测试类:

    public static void main(String[] args) throws ClassNotFoundException {
    
    
        //获取Class对象
        Class<?> c = Class.forName("demo1.Student");
        //获取私有和公共字段(变量)
        /**
         * Field getDeclaredField(String name) 返回一个 Field对象,该对象反映由该 Class对象表示的类或接口的指定声明字段。
         *Field[] getDeclaredFields() 返回一个 Field对象的数组,反映了由该 Class对象表示的类或接口声明的所有字段。 
        */
        Field[] fields = c.getDeclaredFields();
        for (Field field : fields) {
    
    
            System.out.println(field);
        }
    }
}

测试结果(所有的成员变量):
在这里插入图片描述


2、获取私有变量并赋值

 public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
    
    
        //获取Class对象
        Class<?> c = Class.forName("demo1.Student");
        //获取私有和公共字段(变量)
        /**
         * Field getDeclaredField(String name) 返回一个 Field对象,该对象反映由该 Class对象表示的类或接口的指定声明字段。
         *Field[] getDeclaredFields() 返回一个 Field对象的数组,反映了由该 Class对象表示的类或接口声明的所有字段。
        */
        Field name = c.getDeclaredField("name");

        //获取无参构造方法创建对象
        Constructor<?> cons = c.getConstructor();
        Object student = cons.newInstance();
          
          // Field提供有关类或接口的单个字段的信息和动态访问。 反射的字段可以是类(静态)字段或实例字段。
        //void setAccessible(boolean flag) 将此反射对象的 accessible标志设置为指示的布尔值。
        //void set(Object obj, Object value) 将指定的对象参数中由此 Field对象表示的字段设置为指定的新值。
        
        name.setAccessible(true);
        name.set(student,"蔡徐坤");
        System.out.println(student);
    }
}

测试结果:

在这里插入图片描述

小结

在这里插入图片描述


2.5 反射获取成员方法

反射获取成员方法并传参调用

测试类:

    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
    
    
        //获取Class对象
        Class<?> c = Class.forName("demo1.Student");

        //方法 getMethod(String name, Class<?>... parameterTypes) 返回一个 方法对象,该对象反映由该 Class对象表示的类或接口的指定公共成员方法。
        //方法[] getMethods() 返回一个包含 方法对象的数组, 方法对象反映由该 Class对象表示的类或接口的所有公共方法,包括由类或接口声明的对象以及从超类和超级接口继承的类。
        //方法 getDeclaredMethod(String name, Class<?>... parameterTypes) 返回一个 方法对象,它反映此表示的类或接口的指定声明的方法 Class对象。
        //方法[] agetDeclaredMethods() 返回一个包含 方法对象的数组, 方法对象反映由 Class对象表示的类或接口的所有声明方法,包括public,protected,default(package)访问和私有方法,但不包括继承方法。

        //void setAccessible(boolean flag) 将此反射对象的 accessible标志设置为指示的布尔值。

        //获取一个私有的无参方法
        Method method = c.getDeclaredMethod("function");
        method.setAccessible(true);

        //获取一个返回值类型为Sting 的有参方法
        Method method3 = c.getDeclaredMethod("method3", String.class, int.class);


        //通过无参构造来创建对象
        Constructor<?> con = c.getConstructor();
        //实例化
        Object student = con.newInstance();
        //Object invoke(Object obj, Object... args) 在具有指定参数的指定对象上调用此 方法对象表示的基础方法。
        //1调用无参方法
        method.invoke(student);
        //2、调用有参方法
        method3.invoke(student,"蔡徐坤",30);
        System.out.println("method3:"+ method3.invoke(student,"蔡徐坤",30)+"(我是带参公共滴!)");
    }
}

测试结果:

在这里插入图片描述

在上述中有一个重要的方法:Object invoke(Object obj, Object… args) ,它就是调用类中的方法,并把方法参数化

小结

在这里插入图片描述


三、反射越过泛型检查

我们都知道在集合类型中,一旦明确类泛型的类型,只能在这个集合中用声明的类型。但是我们可以通过反射来越过泛型的检查。

下面我们通过一个实例来体会

实例:在ArrayList集合中,添加一个字符串数据

    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
    
    
        //创建集合
        ArrayList<Integer> array = new  ArrayList<Integer>();
//        array.add(12);
//        array.add(10);
        Class<? extends ArrayList> c = array.getClass();
        Method m = c.getMethod("add", Object.class);
        m.invoke(array,"123");
        m.invoke(array,"java");
        System.out.println(array);
        
    }
}

总结

关于反射机制

(1)Java反射机制的核心是在程序运行时动态加载类并获取类的详细信息,从而操作类或对象的属性和方法。本质是JVM得到class对象之后,再通过class对象进行反编译,从而获取对象的各种信息。

(2)Java属于先编译再运行的语言,程序中对象的类型在编译期就确定下来了,而当程序在运行时可能需要动态加载某些类,这些类因为之前用不到,所以没有被加载到JVM。通过反射,可以在运行时动态地创建对象并调用其属性,不需要提前在编译期知道运行的对象是谁。

反射使用的步骤:
1️⃣通过三种方法获取到想要反射l类的.Class对象

  • 使用类的class属性来获取该类对象的Class对象。举例:Student.class将会返回Student类对应的Class对象
  • 调用对象的getClass()方法,返回该对象所属类对应的Class对象,该方法是Object类中的方法,所有的java对象都可以调用该方法
  • 使用Class类中的静态方法forName(String className) ,该方法需要传入字符串参数,该字符串参数的值是某个类的全路径,也就是完整包名路径

2️⃣使用Class对应的方法

基本方法
类型 方法 描述
ClassLoader getClassLoader​() 返回类的类加载器。
static Class<?> forName​(String className) 返回与给定字符串名称的类或接口相关联的 Class对象。
获取构造器方法
类型 方法 描述
Constructor getConstructor​(Class<?>... parameterTypes) 返回一个 Constructor对象,该对象反映由该 Class对象表示的类的指定公共构造函数。
Constructor<?>[] getConstructors​() 返回一个包含 Constructor对象的数组, Constructor对象反映了由该 Class对象表示的类的所有公共构造函数。
Constructor getDeclaredConstructor​(Class<?>... parameterTypes) 返回一个 Constructor对象,该对象反映由此 Class对象表示的类或接口的指定构造函数。
Constructor<?>[] getDeclaredConstructors​() 返回反映由该 Class对象表示的类声明的所有构造函数的 Constructor对象的数组
获取字段(变量)的方法
类型 方法 描述
Field getField​(String name) 返回一个 Field对象,该对象反映由该 Class对象表示的类或接口的指定公共成员字段。
Field[] getFields​() 返回一个包含 Field对象的数组, Field对象反映由该 Class对象表示的类或接口的所有可访问的公共字段。
Field getDeclaredField​(String name) 返回一个 Field对象,该对象反映由该 Class对象表示的类或接口的指定声明字段。
Field[] getDeclaredFields​() 返回一个 Field对象的数组,反映了由该 Class对象表示的类或接口声明的所有字段。
获取类方法
类型 方法 描述
Method getMethod​(String name, Class<?>... parameterTypes) 返回一个 方法对象,该对象反映由该 Class对象表示的类或接口的指定公共成员方法。
Method[] getMethods​() 返回一个包含 方法对象的数组, 方法对象反映由该 Class对象表示的类或接口的所有公共方法,包括由类或接口声明的对象以及从超类和超级接口继承的类。
Method getDeclaredMethod​(String name, Class<?>... parameterTypes) 返回一个 方法对象,它反映此表示的类或接口的指定声明的方法 Class对象。
Method[] getDeclaredMethods​() 返回一个包含 方法对象的数组, 方法对象反映由 Class对象表示的类或接口的所有声明方法,包括public,protected,default(package)访问和私有方法,但不包括继承方法。
声明类的新实例
类型 方法 描述
T newInstance​(Object... initargs) 使用由此 Constructor对象表示的构造函数,使用指定的初始化参数来创建和初始化构造函数的声明类的新实例。
方法调用
类型 方法 描述
Object invoke​(Object obj, Object... args) 在具有指定参数的指定对象上调用此 方法对象表示的基础方法。
越过权限检查
类型 方法 描述
void setAccessible​(boolean flag) 将此反射对象的 accessible标志设置为指示的布尔值。

反射的应用场景

  • 使用JDBC时,如果要创建数据库的连接则需要先通过反射机制加载数据库的驱动程序;
  • 多数框架都支持注解/XML配置,从配置中解析出来的类是字符串,需要利用反射机制实例化;
  • 面向切面编程(AOP)的实现方案,是在程序运行时创建目标对象的代理类,这必须由反射机制来实现。

注明:部分内容引用自:
张维鹏

猜你喜欢

转载自blog.csdn.net/qq_49385719/article/details/125123938