第18章 类加载和反射

类的加载、连接和初始化

系统可能在第一次使用某个类时加载该类,也可能采用预先加载机制来预加载某个类。

类的加载

类的加载指的是类的 class 文件读入内存,并为之创建一个 java.lang.Class 对象,也就是说当程序中使用任何类时,系统都会为之建立一个 java.lang.Class 对象。类的加载是由类加载器完成的,类加载器通常由 JVM 提供。
通过使用不同的类加载器,可以从不同来源加载类的二进制数据:

  • 从本地文件系统来加载 class 文件
  • 从 JAR 包中加载 class 文件
  • 通过网络加载 class 文件
  • 把一个 Java 源文件动态编译、并执行加载

类的连接

当类被加载之后,系统为之生成一个对应的 Class 对象,接着将会进入连接阶段,连接阶段将会负责把类的二进制数据合并到 JRE 中。类连接又可分为如下三个阶段:

  • 验证:验证阶段用于检验被加载的类是否有正确的内部结构,并和其他类协调一致
  • 准备:类准备阶段负责为类的静态属性分配内存,并设置默认初始值
  • 解析:将类的二进制数据中的符号引用替换成直接引用

类的初始化

在类的初始化阶段,虚拟机负责对类进行初始化,主要就是对静态属性进行初始化。在 Java 类中对静态属性指定初始值有两种方式:(1)声明静态属性时指定初始值;(2)使用静态初始化块为静态属性指定初始值。

public class Test{
    //声明a变量时指定初始值
    static int a = 5;
    static int b;
    static int c;
}
static{
    //使用静态初始化块为b变量指定初始值
    b = 6;
}

静态属性 c 没有指定初始值,它将采用默认初始值 0。

JVM 初始化一个类包括如下几个步骤:

  1. 加入这个类还没有被加载和连接,程序先加载并连接该类
  2. 加入该类的直接父类还没有被初始化,则先初始化其直接父类
  3. 加入类中有初始化语句,则系统依次执行这些初始化语句
    当执行到第二步时,系统对直接父类的初始化也遵循上面的三个步骤,如果该直接父类又有直接父类,系统将再次重复这三个步骤来初始化这个父类…。所以,JVM 最先初始化的总是 java.lang.Object类。当程序主动使用任何一个类时,系统会保证该类以及其所有的父类都会被初始化。

类初始化的时机

当 Java 程序首次通过下面 6 种方式来使用某个类或接口时,系统会初始化该类或接口:

  1. 创建类的实例。为某个类创建实例的方式包括使用 new 操作符来创建实例,通过反射来创建实例,通过反序列化的方式来创建实例。
  2. 调用某个类的静态方法。
  3. 访问某个类或接口的静态属性,或为该静态属性赋值。
  4. 使用反射方式来强制创建某个类或接口对应的 java.lang.Class 对象。
  5. 初始化某个类的子类。当初始化某个类的子类时,该子类的所有父类都会被初始化。
  6. 直接使用 java.exe 命令来运行某个主类,当运行某个主类时,程序会先初始化该主类。
    下面几种情形需要特别指出:
    对于一个 final 型的静态属性,如果该属性在编译时就可以得到属性值,则可认为该属性可被当成编译时常量。当程序使用编译时常量时,系统会认为这是对该类的被动使用,所以不会导致该类的初始化。
class Tester{
    static{
        System.out.println("Tester类的静态初始化块...");
    }
}

public class ClassLoaderTest {
    public static void main(String[] args) throws ClassNotFoundException{
        ClassLoader c1 = ClassLoader.getSystemClassLoader();
        //下面语句仅仅是加载Tester类
        c1.loadClass("Tester");
        //下面语句才会初始化Tester类
        Class.forName("Tester");
    }
}

类加载器

通过反射查看类的信息

Java 程序种许多对象在运行时会出现两种类型:编译时类型和运行时类型,例如:Person p = new Student();,这行代码将会生成一个 p 变量,该变量的编译类型为 Person,运行时类型为 Student。除此之外,还有程序在运行时接收外部传入的一个对象,该对象的编译类型是 Object,但程序又需要调用该对象运行时类型的方法。
为了解决这些问题,程序需要在运行时发现对象和类的真是信息,有两种做法:

  1. 假设在编译和运行时完全直到类型的具体信息,我们可以利用 instanceof 运算符进行判断,再利用强制类型转换将其转换为运行时变量。
  2. 第二种是编译时根本无法预知该对象和类可能属于哪些类,程序只能依靠运行时信息来发现该类和对象的真实信息,这就必须使用反射。

获取 Class 对象

Java 程序中获取 Class 对象的三种方法:

  1. 使用 Class 类的 forName() 静态方法。该方法需要传入字符串参数,参数值是某个类的全限定名。
  2. 调用某个类的 class 属性来获取该类的 Class 对象。例如 Person.class 将会返回 Person 类对应的 Class 对象。
  3. 调用某个对象的 getClass() 方法,该方法是 java.lang.Object 类中的一个方法,该方法将会返回该对象所属类对应的 Class 对象。

通过反射生成并操作对象

猜你喜欢

转载自blog.csdn.net/qq_32682177/article/details/83019898