反射机制理解

98 阅读14分钟

反射机制理解

1. 反射的概述

  • Java给我们提供了一套API,使用这套API我们可以在运行时动态的获取指定对象所属的类,创建运行时类的对象,调用指定的结构(属性、方法)等。
  • API:
    • java.lang.Class:代表一个类
    • java.lang.reflect.Method:代表类的方法
    • java.lang.reflect.Field:代表类的成员变量
    • java.lang.reflect.Constructor:代表类的构造器
    • … …
  • 反射的优点和缺点
    • 优点:
      • 提高了Java程序的灵活性和扩展性,降低了耦合性,提高自适应能力

      • 允许程序创建和控制任何类的对象,无需提前硬编码目标类

    • 缺点:
      • 反射的性能较低
        • 反射机制主要应用在对灵活性和扩展性要求很高的系统框架上
      • 反射会模糊程序内部逻辑,可读性较差
  • 反射,平时开发中,我们使用并不多。主要是在框架的底层使用。

注意点:

面向对象中创建对象,调用指定结构(属性、方法)等功能,可以不使用反射,也可以使用反射。有什么区别?

  • 不使用反射,我们需要考虑封装性。比如:出了Person类之后,就不能调用Person类中私有的结构
  • 使用反射,我们可以调用运行时类中任意的构造器、属性、方法。包括了私有的属性、方法、构造器。

以前创建对象并调用方法的方式,与现在通过反射创建对象并调用方法的方式对比的话,哪种用的多?场景是什么?

  • 从我们作为程序员开发者的角度来讲,我们开发中主要是完成业务代码,对于相关的对象、方法的调用都是确定的。所以,我们使用非反射的方式多一些。
  • 因为反射体现了动态性(可以在运行时动态的获取对象所属的类,动态的调用相关的方法),所以我们在设计框架的时候,会大量的使用反射。意味着,如果大家需要学习框架源码,那么就需要学习反射。

框架 = 注解 + 反射 + 设计模式

单例模式的饿汉式和懒汉式中,私有化类的构造器了!此时通过反射,可以创建单例模式中类的多个对象

封装性:体现的是是否建议我们调用内部api的问题。比如,private声明的结构,意味着不建议调用。

反射:体现的是我们能否调用的问题。因为类的完整结构都加载到了内存中,所有我们就有能力进行调用。

2. Class:反射的源头

  • Class的理解

针对于编写好的.java源文件进行编译(使用javac.exe),会生成一个或多个.class字节码文件。接着,我们使用java.exe命令对指定的.class文件进行解释运行。这个解释运行的过程中,我们需要将.class字节码文件加载(使用类的加载器)到内存中(存放在方法区)。加载到内存中的.class文件对应的结构即为Class的一个实例

比如:加载到内存中的Person类或String类或User类,都作为Class的一个一个的实例

Class clazz1 = Person.class; //运行时类

Class clazz2 = String.class;

Class clazz3 = User.class;

Class clazz4 = Comparable.class;

说明:运行时类在内存中会缓存起来,在整个执行期间,只会加载一次

Class看做是反射的源头

  • 获取Class的实例的几种方式(前三种)

    • 类.class
    • 对象.getClass()
    • (使用较多)Class调用静态方法forName(String className)
    • (了解)使用ClassLoader的方法loadClass(String className)
   /*
    * 获取Class实例的几种方式(掌握前三种)
    * */
    @Test
    public void test1() throws ClassNotFoundException {
        //1.调用运行时类的静态属性:class
        Class clazz1 = User.class;
        System.out.println(clazz1);

        //2. 调用运行时类的对象的getClass()
        User u1 = new User();
        Class clazz2 = u1.getClass();

        //3. 调用Class的静态方法forName(String className)
        String className = "com.xxw._class.User"; //全类名
        Class clazz3 = Class.forName(className);

        System.out.println(clazz1 == clazz2);//true
        System.out.println(clazz1 == clazz3);//true

        //4. 使用类的加载器的方式 (了解)
        Class clazz4 = ClassLoader.getSystemClassLoader().loadClass("com.xxw._class.User");
        System.out.println(clazz1 == clazz4);//true
    }

    @Test
    public void test2(){
        Class c1 = Object.class;
        Class c2 = Comparable.class;
        Class c3 = String[].class;
        Class c4 = int[][].class;
        Class c5 = ElementType.class;
        Class c6 = Override.class;
        Class c7 = int.class;
        Class c8 = void.class;
        Class c9 = Class.class;

        int[] a = new int[10];
        int[] b = new int[100];
        Class c10 = a.getClass();
        Class c11 = b.getClass();
        // 只要元素类型与维度一样,就是同一个Class
        System.out.println(c10 == c11);
    }
  • Class 可以指向哪些结构。

