当前位置: 首页 > news >正文

【Java高级】框架底层基础:Java的反射机制剖析

💁 个人主页:黄小黄的博客主页
❤️ 支持我:👍 点赞 🌷 收藏 🤘关注
🎏 格言:All miracles start from sometime somewhere, make it right now.
本文来自专栏:JavaSE从入门到精通
在这里插入图片描述


文章目录

  • 1 为什么需要反射?
  • 2 走进Java反射机制
    • 2.1 Java Reflection 说明与原理刨析
    • 2.2 反射的相关类
    • 2.3 反射调用优化
  • 3 Class类
    • 3.1 Class类说明与演示
    • 3.2 Class常用方法
    • 3.3 获得Class对象的方式
    • 3.4 哪些类型有Class对象
  • 写在最后


1 为什么需要反射?

不知道大家有没有思考过一个问题,为什么需要反射?
通过我们以前对Java的学习,我们知道,可以通过 new 的方式,来创建一个对象,并调用对象中的方法。

但是在实际开发中,可以说很多框架都有一个需求:通过配置文件,实现在不修改源码的情况下,实现需求。

那么问题就出现了,如果没有反射机制,我们就没办法通过配置文件来创建对象… …
在这里插入图片描述

例子需求如下:

创建一个Cat类,Cat类里包含两个方法,一个为hi,调用会打印hello,另一个方法为say,调用会打印喵喵喵。
请你在main函数中创建Cat类对象,分别调用这两个方法。

Cat类代码如下:

class Cat{
    public void hi(){
        System.out.println("hello");
    }
    public void say(){
        System.out.println("喵喵喵");
    }
}

常规调用方法如下:

        Cat cat = new Cat();
        cat.say();

显然这样是可行的。
但是,当我们需要调用的方法不是say,而是hi时,就需要修改源码了。

有没有一种方式,能够让我们,仅仅通过外部文件的配置,在不修改源码的情况下,来控制程序呢?

我们尝试 通过Java的反射机制来解决, 相应的源码已经有了注释:

import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Properties;

/**
 * @author 兴趣使然黄小黄
 * @version 1.0
 * 走进反射
 */
public class Test {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException,
            IllegalAccessException, NoSuchMethodException, InvocationTargetException, IOException {

        //读取配置文件
        Properties properties = new Properties();
        properties.load(new FileInputStream("D:\\Ideaproject2021\\JavaSE\\src\\reflection\\re.properties"));
        String classFullPath = properties.getProperty("classFullPath");
        String method = properties.getProperty("method");

        //1.加载类,返回Class类型的对象
        Class aClass = Class.forName(classFullPath);

        //2.通过 aClass 得到加载的类 Cat 实例
        Object o = aClass.newInstance();
        System.out.println("o 的运行类型=" + o.getClass());

        //3.通过 aClass 得到加载的类 Cat 的 hi 方法对象
        //在反射中,万物皆是对象,可以把方法也看成对象
        Method method1 = aClass.getMethod(method);

        //4.通过method1调用方法
        //传统:对象.方法() , 反射机制:方法.invoke(对象)
        method1.invoke(o);
    }
}

配置文件信息如下:
在这里插入图片描述
运行结果:
在这里插入图片描述

如果修改配置文件中method的信息,则可以实现调用不同的方法,我们无需再更改源码。小伙伴可以自己尝试,这里不再赘述!


2 走进Java反射机制

在这里插入图片描述

2.1 Java Reflection 说明与原理刨析

反射机制 允许程序在执行期借助于Reflection API取得任何类的内部信息, 比如:类的成员变量、构造器、成员方法等。并且,能够操作对象的属性以及方法。反射在设计模式和框架的底层都经常用到。

加载完类之后,在堆中会产生一个Class类型的对象 (一个类只有一个Class对象),这个对象包含了类的完整结构信息。通过这个对象得到类的结构。这个对象就像一面镜子,透过镜子看到类的结构,所以称之为反射。

