Java架构师必经之路:类和对象的加载过程原理(上篇)

1.类加载的结论和代码演示

顺序:

  1. 父类静态属性(可以是对象)和静态代码块,看其在类中的先后顺序

  2. 子类静态属性和静态代码块 ,看其在类中的先后顺序

  3. 父类非静态属性和非静态代码块 ,看其在类中的先后顺序

  4. 父类构造方法

  5. 子类非静态属性和非静态代码块 ,看其在类中的先后顺序

  6. 子类构造方法

  7. 难点:与前面的过程分开讲解,类中的静态属性是自己,这个时候应该如何加载呢?即如下的第一行代码:

class Root {
    public static Root s = new Root();
    
    static {
        System.out.println("Root static block");
    }

    {
        System.out.println("Root no static block");
    }

    public Root() {
        System.out.println("Root no parameter");
    }
}

public class LeafTest {
    public static void main(String[] args) {
        new Root();
    }
}

这里先给出结论:当类加载到第一行代码时:

public static Root s = new Root();

因为此时正在执行类加载的过程,内存中已经开始加载Root这个类了,这时会认为已经加载过一次Root类了,这个时候先中断类的加载,开始对象的加载,对象加载完成之后,就会恢复类的加载,继续开始完成未完成的类加载。

1.1 第1-6条结论演示,第七点结论是重难点,单独举例

静态的随类的加载而加载,有且只会加载一次,其他的属于对象的,随对象的加载而加载,可加载多次。

咱们先看比较简单的1-6条的例子,先弄清楚简单的,在来看稍微复杂一点的第七条。

先来个超级简单的,如下:

class StaticAttribute {

    static {
        System.out.println("StaticAttribute static Block");
    }

    {
        System.out.println("StaticAttribute Block");
    }

    StaticAttribute(String name) {
        System.out.println(name + "StaticAttribute");
    }
}

class Attribute {
    static {
        System.out.println("Attribute static Block");
    }

    {
        System.out.println("Attribute Block");
    }

    Attribute(String name) {
        System.out.println(name + "Attribute");
    }
}

class Root {
    public static StaticAttribute s;
    public Attribute s1;

    static {
        System.out.println("Root static block");
    }

    {
        System.out.println("Root no static block");
    }

    public Root() {
        super();
        System.out.println("Root no parameter");
    }
}

class Sub extends Root {
    static {
        System.out.println("Sub static block");
    }

    {
        System.out.println("Sub no static block");
    }

    public Sub() {
        super();
        System.out.println("Sub no parameter");
    }

    public Sub(String msg) {
        this();
        System.out.println("Sub constructor:" + msg);
    }
}

public class LeafTest {
    public static void main(String[] args) {
        new Sub();
        System.out.println();
        new Sub();
    }
}

输出结果如下:

Root static block
Sub static block
Root no static block
Root no parameter
Sub no static block
Sub no parameter

Root no static block
Root no parameter
Sub no static block
Sub no parameter

Process finished with exit code 0

可以看出,静态代码块内的语句由父类到子类最先执行,且只执行一次,这是因为静态代码块随类的加载而加载;
之后每创建一次对象的时候,由父及子,先执父类中的普通代码块,再执行父类中的构造器,然后在执行子类中的普通代码块,再执行子类中的构造器。

以上是没有对类的属性和静态属性初始化赋值,这些都好说,下面我们来看加载类属性和静态属性的例子,在Root类中加上两行代码,如下,其他代码没有任何变化:

请添加图片描述

输出结果如下:

StaticAttribute static Block
StaticAttribute Block
YuShiwenStaticAttribute
Root static block
Sub static block
Attribute static Block
Attribute Block
YuShiwenAttribute
Root no static block
Root no parameter
Sub no static block
Sub no parameter

Attribute Block
YuShiwenAttribute
Root no static block
Root no parameter
Sub no static block
Sub no parameter

Process finished with exit code 0

我们从主方法开始,先new Sub()

new Sub();

这个时候会找到Sub类,找到其父类,从父类开始加载,Sub类继承了Root类,所以先加载Root类的静态属性:

  1. 加载Root类,静态属性和代码块随着类的加载而加载
public static StaticAttribute s = new StaticAttribute("YuShiwen");

先加载静态属性,new StaticAttribute(“YuShiwen”);这个时候就要加载StaticAttribute类了,加载过程如输出结果的前三行所示,加载了其类StaticAttribute的静态代码块、代码块、构造方法。

