Java基础—反射

一、概念

1、反射是什么

  • 作用:反射机制允许程序在执行期间借助于Reflection API取得任何类的内部信息,包括成员变量、构造器、成员方法等,并能操作对象的属性及方法。
  • 功能:动态获取信息以及动态调用对象方法
  • 用途:在设计模式或者框架底层都会用到
  • 个人理解:之前都是通过new关键字创建对象,进而使用方法。现在是直接通过大Class实例来完成对象创建、属性复制、方法调用这一系列过程

2、绘制 内存模型图 来解释 反射机制

在这里插入图片描述

3、反射机制 相关的类

  • java.lang.Class:Class代表类,由启动类加载器进行加载;某个类被类加载器加载后,将在堆中生成一个Class对象,该对象和方法区中的二进制字节码文件存在联系
  • java.lang.reflect.Method:代表类的方法,Method对象代表类中某个方法
  • java.lang.reflect.Field:代表类的成员变量,Field对象代表类中某个属性
  • java.lang.reflect.Constructctor:代表类的构造方法,Constructctor对象代表类中某个构造器

4、反射的优点和缺点

  • 优点:可以动态的创建和使用对象,其应用于框架底层,如果没有反射机制,框架技术就失去底层支撑
  • 缺点:使用反射基本是解释执行,对执行速度有影响(对于普通代码来说,现在JVM编辑器采用热点代码即时编译 + 解释执行两种方式并行的方案)

证明反射缺点:

import java.lang.reflect.Method;

public class Test {
    public static void main(String[] args) throws Exception {
        int num = 100000000;
        System.out.println("执行方法次数:" + num);
        m1(num);
        m2(num);
    }

    /**
     * 通过普通方式执行方法
     * 
     * @author 明快de玄米61
     * @date   2024/7/22 23:10
     * @param  num 执行次数
     **/
    public static void m1(int num) {
        // 开始时间
        long startTime = System.currentTimeMillis();

        // 执行代码
        Cat cat = new Cat();
        for (int i = 0; i < num; i++) {
            cat.said();
        }

        // 结束时间
        long endTime = System.currentTimeMillis();

        // 耗时
        System.out.println("普通方式耗时:" + (endTime - startTime) + "毫秒");
    }

    /**
     * 通过反射方式执行方法
     *
     * @author 明快de玄米61
     * @date   2024/7/22 21:45
     * @param  num 执行次数
     **/
    public static void m2(int num) throws Exception {
        // 开始时间
        long startTime = System.currentTimeMillis();

        // 执行代码
        Class<?> cls = Class.forName("com.atguigu.reflect.Cat");
        Object o = cls.newInstance();
        Method method = cls.getMethod("said");
        for (int i = 0; i < num; i++) {
            method.invoke(o);
        }

        // 结束时间
        long endTime = System.currentTimeMillis();

        // 耗时
        System.out.println("反射方式耗时:" + (endTime - startTime) + "毫秒");
    }
}

class Cat {
    public void said() {
        // 控制台会出现太多打印结果,所以注释掉吧!
//        System.out.println("喵喵喵~");
    }
}

证明反射缺点的结果:

执行方法次数:100000000
普通方式耗时:9毫秒
反射方式耗时:775毫秒

5、反射调用优化—关闭访问检查

Method、Field、Constructor对象都有setAccessible()方法

该方法作用是禁用访问安全检查的开关,默认是关闭的。即:默认开启访问检查

如果参数设置为true,表示反射的对象在使用时取消访问检查,提高反射的效率

如果参数设置为false(默认值),表示反射的对象将执行访问检查

在这里插入图片描述

举例说明:

import java.lang.reflect.Method;

public class Test {
    public static void main(String[] args) throws Exception {
        int num = 100000000;
        System.out.println("执行方法次数:" + num);
        m2(num);
        m3(num);
    }