简言之,所有Java类型!

(1)class:外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类

(2)interface:接口

(3)[]:数组

(4)enum:枚举

(5)annotation:注解@interface

(6)primitive type:基本数据类型

(7)void

3. 类的加载过程、类的加载器

  • 类的加载过程

过程1:类的装载(loading)

  • 将类的class文件读入内存,并为之创建一个java.lang.Class对象。此过程由类加载器完成

过程2:链接(linking)

  • 验证(Verify):确保加载的类信息符合JVM规范,例如:以cafebabe开头,没有安全方面的问题。
  • 准备(Prepare):正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配。
  • 解析(Resolve):虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。

过程3:初始化(initialization)

  • 执行类构造器()方法的过程。
  • 类构造器()方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。
  • 类的加载器

作用

  • 负责类的加载,并对应于一个Class的实例。

分类(分为两种):

BootstrapClassLoader:引导类加载器、启动类加载器

  • 使用C/C++语言编写的,不能通过Java代码获取其实例
  • 负责加载Java的核心库(JAVA_HOME/jre/lib/rt.jar或sun.boot.class.path路径下的内容)

继承于ClassLoader的类加载器

  • ExtensionClassLoader:扩展类加载器
    • 负责加载从java.ext.dirs系统属性所指定的目录中加载类库,或从JDK的安装目录的jre/lib/ext子目录下加载类库
  • SystemClassLoader/ApplicationClassLoader:系统类加载器、应用程序类加载器
    • 我们自定义的类,默认使用的类的加载器。
  • 用户自定义类的加载器
    • 实现应用的隔离(同一个类在一个应用程序中可以加载多份);数据的加密

使用类的加载器获取流,并读取配置文件信息:


/*
* 通过ClassLoader加载指定的配置文件
* */
@Test
public void test3() throws IOException {
    Properties pros = new Properties();

    //通过类的加载器读取的文件的默认的路径为:当前module下的src下
    InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("info1.properties");

    pros.load(is);

    String name = pros.getProperty("name");
    String pwd = pros.getProperty("password");
    System.out.println(name + ":" +pwd);
}

4. 反射的应用1:创建运行时类的对象(重点)

通过Class的实例调用newInstance()方法

Class clazz = Person.class;

//创建Person类的实例
Person per = (Person) clazz.newInstance();

System.out.println(per);

要想创建对象成功,需要满足:

条件1:要求运行时类中必须提供一个空参的构造器

条件2:要求提供的空参的构造器的权限要足够。

5. 反射的应用2:获取运行时类所有的结构

