年轻时也曾被这个问题困扰多时直到去看open jdk的源代码才彻底搞懂了。为什么这个问题当时的我一直搞不懂呢? 因为在java里到处有class的身影比如.class文件 - 磁盘上的字节码文件Class对象 - 运行时JVM内存中的元数据对象class关键字 - 源码中定义类的语法。要想彻底搞懂java中的class则上面的三个就必须完全搞懂另外这三者还是递进关系来的:编译成.class字节码 -- JVM加载后生成Class对象。其中最难啃的就是Class对象。Class对象到底是何方神圣在正式开始之前我需要先分享一个理解知识的重要方法就是有什么问题要解决?你得先知道到底存在什么问题需要被去解决而各路神仙的解决方案又是什么然后方能彻底理解这块的知识。比如你看哈我最近在写了一篇技术内容Java NIO到底是个什么东西?我并没有一来就开始介绍NIO而是通过高可靠的网络服务需要解决什么问题然后慢慢引导出设计方案最终才到JAVA的NIO是如何解决这些问题的。这样子就能很好的理解这块的知识也比较容易记得牢。好我们现在依然采用这个方法从问题入手然后慢慢引导出解决方案以及java是如何解决的。从类型内省谈起软件系统发展到一定阶段必然会遇到一个架构层面的根本问题程序能否在运行时“看清”自己的结构这不是什么虚头巴脑的东西而是实实在在的工程需求一种纯技术的需求比如说JSON序列化框架拿到一个对象需要知道它有哪些字段才能把它转成 JSON 字符串依赖注入容器Spring需要知道构造函数里声明了什么参数才能把对应的 Bean 自动塞进去半ORM和ORM框架MyBatis/Hibernate需要知道对象字段的类型才能把它映射到数据库的列RPC方法签名是什么参数/返回值怎么编解码才能远程调用。这个能力在编程语言设计中有一个专门的术语类型内省Introspection。面对这个问题不同的编程语言选择了完全不同的技术设计方案。各种编程语言是如何处理类型信息的各种编程语言针对这个问题给出的解决方案是截然不同的。设计思路一极简主义C/C编译即销毁C 追求极致的性能。代码编译成机器码后绝大部分类型信息都被抹除只剩下内存地址和指令。虽然 C 也有 RTTI运行时类型识别但非常弱。所以 C 要实现序列化往往需要手写代码或者依赖编译期的宏无法像 Java 那样动态自查。设计思路二折中主义Go自带说明书Go 语言没有虚拟机编译出来的也是二进制文件。但它在编译时会把类型的元数据字段名、Tag等打包写进二进制文件的特定段Section里。在运行时通过特定的接口reflect包去读取这份内置说明书。这是一种很好的平衡既保留了原生编译的高性能又提供了一定的动态能力。不得不说GO语言的设计者还是挺天才的。设计思路三全动态主义Java/C#Java 选择了最重但也最灵活的方案它把类型的定义信息视为一种对象。读到这里的时候你就应该能意识到编译之后运行时还能保留多少可供程序访问的类型元数据访问成本是多少是大多数程序语言要考虑的问题。为了帮助你理解接下来的内容你需要再理解一个东西就是所谓的元数据。很多人第一次看到MetaData/元数据会懵其实它没那么难理解。元数据 描述数据的数据。你可以把它理解成说明书。举个最直观的例子你写的User对象里面有id、name、age这些值,这种叫数据但这个对象有哪些字段、字段叫什么、字段类型是什么、有哪些方法、有哪些注解、构造器参数是什么 这些是描述****User**** 的信息这就叫元数据再强调一下元数据不是你的业务数据它是结构信息说明书信息。那什么叫【类型】的元数据其实就是专门描述一个类型类长什么样的那份说明书包括类名、包名父类、实现了哪些接口有哪些字段字段名/字段类型/修饰符有哪些方法方法名/参数类型/返回值/异常有哪些注解、泛型签名部分、访问权限等等在 Java 里这份类型说明书的入口就是java.lang.Class**** 对象。你平时写的User.class、obj.getClass()拿到的就是这本说明书Class? clazz User.class; // 类名com.xxx.User System.out.println(clazz.getName()); // 字段数量 System.out.println(clazz.getDeclaredFields().length); // 方法数量 System.out.println(clazz.getDeclaredMethods().length);所以这句话就很好理解了编译之后运行时还能保留多少可供程序访问的类型元数据大白话讲就是编译完以后运行时还能不能拿到这本说明书也就是 Class 里能不能读到字段、方法、注解等信息并且读它要付出多大代价。Java的解决方案Class对象是桥梁好现在可以开始回答Java 中的Class到底是什么这个问题了。在 java虚拟机JVM 的视角里内存被分为了两个大块元数据Metaspace这里存放着类的说明信息这是给虚拟机用的Java 代码无法直接访问堆Heap这里存放着我们在代码里new出来的各种实例。而java.lang.Class对象就是 JVM 在加载类时特意在Heap里暴露的一个访问入口。当User.class被加载到内存时JVM 做了两件事解析字节码将类的结构信息元数据存入元空间Metaspace。在**堆Heap**中创建一个ClassUser的实例对象。这个Class对象内部持有指向元空间的指针。它就像是一个中间人它对外Java代码暴露方法getName(),getFields()它对内JVM通过指针去元空间查询真正的数据结构。所以Java 中所有的反射操作Reflection本质上都是在和这个Class对象对话通过它间接操控底层的类型信息。如果你还是理解不了的话我们可以用苹果手机来做一个比喻我们平时new出来的对象Object是工厂生产出来的具体产品,比如一台台 iPhone,而与之对应的Class对象就是这台 iPhone 的设计图纸。因此所谓的反射就是程序在运行的时候拿着手里的产品Object反向去找它的图纸Class看清楚内部构造后再对产品进行修改或复制。因此反射中的【反】字不是随便起的是有真实的具体含义的。当我们正确理解Class对象后class关键字和.class文件就可以简单用几句话来描述了。在JAVA中的类它是有不同形态的。静态形态源代码里的class关键字传输形态编译后的.class字节码文件运行时形态堆内存中的java.lang.Class对象。因此最关键的还是需要理解好Class对象其他的就自然而然能理解了。小结希望这篇内容能帮助到你真正的理解java的Class对象如果有疑问的可以在评论区留言。最近在知乎出了「应付6000万会员的秒杀系统专栏」「几亿用户,百万并发的C端商品系统实战」「技术团队DDD领域驱动设计三年落地实战」「应付亿级用户规模的支付系统代码实战」「应付亿级用户的会员体系代码实战」专栏感兴趣的可以订阅一下。至于知识星球的可以搜老码头的技术浮生录它是一个能实际帮你解决难题的星球。有问题的找知心的Sam哥支持无限次语音一对一解决你遇到的难题。「另外后续我新写的所有对外的付费专栏在星球内都是免费的且可以拿到所有源代码。」当前星球里免费看的专栏是「应付6000万会员的秒杀系统专栏」「几亿用户,百万并发的C端商品系统实战」「技术团队DDD领域驱动设计三年落地实战」「应付亿级用户规模的支付系统代码实战」「应付亿级用户的会员体系代码实战」知识星球内后续将推出20个付费专栏覆盖电商全链路选购线用户会员营销线中后台购物车服务营销系统订单系统商品服务用户系统支付系统菜单服务结算服务从前台选购到中后台结算星球成员全部免费后续新增也不额外收费。我的知乎账号:SamDeepThinking