欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

【Java中类的加载和执行过程】

程序员文章站 2022-07-11 20:32:57
...

Java中类的加载过程,总是搞不清楚,所以今天将所有的想法整理下来,认真分析一下。  (最后更新日期 2018/07/06 15:28)

学习的目标: 分清楚 加载 和 初始化,以及明确 类的加载 和 程序的执行顺序。


目前,多数内容来源于: (沙中世界)https://www.cnblogs.com/tengpan-cn/p/5869099.html


1. 一道阿里的笔试题解析:

/**
 * 加载方法不等于执行方法,初始化变量则会赋值
 *             类加载顺序应为 加载静态方法-初始化静态变量-执行静态代码块
 *             实例化时 先加载非静态方法-实例化非静态变量-执行构造代码块-执行构造函数
 * @author panteng
 *
 */
public class StaticTest {
    /**第一个加载*/
    public static int k = 0;
    /**第二个加载,因为是new一个实例,
     * 首先初始化j 打印出  1:j i=0 n=0
     * 执行构造块     打印出  2:构造快 i=1 n=1
     * 执行构造方法 打印出  3:t1 i=2 n=2
     * 实例化完成
     */
    public static StaticTest t1 = new StaticTest("t1");
    /**第三个加载 过程同上
     * 首先初始化j 打印出  4:j i=3 n=3
     * 执行构造块     打印出  5:构造快 i=4 n=4
     * 执行构造方法 打印出  6:t2 i=5 n=5
     */
    public static StaticTest t2 = new StaticTest("t2");
    /**第四个加载
     * 打印出  7:i i=6 n=6
     */
    public static int i = print("i");
    /**
     * 第五个加载
     */
    public static int n = 99;
    /**
     * 此变量在类加载的时候并不初始化,在实例化类的时候初始化
     */
    public int j = print("j");
     
    {
        print("构造快");
    }
    /**
     * 第六个加载 此时,n已经被初始化  所以打印出
     * 8:静态块 i=7 n=99
     */
    static{
        print("静态块");
    }
    //-----------以上属于类加载---------------------
    /**
     * 实例化过程:
     *         首先加载非静态方法集;
     *         初始化非静态变量:9:j i=8 n=100
     *         执行构造块:10:构造快 i=9 n=101
     *         执行构造方法:11:init i=10 n=102
     * 实例化完成
     */
    
    /**
     * 执行构造函数  实例化完成
     * @param str
     */
    public StaticTest(String str) {
        System.out.println((++k) + ":" + str + " i=" + i + " n=" + n);
        ++n;
        ++i;
    }
    /**
     * 这个应该是最先加载 但是,加载不等于执行
     * 因为如果不加载此函数,静态变量是无法初始化的
     * @param str
     * @return
     */
    public static int print(String str) {
        System.out.println((++k) + ":" + str + " i=" + i + " n=" + n);
        ++i;
        return ++n;
    }
    
    public static void main(String[] args) {
        /**首先加载类,然后实例化:
         * 类加载过程:
         *         首先加载所有的静态方法,但不执行;
         *         然后按照静态变量的顺序开始初始化
         *         静态变量初始化完毕后执行静态构造块(不执行构造块)
         *         此时类加载完毕
         * 实例化过程:
         *         加载非静态方法
         *         初始化非静态变量
         *         执行构造代码块
         *         执行构造函数
         *         此时实例化完毕
         */
        StaticTest t = new StaticTest("init");
    }
 
}

2. 个人的理解:

首先,我们得理解 类什么时候加载,和 类什么时候初始化。


2.1 类什么时候加载

类的加载是通过类加载器(Classloader)完成的,它既可以是饿汉式[eagerly load](只要有其它类引用了它就加载)加载类,也可以是懒加载    [lazy load](等到类初始化发生的时候才加载)。不过我相信这跟不同的JVM实现有关。

然而他又是受JLS保证的(当有静态初始化需求的时候才被加载)。

2.1.2 类的加载顺序:

① 父类静态块--子类静态块--父类初始化块--父类构造方法--子类初始化块--子类构造方法

② 静态块---main()---初始化构造块-----构造方法(简化版)


2.2 类什么时候初始化

加载完类后,类的初始化就会发生,意味着它会初始化所有类静态成员,以下情况一个类被初始化:

  1. 实例通过使用 new()关键字创建 或者 使用class.forName()反射,但它有可能导致ClassNotFoundException。
  2. 类的静态方法被调用
  3. 类的静态域被赋值
  4. 静态域被访问,而且它不是常量
  5. 在顶层类中执行assert语句
  6. clone方式:clone只是复制拷贝(深拷贝需要new,浅拷贝不需要,原来的对象的改变不反应在拷贝对象上)
  7. 反序列化:readObject是从文件中还原对象

反射同样可以使类初始化,比如java.lang.reflect包下面的某些方法,JLS严格的说明:一个类不会被任何除以上之外的原因初始化。



2.3.1  类是如何被初始化的

现在我们知道什么时候触发类的初始化了,他精确地写在Java语言规范中。但了解清楚 域(fields,静态的还是非静态的)、块(block静态的还是非静态的)、不同类(子类和超类)和不同的接口(子接口,实现类和超接口)的初始化顺序也很重要类。事实上很多核心Java面试题和SCJP问题都是基于这些概念,下面是类初始化的一些规则:

  1. 类从顶至底的顺序初始化,所以声明在顶部的字段的早于底部的字段初始化
  2. 超类早于子类和衍生类的初始化
  3. 如果类的初始化是由于访问静态域而触发,那么只有声明静态域的类才被初始化,而不会触发超类的初始化或者子类的初始化即使静态域被子类或子接口或者它的实现类所引用。
  4. 接口初始化不会导致父接口的初始化。
  5. 静态域的初始化是在类的静态初始化期间,非静态域的初始化时在类的实例创建期间。这意味这静态域初始化在非静态域之前。
  6. 非静态域通过构造器初始化,子类在做任何初始化之前构造器会隐含地调用父类的构造器,他保证了非静态或实例变量(父类)初始化早于子类

2.3.2 类的初始化过程:

   初始化父类静态变量、静态代码块,

   初始化子类静态变量、静态代码块,

   初始化父类普通成员变量、代码块、父类构造方法,

   初始化子类普通成员变量、代码块、子类构造方法,

 

Java中对字段属性是静态绑定(编译出错),方法成员是动态绑定(运行出错)















相关标签: 类的加载