最近在看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的加载原理,也欢迎各位有问题的小伙伴在此留言讨论~~