获取运行时类的内部结构1:所有属性、所有方法、所有构造器

    @Test
    public void test1() {

        Class clazz = Person.class;
        //getFields():获取到运行时类本身及其所有的父类中声明为public权限的属性
//        Field[] fields = clazz.getFields();
//        for (Field f : fields) {
//            System.out.println(f);
//        }

        //getDeclaredFields():获取当前运行时类中声明的所有属性
        Field[] declaredFields = clazz.getDeclaredFields();
        for(Field f : declaredFields){
            System.out.println(f);
        }
    }

    //权限修饰符  变量类型  变量名
    @Test
    public void test2() {
        Class clazz = Person.class;
        Field[] declaredFields = clazz.getDeclaredFields();
        for (Field f : declaredFields) {
            //1.权限修饰符
            /*
             * 0x是十六进制
             * PUBLIC           = 0x00000001;  1    1
             * PRIVATE          = 0x00000002;  2   10
             * PROTECTED        = 0x00000004;  4   100
             * STATIC           = 0x00000008;  8   1000
             * FINAL            = 0x00000010;  16  10000
             * ...
             */
            int modifier = f.getModifiers();
            System.out.print(modifier + ":" + Modifier.toString(modifier) + "\t");

            //2.数据类型
            Class type = f.getType();
            System.out.print(type.getName() + "\t");
//
//            //3.变量名
            String fName = f.getName();
            System.out.print(fName);

            System.out.println();
        }
    }
 @Test
    public void test1() {

        Class clazz = Person.class;
        // getMethods():获取到运行时类本身及其所有的父类中声明为public权限的方法
//        Method[] methods = clazz.getMethods();
//        for (Method m : methods) {
//            System.out.println(m);
//        }

        // getDeclaredMethods():获取当前运行时类中声明的所有方法
        Method[] declaredMethods = clazz.getDeclaredMethods();
        for (Method m : declaredMethods) {
            System.out.println(m);
        }

    }

    // 注解信息
    // 权限修饰符 返回值类型 方法名(形参类型1 参数1,形参类型2 参数2,...) throws 异常类型1,...{}
    @Test
    public void test2() {
        Class clazz = Person.class;
        Method[] declaredMethods = clazz.getDeclaredMethods();
        for (Method m : declaredMethods) {
            // 1.获取方法声明的注解
            Annotation[] annos = m.getAnnotations();
            for (Annotation a : annos) {
                System.out.println(a);
            }

            // 2.权限修饰符
            System.out.print(Modifier.toString(m.getModifiers()) + "\t");

            // 3.返回值类型
            System.out.print(m.getReturnType().getName() + "\t");
//
//            // 4.方法名
            System.out.print(m.getName());
            System.out.print("(");
//            // 5.形参列表
            Class[] parameterTypes = m.getParameterTypes();
            if (!(parameterTypes == null && parameterTypes.length == 0)) {
                for (int i = 0; i < parameterTypes.length; i++) {

                    if (i == parameterTypes.length - 1) {
                        System.out.print(parameterTypes[i].getName() + " args_" + i);
                        break;
                    }
                    System.out.print(parameterTypes[i].getName() + " args_" + i + ",");
                }
            }
//
            System.out.print(")");
//
//            // 6.抛出的异常
            Class[] exceptionTypes = m.getExceptionTypes();
            if (exceptionTypes.length > 0) {
                System.out.print("throws ");
                for (int i = 0; i < exceptionTypes.length; i++) {
                    if (i == exceptionTypes.length - 1) {
                        System.out.print(exceptionTypes[i].getName());
                        break;
                    }
                    System.out.print(exceptionTypes[i].getName() + ",");
                }
            }
            System.out.println();
        }
    }

获取运行时类的内部结构2:父类、接口们、包、带泛型的父类、父类的泛型等

//获取运行时类的内部结构2:父类、接口们、包、带泛型的父类、父类的泛型等
//1. 获取运行时类的父类
@Test
public void test1() throws ClassNotFoundException {
    Class clazz = Class.forName("com.atguigu03.reflectapply.data.Person");
    Class superClass = clazz.getSuperclass();
    System.out.println(superClass);
}
//2. 获取运行时类实现的接口
@Test
public void test2() throws ClassNotFoundException {
    Class clazz = Class.forName("com.atguigu03.reflectapply.data.Person");

    Class[] interfaces = clazz.getInterfaces();
    for(Class c : interfaces){
        System.out.println(c);
    }
}
//3. 获取运行时类所在的包
@Test
public void test3() throws ClassNotFoundException {
    Class clazz = Class.forName("com.atguigu03.reflectapply.data.Person");

    Package pack = clazz.getPackage();
    System.out.println(pack);
}
//4. 获取运行时类的带泛型的父类
@Test
public void test4() throws ClassNotFoundException {
    Class clazz = Class.forName("com.atguigu03.reflectapply.data.Person");
    Type superclass = clazz.getGenericSuperclass();
    System.out.println(superclass);
}

