深入理解java Class对象

badmonkey 2021年05月17日 55次浏览

深入理解java Class对象

java具有反射的机制,而反射依赖于class对象,想要理解反射必须理解class对象。

反射:在程序运行时,能够获得类的信息。例如自定义一个People类,我们能够不通过new 的方法创建一个对应的实例对象。

Class对象

Class类被创建后的对象就是Class对象,Class对象表示的是自己手动编写类的类型信息,比如创建一个Shapes类,那么,JVM就会创建一个Shapes对应Class类的Class对象,该Class对象保存了Shapes类相关的类型信息。

也就是说class 对象其实就是用于保存具体类的信息,这些信息包括方法和成员

实际上使用javac编译源文件得到的class字节码文件保存的就是class对象,但我们需要new一个对象时会到class对象中寻找对应的构造方法,其实所有涉及到类的信息(调用方法和成员)都需要寻找class对象,同一个类型的不同实例都指向一个class对象(可以将class对象理解为一种类型,只不过是一种复杂的自定义类型,包括了很多方法和成员信息)。

class对象示意图

动态加载

class对象是保存在class文件中的,编译时会将所有源文件都编译,但是jvm在加载的时候并不是将所有的class文件同时加载到jvm中的(要使用class对象必须加载class文件)。而是采用动态加载。所谓动态加载,就是需要使用对应的类信息时才会加载对应的class对象。主要的流程如下:

类加载器检查jvm中是否已经加载对应的class对象,如果已经加载直接使用即可。

如果没有被加载,那么默认的类加载器会根据类名寻找对应的class文件,检查class文件的安全性没有问题后,会将class文件加载到jvm中。

那么时候会触发动态加载呢?前面提到过如果需要使用类对应的信息会才会加载对应的class对象,其实这个说法不太准确,只有使用那些静态成员信息和静态成员方法时会触发动态加载。

class Demo{
    static int staticInt = 233;
    Demo(){};
    void test(){System.out.println("test")};
    public static void main(String[] args){
        System.out.println(Demo.staticInt);// 访问静态成员,会触发动态加载
        Demo demo = new Demo();// 构造方法属于静态方法,同样的会触发动态加载,毕竟new了一个实例必然加载class对象(doge
        // 非静态方法,不能直接调用,必须实例化后才能调用,实例化必然已经加载过class对象,所以无需考虑非静态发方法
    }
}

此外使用Class.forName("完整类名")也会触发class对象的加载(前提时class对象没有被加载过),这里笔者的理解是,Class.forName属于反射操作,想要通过类名获取对应的class对象,那么就会在jvm中寻找对应的class对象,如果存在就返回,不存在的话,就尝试加载对应的类然后返回,说到这里反射的原理也较清晰了。

反射

反射是通过获取jvm中已经加载过的class对象得到对应实例所属类的信息,获得class对象中的类信息后,可调用对应类的方法和成员,可以达到创建新的实例化对象的效果等等。。

那么怎么获取class对象呢?

  1. 首先是通过类名获取,即Class.forName前文已经提到过了,这里不做过多讲解(这种方法不需要对应类的实例化对象)。
  2. 通过调用实例的getClass()方法,获取对应的class对象,class对象是实例的一个属性。
  3. 通过字面常量的方法char.class,注意即使Person.class中的Person的class对象没有被加载到jvm中也不会触发动态加载(因为.class属于静态加载,在编译期就会使用类加载器加载对应的类

反射的内容大致就是这么多,下面补充一点点关于类加载的过程

类加载

前面提到过,使用类的信息是会加载class对象,即加载class文件。但是这只是类加载的第一部分。

  • 加载:类加载过程的一个阶段,通过一个类的完全限定查找此类字节码文件,并利用字节码文件创建一个Class对象;
  • 链接:验证字节码的安全性和完整性,准备阶段正式为静态域分配存储空间,注意此时只是分配静态成员变量的存储空间,不包含实例成员变量,如果必要的话,解析这个类创建的对其他类的所有引用;
  • 初始化:类加载最后阶段,若该类具有超类,则对其进行初始化,执行静态初始化器和静态初始化成员变量。

类加载过程

这里我们不关心,链接的过程,主要关注一下初始化的过程。

  • 其中实例类的getClass方法和Class类的静态方法forName都将会触发类的初始化阶段,而字面常量获取Class对象的方式则不会触发初始化。
  • 初始化是类加载的最后一个阶段,也就是说完成这个阶段后类也就加载到内存中(Class对象在加载阶段已被创建),此时可以对类进行各种必要的操作了(如new对象,调用静态成员等),注意在这个阶段,才真正开始执行类中定义的Java程序代码或者字节码。

参考链接

https://blog.csdn.net/javazejian/article/details/70768369