反射可以做什么?

  • 在运行时判断任意一个对象所处的类;
  • 在运行时构造任意一个类的对象;
  • 在运行时得到任意一个类所具有的成员变量和方法;
  • 在运行时调用任意一个对象的成员变量和方法;
  • 生成动态代理。

反射机制原理刨析:
在这里插入图片描述
Java程序在计算机有三个阶段:编译阶段、加载阶段和运行阶段。

  1. Student类中包含构造器、成员变量和方法等,其源码通过 Javac 编译,可以得到 Student.class字节码文件;
  2. Student.class字节码文件中包含了该类的属性、构造器、成员变量、泛型、注解等信息;
  3. 当我们 new 一个Student对象的时候,会导致类的加载,Student.class字节码文件会被加载,加载到内存的堆区,形成Class类对象;
  4. 加载通过类加载器实现,形成的Class类对象中包含成员变量、构造器、注解等,体现了反射;
  5. 在底层,会把成员变量等当作对象来看待,映射成各种数据结构,例如成员变量映射成 Field[] 数组,构造器映射成 Constructor[] 数组;
  6. 最后生成的对象生成在堆中,该对象知道自身属于哪个Class对象,存在一个映射关系,这就是反射能够操作的原因;
  7. 当得到Class对象后,可以创建对象、调用对象方法、操作属性等。

2.2 反射的相关类

在这里插入图片描述

在反射中,我们需要掌握的主要类如下:

  • java.lang.Class:代表一个类,Class对象表示某个类加载后在堆中的对象;
  • java.lang.reflect.Method:代表类的方法,Method对象表示某个类的方法;
  • java.lang.reflect.Field:代表类的成员变量,Field对象表示某个类的成员变量;
  • java.lang.reflect.Constructor:代表类的构造方法,Constructor对象标识构造器;

在下面的例子中展示反射的使用:

        //获取成员变量
        //getField不能得到私有属性
        Field field1 = aClass.getField(field);
        System.out.println(field1.get(o));
        
        //获取无参构造方法
        Constructor c1 = aClass.getConstructor();
        //获取有参构造方法
        Constructor c2 = aClass.getConstructor(String.class);//传入参赛的class对象

2.3 反射调用优化

反射可以动态的创建和使用对象,但是,使用反射基本是解释执行,对执行速度有一定的影响。

下面通过一个例子来让大家体验一下使用反射和不使用反射,效率上的区别,然后我们再来谈谈如何优化(虽然作用微乎其微)。

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * @author 兴趣使然黄小黄
 * @version 1.0
 * 反射机制使用与否的效率比对
 */
public class ReflectionTest {
    public static void main(String[] args) throws ClassNotFoundException, InvocationTargetException,
            InstantiationException, IllegalAccessException, NoSuchMethodException {
        m1();
        m2();
    }

    //使用传统方法
    public static void m1(){
        Cat cat = new Cat();
        long start = System.currentTimeMillis();
        for (int i = 0; i < 90000000; i++) {
            cat.hi();
        }
        long end = System.currentTimeMillis();
        System.out.println("传统方式调用 m1 耗时: " + (end-start) + "ms");
    }

    //使用反射方法
    public static void m2() throws ClassNotFoundException, InstantiationException, IllegalAccessException,
            InvocationTargetException, NoSuchMethodException {
        Class aClass = Class.forName("reflection.Cat");
        Object o = aClass.newInstance();
        Method hi = aClass.getMethod("hi");
        long start = System.currentTimeMillis();
        for (int i = 0; i < 90000000; i++) {
            hi.invoke(o);
        }
        long end = System.currentTimeMillis();
        System.out.println("使用反射调用 m2 耗时: " + (end-start) + "ms");
    }
}

在这里插入图片描述
可以看到,使用反射与否,对执行效率是有一定影响的。

如何对反射调用进行优化呢?

可以通过 关闭访问检查 的方式来优化:

  1. Method、Field和Constructor对象都有setAccessible()方法;
  2. setAccessible()作用是启动和关闭访问检查的开关;
  3. 参数值为true表示,反射的对象在使用时取消访问检查,提高反射效率。而参数值为false,则表示反射的对象执行访问检查。
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * @author 兴趣使然黄小黄
 * @version 1.0
 * 反射机制使用与否的效率比对
 */