//5. 获取运行时类的父类的泛型 (难)
/*
* 平时写的代码:
* 类型1:业务逻辑代码 (多关注)
* 类型2:算法逻辑代码 (多积累)
*
* */
@Test
public void test5() throws ClassNotFoundException {
    Class clazz = Class.forName("com.xxw.reflectapply.data.Person");
    //获取带泛型的父类(Type是一个接口,Class实现了此接口
    Type superclass = clazz.getGenericSuperclass();
    //如果父类是带泛型的,则可以强转为ParameterizedType
    ParameterizedType paramType = (ParameterizedType) superclass;
    //调用getActualTypeArguments()获取泛型的参数,结果是一个数组,因为可能有多个泛型参数。
    Type[] arguments = paramType.getActualTypeArguments();
    //获取泛型参数的名称
    System.out.println(((Class)arguments[0]).getName());
}

6. 反射的应用3:调用指定的结构(重点)

调用指定的属性(步骤)

  • 步骤1.通过Class实例调用getDeclaredField(String fieldName),获取运行时类指定名的属性
  • 步骤2. setAccessible(true):确保此属性是可以访问的
  • 步骤3. 通过Filed类的实例调用get(Object obj) (获取的操作)或 set(Object obj,Object value) (设置的操作)进行操作。
//********************如下是调用指定的属性************************
    /*
    * 调用指定的属性
    * */
    //public int age = 1;
    @Test
    public void test1() throws Exception {
         Class clazz = Person.class;

         //
        Person per = (Person) clazz.newInstance();

        //1. 获取运行时类指定名的属性
        Field ageField = clazz.getField("age");

        //2. 获取或设置此属性的值
        ageField.set(per,2);
        System.out.println(ageField.get(per));
    }

    //private String name;
    @Test
    public void test2() throws Exception {
        Class clazz = Person.class;

        //
        Person per = (Person) clazz.newInstance();

        //1.通过Class实例调用getDeclaredField(String fieldName),获取运行时类指定名的属性
        Field nameField = clazz.getDeclaredField("name");

        //2. setAccessible(true):确保此属性是可以访问的
        nameField.setAccessible(true);

        //3. 通过Filed类的实例调用get(Object obj) (获取的操作)
        // 或 set(Object obj,Object value) (设置的操作)进行操作。
        nameField.set(per,"Tom");
        System.out.println(nameField.get(per));
    }

    //private static String info;
    @Test
    public void test3() throws Exception {
        Class clazz = Person.class;

        //1.通过Class实例调用getDeclaredField(String fieldName),获取运行时类指定名的属性
        Field infoField = clazz.getDeclaredField("info");

        //2. setAccessible(true):确保此属性是可以访问的
        infoField.setAccessible(true);

        //3. 通过Filed类的实例调用get(Object obj) (获取的操作)
        // 或 set(Object obj,Object value) (设置的操作)进行操作。
//        infoField.set(Person.class,"我是一个人");
//        System.out.println(infoField.get(Person.class));
        //或 (仅限于类变量可以如下的方式调用)
        infoField.set(null,"我是一个人");
        System.out.println(infoField.get(null));
    }

调用指定的方法(步骤)

  • 步骤1.通过Class的实例调用getDeclaredMethod(String methodName,Class ... args),获取指定的方法
  • 步骤2. setAccessible(true):确保此方法是可访问的
  • 步骤3.通过Method实例调用invoke(Object obj,Object ... objs),即为对Method对应的方法的调用。
    • invoke()的返回值即为Method对应的方法的返回值
    • 特别的:如果Method对应的方法的返回值类型为void,则invoke()返回值为null
