Java中类的加载顺序

最近在看jvm的加载顺序,发现了几个比较有意思的点,对于初学者而言可能比较绕,所以拿出来再给大家理顺一下,废话不多说,直接上Demo:

 

Demo1:

 

public class Demo01 {

  public static void main(String[] args) {

A a=new B();

}

      

}

class A{

    public A(){          

        this(10);

        System.out.println(1);

    }

    public A(int a){

        System.out.println(2);

    }

    public  A(String str){

        System.out.println(3);  

    }

}

class  B extends A{

A a=new A();

    public B(){

        this(2); 

        System.out.println(6);

    }

    public B(int a){

        super(" "); 

        B b=new B(" ");  

        System.out.println(5);

    }

    public B(String string){ 

        System.out.println(4);

    }

}

最终的输出结果是多少?

    根据JVM的类的加载机制,大家都知道创建B类的实例之前要先加载A类的属性和方法(有些地方也说是加载A类的实例,但是这么说并不是太准确,这里暂且不讨论这个问题),那么什么时候加载B什么时候加载A呢?我们来一步一步分析。

    首先主方法中遇到了new关键字,那么要开始创建对象了,Demo1中new的是A的子类,也就是B类,那么首先要走B的构造方法(这里执行的构造并不是真正执行子类的构造,因为在子类中有一个super关键字来跳转到父类执行父类相关的构造方法,执行完父类中的构造方法之后,才会真正执行子类的构造方法),在B的构造方法中碰到了this关键字(this和super这两个关键字在java的构造方法中只会放在首行并且只能存在一个),那么就跳到了B(int a)这个构造方法里,在该构造方法里面碰到了super(""),之后又走到了A类的A(String str)中,此时输出一个3。

    走到这,一个A类也就是父类的构造方法已经走完,也就是一个super()已经走完了,那么此时,我们要注意的一点是,该真正的执行B的构造了,根据JVM的规则,那么在执行构造方法之前,要先初始化子类的属性,此时B类中有一个属性为new A(),也就是说要走A的无参构造,在A()中走了this(10),跳转到A(int a),这里输出一个2,在输出一个1。

    之后,继续走我们之前没有走完的代码,该执行的是B b=new B(“”)(此时大家先记住,这里又new了一个B类),走B(String str)构造方法,大家在这里可能会进坑,任务下一个输出的是4,但是这里是一个B类的构造方法,所以第一行有一个隐式调用super(),所以先走了A(),输出一个2,输出一个1,父类走完,初始化子类的属性,new A(),所以又走了一遍A(),输出一个2,输出一个1,之后才会真正的走B的构造,输出一个4,然后再走的话就走之前没有走完的代码,输出5,输出6。

整个流程是这样的:

public class Demo01 {

  public static void main(String[] args) {

A a=new B(); //--1

}

      

}

class A{

    public A(){     //---6 ----13 ----18         

        this(10); //---7 ----14 ----19

        System.out.println(1); //---9 输出1 --16输出1 --21输出1

    }

    public A(int a){

        System.out.println(2); //---8 输出2 --15 输出2 --20 输出2

    }

    public  A(String str){

        System.out.println(3);  //---4  输出3

    }

}

class  B extends A{

A a=new A(); //---5 -----17

    public B(){

        this(2); //---2

        System.out.println(6);//---24输出6

    }

    public B(int a){

        super(" ");  //---3

        B b=new B(" ");  //---10   

        System.out.println(5); //---23 输出5

    }

    public B(String string){ //---11

     //super();//---12

        System.out.println(4); //---22输出4

    }

}

 

所以,最终的输出结果是

3 2 1 2 1 2 1 4 5 6


    那我们现在理解了这个题,接下来可以继续去往深层次去探讨了,继续上Demo。

 

Demo2:

 

public class Demo02 {

public static void main(String[] args) {

new Son();

}

}

 

class Futher{

publicint i=2;

public Futher(){

disc();

}

public void disc(){

System.out.println(i);

}

}

 

class Son extends Futher{

private double i=3;

public Son(){

i=222;

}

public void disc(){

System.out.println(i);

}

}

    最终的输出结果是多少?

    

 

        乍一看和上个题差不多,其实这个题目的坑是很深的,跑过这段代码的小伙伴会发现最终结果可能和自己想的完全不一样,我们再来一步一步的分析一下。

    首先还是先new了一个子类Son(),走到子类的无参构造中,老方法,先走父类的构造,和上一题不同的是,这个父类中有一个属性int类型的i,走父类的构造之前,要先加载父类的属性,所以此时父类的i=2,之后执行父类的构造方法,调用disc(),因为子类中有一个disc()方法了,所以该方法被重写,那么调用子类的disc(),输出一个i。

    整个代码走完我们发现子类中的i这个i值好像始终没有被赋值,double类型的值加载之后的默认值是0.0,所以最终输出的值就是0.0。

 

    希望这两个Demo能帮助大家理解JVM的加载原理,也欢迎各位有问题的小伙伴在此留言讨论~~

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值