public class ReflectionTest {
    public static void main(String[] args) throws ClassNotFoundException, InvocationTargetException,
            InstantiationException, IllegalAccessException, NoSuchMethodException {
        m1();
        m2();
        m3();
    }

    //使用传统方法
    public static void m1(){
        Cat cat = new Cat();
        long start = System.currentTimeMillis();
        for (int i = 0; i < 90000000; i++) {
            cat.hi();
        }
        long end = System.currentTimeMillis();
        System.out.println("传统方式调用 m1 耗时: " + (end-start) + "ms");
    }

    //使用反射方法
    public static void m2() throws ClassNotFoundException, InstantiationException, IllegalAccessException,
            InvocationTargetException, NoSuchMethodException {
        Class aClass = Class.forName("reflection.Cat");
        Object o = aClass.newInstance();
        Method hi = aClass.getMethod("hi");
        long start = System.currentTimeMillis();
        for (int i = 0; i < 90000000; i++) {
            hi.invoke(o);
        }
        long end = System.currentTimeMillis();
        System.out.println("使用反射调用 m2 耗时: " + (end-start) + "ms");
    }

    //使用反射方法,进行过优化
    public static void m3() throws ClassNotFoundException, InstantiationException, IllegalAccessException,
            InvocationTargetException, NoSuchMethodException {
        Class aClass = Class.forName("reflection.Cat");
        Object o = aClass.newInstance();
        Method hi = aClass.getMethod("hi");
        //关闭反射调用方法时的访问检查
        hi.setAccessible(true);
        long start = System.currentTimeMillis();
        for (int i = 0; i < 90000000; i++) {
            hi.invoke(o);
        }
        long end = System.currentTimeMillis();
        System.out.println("反射优化调用 m3 耗时: " + (end-start) + "ms");
    }
}

虽然作用是很小的,但是优化后的m3相较m2执行效率已经有了一些改善。

在这里插入图片描述


3 Class类

3.1 Class类说明与演示

在这里插入图片描述

Class类的基本说明:

  • Class也是一个类,是Object类的子类;
  • Class类对象不是new出来的,是由系统创建的;
  • 对于某个类的Class对象,在内存中只有一份,因为类只加载一次;
  • 每个类的实例都会记得自己是哪个Class实例所生成的;
  • 通过Class可以完整地得到一个类的完整结构,通过一系列API;
  • Class对象存放在堆中;
  • 类的字节码二进制数据,是放在方法区的,也有时候叫做类的元数据(包括方法代码、变量名、方法名、访问权限等)。

3.2 Class常用方法

方法摘要:

在这里插入图片描述
演示准备:Car类

/**
 * @author 兴趣使然黄小黄
 * @version 1.0
 */
public class Car {
    public String brand = "奥迪";
    public int price = 999999;
    public String color = "金属黑";

    @Override
    public String toString() {
        return "Car{" +
                "brand='" + brand + '\'' +
                ", price=" + price +
                ", color='" + color + '\'' +
                '}';
    }
}

下面对Class常用方法的使用进行演示:

import java.lang.reflect.Field;

/**
 * @author 兴趣使然黄小黄
 * @version 1.0
 * 演示Class类的常用方法
 */
public class ClassTest {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchFieldException {

        String classAllPath = "reflection.Car";
        //1.获取Car类对应的Class对象
        //<?>表示不确定的Java类型
        Class<?> cls = Class.forName(classAllPath);
        System.out.println("cls = " + cls);//显示cls对象,是哪个类的Class对象
        System.out.println("cls 的运行类型 = " + cls.getClass());//输出运行类型

        //2.获取包名
        System.out.println("包名 = " + cls.getPackage().getName());

        //3.得到全类名
        System.out.println("全类名 = " + cls.getName());

        //4.生成对象实例
        Car car = (Car) cls.newInstance();
        System.out.println(car);//会调用car.toString()