//********************如下是调用指定的方法************************
/*
 * 调用指定的方法
 * */
//private String showNation(String nation,int age)
@Test
public void test4() throws Exception {
    Class clazz = Person.class;

    Person per = (Person) clazz.newInstance();

    //1.通过Class的实例调用getDeclaredMethod(String methodName,Class ... args),获取指定的方法
    Method showNationMethod = clazz.getDeclaredMethod("showNation",String.class,int.class);

    //2. setAccessible(true):确保此方法是可访问的
    showNationMethod.setAccessible(true);

    //3.通过Method实例调用invoke(Object obj,Object ... objs),即为对Method对应的方法的调用。
    //invoke()的返回值即为Method对应的方法的返回值
    //特别的:如果Method对应的方法的返回值类型为void,则invoke()返回值为null
    Object returnValue = showNationMethod.invoke(per,"CHN",10);
    System.out.println(returnValue);
}

//public static void showInfo()
@Test
public void test5() throws Exception {
    Class clazz = Person.class;

    //1.通过Class的实例调用getDeclaredMethod(String methodName,Class ... args),获取指定的方法
    Method showInfoMethod = clazz.getDeclaredMethod("showInfo");

    //2. setAccessible(true):确保此方法是可访问的
    showInfoMethod.setAccessible(true);

    //3.通过Method实例调用invoke(Object obj,Object ... objs),即为对Method对应的方法的调用。
    //invoke()的返回值即为Method对应的方法的返回值
    //特别的:如果Method对应的方法的返回值类型为void,则invoke()返回值为null
    Object returnValue = showInfoMethod.invoke(null);
    System.out.println(returnValue);
}

调用指定的构造器(步骤)

  • 步骤1.通过Class的实例调用getDeclaredConstructor(Class ... args),获取指定参数类型的构造器
  • 步骤2.setAccessible(true):确保此构造器是可以访问的
  • 步骤3.通过Constructor实例调用newInstance(Object ... objs),返回一个运行时类的实例。
//********************如下是调用指定的构造器************************
/*
 * 调用指定的构造器
 * */
//private Person(String name, int age)
@Test
public void test6() throws Exception {

    Class clazz = Person.class;

    //1.通过Class的实例调用getDeclaredConstructor(Class ... args),获取指定参数类型的构造器
    Constructor constructor = clazz.getDeclaredConstructor(String.class, int.class);

    //2.setAccessible(true):确保此构造器是可以访问的
    constructor.setAccessible(true);

    //3.通过Constructor实例调用newInstance(Object ... objs),返回一个运行时类的实例。
    Person per = (Person) constructor.newInstance("Tom", 12);

    System.out.println(per);

}
//使用Constructor替换原有的使用Class调用newInstance()的方式创建对象
@Test
public void test7() throws Exception {
    Class clazz = Person.class;

    //1.通过Class的实例调用getDeclaredConstructor(Class ... args),获取指定参数类型的构造器
    Constructor constructor = clazz.getDeclaredConstructor();

    //2.setAccessible(true):确保此构造器是可以访问的
    constructor.setAccessible(true);

    //3.通过Constructor实例调用newInstance(Object ... objs),返回一个运行时类的实例。
    Person per = (Person) constructor.newInstance();

    System.out.println(per);
}

7. 反射的应用4:注解的使用

框架 = 反射 + 注解 + 设计模式

以@SuppressWarnings为参照,进行定义

@Target({TYPE, FIELD, METHOD,CONSTRUCTOR})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
    String value() default "hello";
}
@MyAnnotation()
public Person(){}

8. 体会:反射的动态性

public class Person {
    //属性
    private String name;
    public int age;

    //构造器
    public Person(){
        System.out.println("Person()...");
    }

    public Person(int age){
        this.age = age;
    }

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

    //方法
    public void show(){
        System.out.println("你好,我是一个Person");
    }

