java findclass_带你深入了解Class类-深度分析:反射从入门到精通

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就创建了对象,那流程是什么样的呢?见下图

d12d2e6a1f6665b4729483d63ef001d1.png

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

82e0267bfd3c20a70338b2a5810ef09b.png

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

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

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

1.2 .class 文件

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

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

有如下一个类:

d30eab5af98f6ac200295270cb1451e3.png

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

acc552fa61ead76cfee5e760147018e5.png

这啥玩意,看不懂。咱也不需要看懂,反正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文件主要分位三个步骤

检查类是否已经加载,如果有就直接返回

当前不存在该类,遵循双亲委派机制,加载 .class文件

上面两步都失败,调用 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对象。

dc89243c17be954e07bf6db07783cc80.png

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

权限修饰符

类名和泛型信息

接口

实体

注解

构造函数

方法

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

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

c1d826eb28eb8d5edbf88a6f065d35bd.png

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

e2af0f392d7466d58cd028efe1b37390.png

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

那我们看看Class类的方法

1.4.1 构造器

6b48055143f5fd3a27689b79c3f57e1d.png

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

1.4.2 Class.forName

aee81a492c96a384520b266dd9c2d578.png

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

1.4.3 newInstance

13221db4711bd067f1f8622461289bf2.png

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

2. 总结

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

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

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

通过ClassLoader的loadClass()方法

从缓存中查找,直接返回

缓存中不存在,通过双亲委派机制加载

上面两步都失败,调用findClass()

通过 IO 流从指定位置获取到 .class文件得到字节数组

调用类加载器defineClass() 方法,由字节数组得到Class对象

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

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值