        //5.通过反射获取属性,但是无法获得私有属性
        Field brand = cls.getField("brand");
        System.out.println("brand的值 = " + brand.get(car));

        //6.通过反射给属性赋值
        brand.set(car, "奔驰");
        System.out.println("修改后brand的值 = " + brand.get(car));

        //7.通过遍历,得到Car类中的所有属性
        System.out.println("===================字段名==================");
        Field[] fields = cls.getFields();
        for (Field f :
                fields) {
            System.out.println(f.getName());
        }
    }
}

在这里插入图片描述

3.3 获得Class对象的方式

回顾:Java程序在计算机中有三个阶段,分别是 编译阶段、加载阶段和运行阶段。

四种核心拿到Class对象的方式如下:

  • 编译阶段:Class.forName();
  • 加载阶段:class对象已经存在了,可以通过 类.class() 拿到;
  • 运行阶段:对象已经存在,可以通过 对象.getClass() 拿到;
  • 类加载器:通过 类加载器 拿到Class对象。

下面 根据不同的应用场景,来演示获取Class对象的方式:

1️⃣ 多用于配置文件,读取全路径,加载类。
前提: 已经知道一个类的全类名,且该类在类路径下,可以通过Class类的静态方法forName()获取,可能抛出ClassNotFoundException
实例:

Class cls = Class.forName("xxx.xxx.xxx");

2️⃣ 多用于参数传递,比如通过反射得到对应的构造器对象。
前提: 若已知具体的类,通过Class获取,该方式最为安全可靠,程序性能高。
实例:

Class cls2 = XXX.class;

3️⃣ 通过创建好的对象,获取Class对象
前提: 已知某个类的实例,调用该实例的getClass()方法获取Class对象。
实例:

Car car = new Car();
Class<? extends Car> cls3 = car.getClass();

4️⃣ 通过类加载器。
实例:

//(1)先得到类加载器 car
ClassLoader classLoader = car.getClass().getClassLoader();
//(2)通过类加载器得到Class对象
Class<?> cls4 = classLoader.loadClass(classAllPath);

比较四种方式对象是否为同一对象

/**
 * @author 兴趣使然黄小黄
 * @version 1.0
 * 获取Class的不同方式,演示
 */
public class GetClassTest {
    public static void main(String[] args) throws ClassNotFoundException {
        //1. Class.forName
        String classAllPath = "reflection.Car";
        Class<?> cls1 = Class.forName(classAllPath);
        System.out.println("cls1 = " + cls1);

        //2.类名.class,多用于参数传递
        Class<Car> cls2 = Car.class;
        System.out.println("cls2 = " + cls2);

        //3.对象.getClass(),已经知道有对象实例
        Car car = new Car();
        Class<? extends Car> cls3 = car.getClass();
        System.out.println("cls3 = " + cls3);

        //4.通过类加载器来获取Class对象
        //(1)先得到类加载器 car
        ClassLoader classLoader = car.getClass().getClassLoader();
        //(2)通过类加载器得到Class对象
        Class<?> cls4 = classLoader.loadClass(classAllPath);
        System.out.println("cls4 = " + cls4);

        //比较
        System.out.println("是否为同一对象:" + ((cls1.hashCode()==cls2.hashCode())
                && (cls3.hashCode()==cls4.hashCode())
                && (cls2.hashCode() == cls3.hashCode())));
    }
}

在这里插入图片描述

当然除了以上四种方式,还有一些琐碎的其他方式没有叙述

5️⃣ 基本数据类型获取Class类对象的方式

Class cls = 基本数据类型.class;

6️⃣ 基本数据类型对应的包装类,可以通过.type得到Class类对象

Class cls = 包装类.TYPE;

3.4 哪些类型有Class对象

  1. 外部类,内部成员类,静态内部类,局部内部类,匿名内部类;
  2. interface:接口;
  3. 数组;
  4. enum:枚举;
  5. annotation:注解;
  6. 基本数据类型;
  7. void。

写在最后

