反射必杀技:深入了解Class类,让你一通百通-创新互联

1. Class 类的原理

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

10多年的青冈网站建设经验,针对设计、前端、开发、售后、文案、推广等六对一服务,响应快,48小时及时工作处理。全网营销推广的优势是能够根据用户设备显示端的尺寸不同,自动调整青冈建站的显示方式,使网站能够适用不同显示终端,在浏览器中调整网站的宽度,无论在任何一种浏览器上浏览网站,都能展现优雅布局与设计,从而大程度地提升浏览体验。创新互联从事“青冈网站设计”,“青冈网站推广”以来,每个客户项目都认真落实执行。

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

1.1 JVM 构建实例

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

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

Person p = new Person()

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

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

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

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

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

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

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

1.2 .class 文件

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

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

有如下一个类:

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

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

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

这啥玩意,看不懂。咱也不需要看懂,反正 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对象。

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

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

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

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

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

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

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

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

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

那我们看看 Class类的方法

1.4.1 构造器

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

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

1.4.2 Class.forName

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

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

1.4.3 newInstance

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

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

2. 总结

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

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

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

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

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

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

创新互联www.cdcxhl.cn,专业提供香港、美国云服务器,动态BGP最优骨干路由自动选择,持续稳定高效的网络助力业务部署。公司持有工信部办法的idc、isp许可证, 机房独有T级流量清洗系统配攻击溯源,准确进行流量调度,确保服务器高可用性。佳节活动现已开启,新人活动云服务器买多久送多久。

当前标题:反射必杀技:深入了解Class类,让你一通百通-创新互联
网站路径:https://www.cdcxhl.com/article30/dsjipo.html

成都网站建设公司_创新互联,为您提供移动网站建设关键词优化静态网站品牌网站建设网站导航企业建站

广告

声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联

搜索引擎优化