百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

反射必杀技:深入了解Class类,让你一通百通

toyiye 2024-06-27 00:52 17 浏览 0 评论

推荐阅读:终于明白阿里美团那些大厂,为什么经常拿HTTP考验求职者了

1. Class 类的原理

孟子曰:得人心者得天下。而在 Java 中,这个「人心」就是 Class 类,获取到 Class 类我们就可以为所欲为之为所欲为。下面让我们深入「人心」,去探索 Class 类的原理。

首先了解 JVM 如何构建实例。

1.1 JVM 构建实例

JVM:Java Virtual Machine,Java 虚拟机。在 JVM 中分为栈、堆、方法区等,但这些都是 JVM 内存,文中所描述的内存指的就是 JVM 内存。.class 文件是字节码文件,是通过 .java 文件编译得来的。

知道上面这些内容,我们开始创建实例。我们以创建 Person 对象举例:

Person p = new Person()

简简单单通过 new 就创建了对象,那流程是什么样的呢?见下图

这也太粗糙了一些,那在精致一下吧。

同志们发现没有,其实这里还是有些区别的,我告诉你区别是下面的字比上面多,你会打我不(别打我脸)。

粗糙的那个是通过 new 创建的对象,而精致的是通过 ClassLoader 操作 .class 文件生成 Class 类,然后创建的对象。

其实通过 new 或者反射创建实例,都需要 Class 对象。

1.2 .class 文件

.class 文件在文章开头讲过,是字节码文件。.java 是源程序。Java 程序是跨平台的,一次编译到处执行,而编译就是从源文件转换成字节码文件。

字节码无非就是由 0 和 1 构成的文件。

有如下一个类:

通过 vim 查看一下字节码文件:

这啥玩意,看不懂。咱也不需要看懂,反正 JVM.class 文件有它自己的读取规则。

1.3 类加载器

还记得上面的精致图中,我们知道是通过类加载器把 .class 文件加载到内存中。具体的类加载器内容,我会另写一篇文章讲解(写完链接会更新到这里)。但是核心方法就是 loadClass(),只需要告诉它要加载的 name,它就会帮你加载:

protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
{
    synchronized (getClassLoadingLock(name)) {
        // 1.检查类是否已经加载
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            long t0 = System.nanoTime();
            try {
                // 2.尚未加载,遵循父优先的等级加载机制(双亲委派机制)
                if (parent != null) {
                    c = parent.loadClass(name, false);
                } else {
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // ClassNotFoundException thrown if class not found
                // from the non-null parent class loader
            }

            if (c == null) {
                // 3.如果还没有加载成功,调用 findClass()
                long t1 = System.nanoTime();
                c = findClass(name);

                // this is the defining class loader; record the stats
                sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                sun.misc.PerfCounter.getFindClasses().increment();
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}

// 需要重写该方法,默认就是抛出异常
protected Class<?> findClass(String name) throws ClassNotFoundException {
    throw new ClassNotFoundException(name);
}

类加载器加载 .class 文件主要分位三个步骤

  1. 检查类是否已经加载,如果有就直接返回
  2. 当前不存在该类,遵循双亲委派机制,加载 .class 文件
  3. 上面两步都失败,调用 findClass()

因为 ClassLoader 的 findClass 方法默认抛出异常,需要我们写一个子类重新覆盖它,比如:

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        try {
            // 通过IO流从指定位置读取xxx.class文件得到字节数组
            byte[] datas = getClassData(name);
            if (null == datas){
                throw new ClassNotFoundException("类没有找到:" + name);
            }
            // 调用类加载器本身的defineClass()方法,由字节码得到 class 对象
            return defineClass(name, datas, 0, datas.length);
        }catch (IOException e){
            throw new ClassNotFoundException("类没有找到:" + name);
        }
    }

    private byte[] getClassData(String name) {
        return byte[] datas;
    }

defineClass 是通过字节码获取 Class 的方法,是 ClassLoader 定义的。我们具体不知道如何实现的,因为最终会调用一个 native 方法:

    private native Class<?> defineClass0(String name, byte[] b, int off, int len,
                                         ProtectionDomain pd);

    private native Class<?> defineClass1(String name, byte[] b, int off, int len,
                                         ProtectionDomain pd, String source);

    private native Class<?> defineClass2(String name, java.nio.ByteBuffer b,
                                         int off, int len, ProtectionDomain pd,
                                         String source);

总结下类加载器加载 .class 文件的步骤:

  • 通过 ClassLoader 类中 loadClass() 方法获取 Class
  • 从缓存中查找,直接返回
  • 缓存中不存在,通过双亲委派机制加载
  • 上面两步都失败,调用 findClass()通过 IO 流从指定位置获取到 .class 文件得到字节数组调用类加载器 defineClass() 方法,由字节数组得到 Class 对象

1.4 Class 类

.class 文件已经被类加载器加载到内存中并生成字节数组,JVM 根据字节数组创建了对应的 Class 对象。

接下来我们来分析下 Class 对象。

我们知道 Java 的对象会有下面的信息:

  1. 权限修饰符
  2. 类名和泛型信息
  3. 接口
  4. 实体
  5. 注解
  6. 构造函数
  7. 方法

这些信息在 .class 文件以 0101 表示,最后 JVM 会把 .class 文件的信息通过它的方式保存到 Class 中。

Class 中肯定有保存这些信息的字段,我们来看一下:

Class 类中用 ReflectionData 里面的字段来与 .class 的内容映射,分别映射了字段、方法、构造器和接口。

通过 annotaionData 映射了注解数据,其它的就不展示了,大家可以自行打开 IDEA 查看下 Class 的源码。

那我们看看 Class 类的方法

1.4.1 构造器

Class 类的构造器是私有的,只能通过 JVM 创建 Class 对象。所以就有了上面通过类加载器获取 Class 对象的过程。

1.4.2 Class.forName

Class.forName() 方法还是通过类加载器获取 Class 对象。

1.4.3 newInstance

newInstance() 的底层是返回无参构造函数。

2. 总结

我们来梳理下前面的知识点:

反射的关键点就是获取 Class 类,那系统是如何获取到 Class 类?

是通过类加载器 ClassLoader.class 文件通过字节数组的方式加载到 JVM 中,JVM 将字节数组转换成 Class 对象。那类加载器是如何加载的呢?

  • 通过 ClassLoaderloadClass() 方法
  • 从缓存中查找,直接返回
  • 缓存中不存在,通过双亲委派机制加载
  • 上面两步都失败,调用 findClass()通过 IO 流从指定位置获取到 .class 文件得到字节数组调用类加载器 defineClass() 方法,由字节数组得到 Class 对象

Class 类的构造器是私有的,所以需要通过 JVM 获取 Class

Class.forName() 也是通过类加载器获取的 Class 对象。newInstance 方法的底层也是返回的无参构造函数。

相关推荐

如何用 coco 数据集训练 Detectron2 模型?

随着最新的Pythorc1.3版本的发布,下一代完全重写了它以前的目标检测框架,新的目标检测框架被称为Detectron2。本教程将通过使用自定义coco数据集训练实例分割模型,帮助你开始使...

CICD联动阿里云容器服务Kubernetes实践之Bamboo篇

本文档以构建一个Java软件项目并部署到阿里云容器服务的Kubernetes集群为例说明如何使用Bamboo在阿里云Kubernetes服务上运行RemoteAgents并在agents上...

Open3D-ML点云语义分割实验【RandLA-Net】

作为点云Open3D-ML实验的一部分,我撰写了文章解释如何使用Tensorflow和PyTorch支持安装此库。为了测试安装,我解释了如何运行一个简单的Python脚本来可视化名为...

清理系统不用第三方工具(系统自带清理软件效果好不?)

清理优化系统一定要借助于优化工具吗?其实,手动优化系统也没有那么神秘,掌握了方法和技巧,系统清理也是一件简单和随心的事。一方面要为每一个可能产生累赘的文件找到清理的方法,另一方面要寻找能够提高工作效率...

【信创】联想开先终端开机不显示grub界面的修改方法

原文链接:【信创】联想开先终端开机不显示grub界面的修改方法...

如意玲珑成熟度再提升,三大发行版支持教程来啦!

前期,我们已分别发布如意玲珑在deepinV23与UOSV20、openEuler24.03发行版的操作指南,本文,我们将为大家详细介绍Ubuntu24.04、Debian12、op...

118种常见的多媒体文件格式(英文简写)

MP4[?mpi?f??]-MPEG-4Part14(MPEG-4第14部分)AVI[e?vi??a?]-AudioVideoInterleave(音视频交错)MOV[m...

密码丢了急上火?码住7种console密码紧急恢复方式!

身为攻城狮的你,...

CSGO丨CS2的cfg指令代码分享(csgo自己的cfg在哪里?config文件位置在哪?)

?...

使用open SSL生成局域网IP地址证书

某些特殊情况下,用户内网访问多可文档管理系统时需要启用SSL传输加密功能,但只有IP,没有域名和证书。这种情况下多可提供了一种免费可行的方式,通过openSSL生成免费证书。此方法生成证书浏览器会提示...

Python中加载配置文件(python怎么加载程序包)

我们在做开发的时候经常要使用配置文件,那么配置文件的加载就需要我们提前考虑,再不使用任何框架的情况下,我们通常会有两种解决办法:完整加载将所有配置信息一次性写入单一配置文件.部分加载将常用配置信息写...

python开发项目,不得不了解的.cfg配置文件

安装软件时,经常会见到后缀为.cfg、.ini的文件,一般我们不用管,只要不删就行。因为这些是程序安装、运行时需要用到的配置文件。但对开发者来说,这种文件是怎么回事就必须搞清了。本文从.cfg文件的创...

瑞芯微RK3568鸿蒙开发板OpenHarmony系统修改cfg文件权限方法

本文适用OpenHarmony开源鸿蒙系统,本次使用的是开源鸿蒙主板,搭载瑞芯微RK3568芯片。深圳触觉智能专注研发生产OpenHarmony开源鸿蒙硬件,包括核心板、开发板、嵌入式主板,工控整机等...

Python9:图像风格迁移-使用阿里的接口

先不多说,直接上结果图。#!/usr/bin/envpython#coding=utf-8importosfromaliyunsdkcore.clientimportAcsClient...

Python带你打造个性化的图片文字识别

我们的目标:从CSV文件读取用户的文件信息,并将文件名称修改为姓名格式的中文名称,进行规范资料整理,从而实现快速对多个文件进行重命名。最终效果:将原来无规律的文件名重命名为以姓名为名称的文件。技术点:...

取消回复欢迎 发表评论:

请填写验证码