    private String showNation(String nation){
        return "我的国籍是:" + nation;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
public class ReflectTest {

    //体会:静态性
    public Person getInstance(){
        return new Person();
    }

    //体会:反射的动态性
    //举例1:
    public <T> T getInstance(String className) throws Exception {

        Class clazz = Class.forName(className);

        Constructor con = clazz.getDeclaredConstructor();
        con.setAccessible(true);

        return (T) con.newInstance();

    }

    @Test
    public void test1() throws Exception {
        Person p1 = getInstance();
        System.out.println(p1);

        String className = "com.xxw.other.dynamic.Person";
        Person per1 = getInstance(className);
        System.out.println(per1);

        String className1 = "java.util.Date";
        Date date1 = getInstance(className1);
        System.out.println(date1);
    }

    //体会:反射的动态性
    //举例2:
    public Object invoke(String className,String methodName) throws Exception {
        //1. 创建全类名对应的运行时类的对象
        Class clazz = Class.forName(className);

        Constructor con = clazz.getDeclaredConstructor();
        con.setAccessible(true);

        Object obj = con.newInstance();

        //2. 获取运行时类中指定的方法,并调用
        Method method = clazz.getDeclaredMethod(methodName);
        method.setAccessible(true);
        return method.invoke(obj);
    }

    @Test
    public void test2() throws Exception {
        String className = "com.xxw.other.dynamic.Person";
        String methodName = "show";

        Object returnValue = invoke(className,methodName);
        System.out.println(returnValue);
    }

}

举个栗子

案例:榨汁机榨水果汁,水果分别有苹果(Apple)、香蕉(Banana)、桔子(Orange)等。 1、声明(Fruit)水果接口,包含榨汁抽象方法:void squeeze(); /skwiːz/

2、声明榨汁机(Juicer),包含运行方法:public void run(Fruit f),方法体中,调用f的榨汁方法squeeze()

/**
 * @author Created by xxw on 2023-09-07 16:38
 * @Description 水果接口
 */
public interface Fruit {
    //榨汁儿的方法
    void squeeze();
}

/**
 * @author Created by xxw on 2023-09-07 16:37
 * @Description 榨汁机
 */
public class Juicer {
    public void run(Fruit f){
        f.squeeze();
    }
}


3、声明各种水果类,实现水果接口,并重写squeeze();

/**
 * @author Created by xxw on 2023-09-07 16:39
 * @Description 苹果
 */
public class Apple implements Fruit{
    @Override
    public void squeeze() {
        System.out.println("榨一杯苹果汁儿");
    }
}

/**
 * @author Created by xxw on 2023-09-07 16:39
 * @Description 香蕉
 */
public class Banana implements Fruit{
    @Override
    public void squeeze() {
        System.out.println("榨一杯香蕉汁儿");
    }
}

/**
 * @author Created by xxw on 2023-09-07 16:40
 * @Description 橘子
 */
public class Orange implements Fruit{
    @Override
    public void squeeze() {
        System.out.println("榨一杯桔子汁儿");
    }
}

4、在src下,建立配置文件:config.properties,并在配置文件中配上fruitName=xxx(其中xx为某种水果的全类名)

fruitName=com.atguigu04.other.exer.Banana

5、在FruitTest测试类中, (1)读取配置文件,获取水果类名,并用反射创建水果对象

//1. 读取配置文件中的信息,获取全类名
Properties pros = new Properties();

File file = new File("src/config.properties");
FileInputStream fis = new FileInputStream(file);

pros.load(fis);

String fruitName = pros.getProperty("fruitName");

//2. 通过反射,创建指定全类名对应的类的实例
Class clazz = Class.forName(fruitName);
Constructor con = clazz.getDeclaredConstructor();
con.setAccessible(true);

Fruit fruit = (Fruit) con.newInstance();

(2)创建榨汁机对象,并调用run()方法

//3. 通过榨汁机的对象调用run()
Juicer juicer = new Juicer();
juicer.run(fruit);

image-20230907164433169.png