    /**
     * 通过反射方式执行方法(执行访问检查:默认值)
     *
     * @author 明快de玄米61
     * @date   2024/7/22 21:45
     * @param  num 执行次数
     **/
    public static void m2(int num) throws Exception {
        // 开始时间
        long startTime = System.currentTimeMillis();

        // 执行代码
        Class<?> cls = Class.forName("com.atguigu.reflect.Cat");
        Object o = cls.newInstance();
        Method method = cls.getMethod("said");
        for (int i = 0; i < num; i++) {
            method.invoke(o);
        }

        // 结束时间
        long endTime = System.currentTimeMillis();

        // 耗时
        System.out.println("在反射时,执行访问检查的耗时:" + (endTime - startTime) + "毫秒");
    }

    /**
     * 通过反射方式执行方法(不执行访问检查:手动设置)
     *
     * @author 明快de玄米61
     * @date   2024/7/22 21:45
     * @param  num 执行次数
     **/
    public static void m3(int num) throws Exception {
        // 开始时间
        long startTime = System.currentTimeMillis();

        // 执行代码
        Class<?> cls = Class.forName("com.atguigu.reflect.Cat");
        Object o = cls.newInstance();
        Method method = cls.getMethod("said");
        // 在反射调用方法时,取消访问检查
        method.setAccessible(true);
        for (int i = 0; i < num; i++) {
            method.invoke(o);
        }

        // 结束时间
        long endTime = System.currentTimeMillis();

        // 耗时
        System.out.println("在反射时,取消访问检查的耗时:" + (endTime - startTime) + "毫秒");
    }
}

class Cat {
    public void said() {
        // 控制台会出现太多打印结果,所以注释掉吧!
//        System.out.println("喵喵喵~");
    }
}

结果:

执行方法次数:100000000
在反射时,执行访问检查的耗时:243毫秒
在反射时,取消访问检查的耗时:125毫秒

6、Class类 介绍

  • Class也是类,因此也继承Object类
  • Class类对象不是new出来的,而是由类加载器加载的,通过ClassLoader类以及子类的loadClass方法
  • 对于某个类的Class类对象,在内存中只有一份,根据双亲委派机制,所以类只加载一次
  • 每个类的实例都会记得自己是由哪个Class实例所生成
  • 通过Class可以完整地得到一个类的完整结构,可以通过一系列API得到属性、方法、构造器
  • Class对象存放在堆中
  • 类的字节码二进制数据存放在方法区中,有的地方称为类的元数据(包括方法代码、变量名、方法名、访问权限等等)

在这里插入图片描述

7、Class类—常用方法

在这里插入图片描述

8、哪些类型有Class对象

  • 类:外部类、成员内部类、静态内部类、局部内部类、匿名内部类
  • 接口
  • 枚举
  • 注解
  • 数组
  • 基本数据类型
  • void

示例代码如下:

// 外部类
Class<String> cls1 = String.class;

// 接口
Class<Serializable> cls2 = Serializable.class;

// 枚举
Class<Thread.State> cls3 = Thread.State.class;

// 注解
Class<Override> cls4 = Override.class;

// 数组
// 一维数组
Class<int[][]> cls5 = int[][].class;
// 二维数组
Class<Integer[]> cls6 = Integer[].class;

// 基本数组类型
Class<Integer> cls7 = int.class;

// void
Class<Void> cls8 = void.class;

二、使用方式

1、Class类

前提准备:

import java.lang.annotation.Annotation;

// 父类
class Animal {
}

// 子类
class Cat extends Animal {
    public void said() {
    }
}

示例:

// 1、获取Class对象
// 全类名
String classPath = "com.atguigu.reflect.Cat";
// 通过全类名获取Class类对象
Class<?> cls = Class.forName(classPath);

// 2、获取全类名,结果是:Cat
System.out.println(cls.getName());

// 3、获取简单类名,结果是:com.atguigu.reflect.Cat
System.out.