然后在继续加载Root类的静态代码块(静态的安装在类中的先后顺序加载),输出第四行

  1. 然后加载Root的子类Sub,静态属性和代码块随着类的加载而加载

    由于只有一个静态代码块,所以就只加载一个,输出第五行

  2. 然后是new 对象,所以再回到父类中加载非静态的属性、代码块(属性和代码块按照再类中写的先后顺序加载),最后再加载父类的构造方法。

    ​ 3.1. 加载非静态属性:

public Attribute s1 = new Attribute("YuShiwen");

new了一个Attribute类,这个时候就要加载Attribute类了,因为是第一次用到Attribute类,所以先加载Attribute类,静态的代码块、属性随着类的加载而加载,输出第六行;

然后new对象,加载代码块、属性,输出第七行;
最后在执行构造方法,输出第八行。

​ 3.2. 加载非静态代码块:输出第九行;

​ 3.3.最后执行构造方法:输出第十行。

  1. new 对象过程,加载Root的子类,先加载非静态的代码块和方法,只有代码块,输出第十一行;然后最后再执行构造方法,输出第十二行。

回单Main方法中继续执行

System.out.println();

换行之后,我们又new了一个对象,这个时候重复3和4过程就行,因为类只需要加载一次,静态属性和代码块是随类的加载而加载的,所以不需要 1,2过程了,并且再3.1中再次new Attribute的时候,如下:

public Attribute s1 = new Attribute("YuShiwen");

我们不在需要加载Attribute类了,因为已经加载过一次了。

1.2 第7条结论演示

当我们把上述Root类中的第一行代码,

public static StaticAttribute s = new StaticAttribute("YuShiwen");

从new 其他类作为自己的静态类属性,改为new 自己作为自己的静态类属性,即如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8cdpdMXS-1652403594287)(类加载顺序.assets/企业微信截图_16523380567059.png)]

我new 我自己,而且是作为类的静态属性,这个时候会肿么样加载呢,我们来看下运行结果:

Attribute static Block
Attribute Block
YuShiwenAttribute
Root no static block
Root no parameter
Root static block
Sub static block
Attribute Block
YuShiwenAttribute
Root no static block
Root no parameter
Sub no static block
Sub no parameter

Attribute Block
YuShiwenAttribute
Root no static block
Root no parameter
Sub no static block
Sub no parameter

Process finished with exit code 0

分析:

我们还是从主方法开始,先new Sub()

new Sub();

这个时候会找到Sub类,找到其父类,从父类开始加载,其父类为Root类,加载Root类,静态属性和代码块随着类的加载而加载,此时Root类中第一行代码为

public static Root s = new Root();

我new我自己,我这才刚开始加载我自己呢,还没加载完Root类,怎么现在要创建一个静态的Root对象呢?

现有要咋办呢,再次加载Root类吗,但是Root类中已经存在了内存中了,类只会加载一次,肯定是不会再次加载了的。

但是我现在在加载Root类的过程中,遇到了new Root对象,我Root类还没加载完呢,这个时候怎么处理呢?

重点:

这个时候会认为Root类已经加载到内存中去了(实际还在加载的过程中,还没加载完),认为已经加载过一次类了,所以会把这次当作第二次new 对象,所以会去加载非静态的代码块,非静态属性,最后加载构造方法,这个new 对象的过程加载完成了,就会继续加载类的静态属性、静态代码块。

所以就会在输出结果中出现第1、2、3行先加载了Attribute类,因为先去加载了类Root的非静态属性,即先执行了public static Root s = new Root();代码后面的public Attribute s1 = new Attribute(“YuShiwen”);让非静态属性先加载了;

然后在按照在类中的顺序加载Root类中的非静态代码块,输出了第4行;

最后执行Root的构造方法,输出第5行。

接着回来继续加载Root类的,静态代码块和静态方法随着类的加载而加载,所以按照在类中的先后顺序,加载完静态属性后,接着加载了静态代码块,输出第6行。接下来的内容与章节1.1中的内容相似,笔者这里就不再进行赘述了。

好了,到这里咋们上篇文章就暂且结束了,大家先记住这个加载顺序的结论,下篇文章咋们讲为什么要这么加载,在JVM和内存中究竟干了哪些事。

我是喜欢分享知识、喜欢写博客的YuShiwen,与大家一起学习,共同成长!咋们下篇博文见。


上篇已完结,下篇待续
于CSDN
2022.05.13
author:YuShiwen
评论 28
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

MrYuShiwen

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值