🌟以上便是本文的全部内容啦,后续内容将会持续免费更新,如果文章对你有所帮助,麻烦动动小手点个赞 + 关注,非常感谢 ❤️ ❤️ ❤️ !
如果有问题,欢迎私信或者评论区!
下一节,将会与大家聊聊 类加载与反射爆破,欢迎关注!
在这里插入图片描述

共勉:“你间歇性的努力和蒙混过日子,都是对之前努力的清零。”
在这里插入图片描述

相关文章:

  • verilog移位寄存器实现序列检测
  • 前端性能优化方法与实战02 性能瓶颈点:从 URL 输入到页面加载整过程分析
  • 34.0、C语言——C语言预处理(2) - 预编译(预处理)详解(2)
  • ES优化实战 - 小操作节省百分之三十以上的磁盘空间
  • [Go WebSocket] 多房间的聊天室(五)用多个小锁代替大锁,提高效率
  • 我在windows环境下的YOLOV3环境搭建过程
  • bat goto 还是 call
  • JVM垃圾回收系列之垃圾收集算法
  • 计算机毕业设计选题推荐 40个高质量计算机毕设项目分享【源码+论文】(三)
  • BDD - SpecFlow BDD 测试实践 SpecFlow + MSTest
  • CRM项目记录(四)
  • React组件的生命周期函数
  • FFmpeg源码分析:avformat_open_input()打开媒体流
  • 深入理解关键字 一(auto,register,static,sizeof)
  • 基于Springboot+vue的停车场管理系统(Java毕业设计)
  • 2019.2.20 c++ 知识梳理
  • Create React App 使用
  • ECS应用管理最佳实践
  • idea + plantuml 画流程图
  • Java多线程(4):使用线程池执行定时任务
  • magento 货币换算
  • React 快速上手 - 07 前端路由 react-router
  • React-flux杂记
  • 分布式事物理论与实践
  • 试着探索高并发下的系统架构面貌
  • 适配iPhoneX、iPhoneXs、iPhoneXs Max、iPhoneXr 屏幕尺寸及安全区域
  • 一个项目push到多个远程Git仓库
  • 在electron中实现跨域请求,无需更改服务器端设置
  • C# - 为值类型重定义相等性
  • 基于django的视频点播网站开发-step3-注册登录功能 ...
  • 新年再起“裁员潮”,“钢铁侠”马斯克要一举裁掉SpaceX 600余名员工 ...
  • #define与typedef区别
  • (八十八)VFL语言初步 - 实现布局
  • (剑指Offer)面试题41:和为s的连续正数序列
  • (全部习题答案)研究生英语读写教程基础级教师用书PDF|| 研究生英语读写教程提高级教师用书PDF
  • (三)docker:Dockerfile构建容器运行jar包
  • (十八)三元表达式和列表解析
  • (十二)devops持续集成开发——jenkins的全局工具配置之sonar qube环境安装及配置
  • (译) 函数式 JS #1:简介
  • (原創) 博客園正式支援VHDL語法著色功能 (SOC) (VHDL)
  • (转)linux 命令大全
  • . ./ bash dash source 这五种执行shell脚本方式 区别
  • .chm格式文件如何阅读
  • .net core 3.0 linux,.NET Core 3.0 的新增功能
  • .NET Micro Framework 4.2 beta 源码探析
  • .Net通用分页类(存储过程分页版,可以选择页码的显示样式,且有中英选择)
  • .NET与java的MVC模式(2):struts2核心工作流程与原理
  • .php结尾的域名,【php】php正则截取url中域名后的内容
  • /usr/bin/env: node: No such file or directory
  • @GetMapping和@RequestMapping的区别
  • @Not - Empty-Null-Blank
  • [ vulhub漏洞复现篇 ] Celery <4.0 Redis未授权访问+Pickle反序列化利用
  • [ vulhub漏洞复现篇 ] struts2远程代码执行漏洞 S2-005 (CVE-2010-1870)
  • [ 英语 ] 马斯克抱水槽“入主”推特总部中那句 Let that sink in 到底是什么梗?
  • [2016.7 test.5] T1