静态代码块、静态变量,构造代码块、实例变量的执行顺序和继承逻辑(上)

简介: 静态代码块、静态变量,构造代码块、实例变量的执行顺序和继承逻辑(上)

前言


各位小伙伴大家好,我是A哥。如果问:Java的三大特性是什么?你顺口就能答出:封装、继承、多态。如果继续问:你真的了解Java中的继承吗?


或许你本来很懂,但被我这么一问就有点怀疑了。那么,就看看本文吧,保证你会有收获,能让你更好的理解Java中的继承机制。


继承案例


case1:父类和子类有同名同类型的属性时


public class Main {
    public static void main(String[] args) {
        // 使用多态
        Parent chidParent = new Child();
        System.out.println("Parent:" + chidParent.getAge()); //40
        System.out.println("Parent:" + chidParent.age); //18 这个结果你能接受吗?哈哈
        // 直接使用原本类型
        Child child = new Child();
        System.out.println("Child:" + child.getAge()); //40
        System.out.println("Child:" + child.age); //40
    }
}
@Getter
@Setter
class Child extends Parent {
    public Integer age = 40;
}
@Getter
@Setter
class Parent {
    public Integer age = 18;
}


输出结果:


Parent:40
Parent:18
Child:40
Child:40

我相信和最初的我一样,对Parent:18这个结果大吃一惊,what?其实这就是Java的继承机制,对此说明如下:


属性属于实例自己的,所以Parent的age属性值是18,这就解释通了

属性不存在覆盖(即使同名),而方法是实实在在的覆盖(复写)。所以你调用getAge()方法返回的100%是40


case2:父类和子类有同名但不同类型的属性时

结论同上。


case3:下面代码输出什么?

public class Main {
    public static void main(String[] args) {
        new Child();
    }
}
@Getter
class Child extends Parent {
    static {
        System.out.println("Child的静态块");
    }
    {
        System.out.println("Child的构造块");
    }
    Child() {
        System.out.println("Child的构造方法");
    }
}
@Getter
class Parent {
    Integer age = 18;
    static {
        System.out.println("Parent的静态块");
    }
    {
        System.out.println("Parent的构造块");
    }
    Parent() {
        System.out.println("Parent的构造方法");
    }
}


结果输出:


Parent的静态块
Child的静态块
Parent的构造块
Parent的构造方法
Child的构造块
Child的构造方法


Tips:构造代码块优先于构造方法执行,且优先于属性初始化之前执行

@PostConstruct是对象的属性都初始化ok了之后才去执行的(注意你new的话,@PostConstruct方法是不会执行的,他是Spring给与的支持哦~)


值得注意的是,此处子类没有显示调用super(),但父类的构造还是执行了的。但是,但是,但是,如果构造快为有参构造,请记得显示调用super方法,否则父类是不能被初始化的。如果子类的构造器没有显示地调用超类的构造器,则将自动调用超类默认(没有参数) 的构造器。如果超类没有不带参数的构造器,并且在子类的构造器又没有显式地调用超类的其他构造器,则 java 编译器将报告错误~



变种面试题


public class StaticTest {
    public static void main(String[] args) {
        staticFunction();
    }
    // 静态变量(有实例化的过程,这就是本题的重点)
    static StaticTest st = new StaticTest();
    static {
        //System.out.println(b); // 编译报错:因为b在构造代码块后边,此处不能引用。因此Java代码是从上到下的顺序
        System.out.println("1");
    }
    {
        System.out.println("2");
    }
    StaticTest() {
        System.out.println("3");
        System.out.println("a=" + a + ",b=" + b);
    }
    public static void staticFunction() {
        System.out.println("4");
    }
    // 这两个变量写在最后面
    int a = 110;
    static int b = 112;
}


输出


2
3
a=110,b=0
1
4


答案五花八门,真正能答对这道题的小伙伴少之又少。从结果中,这里先给你扔个结论:


1.先初始化静态变量,也就是执行new StaticTest(),从而打印:2


2.再执行构造函数,打印:3和a=110,b=0

      为何a=110,而b却为0呢?


        1. 执行构造函数之前,必须初始化实例属性,所以a=110

        2. 静态变量从上到下初始化,而st变量还没初始化完呢,所以b此时值为0


3.执行紧跟着的静态代码块。打印:1


4.执行静态方法staticFunction,打印:4


从该结果你应该能知道:static变量可不是100%一定在实例变量之前被赋值(初始化哦~),比如本例的b就在a之后初始化了


原因:


类的生命周期是:加载->验证->准备->解析->初始化->使用->卸载。


只有在准备阶段和初始化阶段才会涉及类变量的初始化和赋值,因此只针对这两个阶段进行分析;


类的准备阶段:需要做是为类变量(static变量)分配内存并设置默认值(注意此处都是先给默认值),因此类变量st为null、b为0;


需要注意的是,如果类变量是final的,编译时javac就会为它赋上值。因此上面如果我们这样写static final int b=112它哪怕在准备阶段,值就应该是112了


类的初始化阶段:需要做的是执行类构造器(请注意:这里不是指的构造函数)。


类构造器:编译器收集所有静态语句块和类变量的赋值语句,按语句在源码中的**顺序(请注意这三者是有序的)**合并生成类构造器


因此现在执行:st = new StaticTest().此时我们发现,就会进行对象的初始化了(看到没,这个时候b变量的赋值语句还没有执行哦~~~)


而对象初始化的顺序为:成员变量 -> 普通代码块 -> 构造函数,因此这一波过后:a=110了。

输出为:

2
3
a=110,b=0


需要注意的是,此时b仍然为0,并没有被赋值哦~


到此st = new StaticTest()这句就执行结束了。继续执行类构造器,显然就会执行static语句块了~~~输出1,最后调用静态方法,就输出4了 完美~


冷知识


通过结果看,有点颠覆我们之前的认知。其实这是一个冷知识:


它的关键在于:static StaticTest st = new StaticTest()这句代码,内嵌的这个变量恰好是个静态成员,而且是本类的实例 这就导致了这个有趣的现象:“实例初始化竟然出现在静态初始化之前”。


这里面我只做一小步变化:


static StaticTest st = new StaticTest()
改成
StaticTest st = new StaticTest()
或者改成:
static Object st = new Object();


最终输出结果就为(符合我们常识了吧,啊哈哈哈哈):

1
4



相关文章
|
存储 Cloud Native Linux
C++ 继承下的构造函数和析构函数执行顺序
C++ 继承下的构造函数和析构函数执行顺序
|
6天前
|
JavaScript 前端开发
静态方法和类的实例方法的执行顺序是怎样的?
静态方法和实例方法的执行顺序取决于具体的调用逻辑和代码结构,理解它们之间的执行顺序有助于更好地组织和编写面向对象的 JavaScript 代码,确保程序的逻辑正确和清晰。
34 10
实例详解局部代码块,构造代码块,静态代码块
实例详解局部代码块,构造代码块,静态代码块
|
XML Java 数据格式
Java中静态代码块、构造方法、代码块、父类与子类之间执行顺序及父类子类实例化对象
Java中静态代码块、构造方法、代码块、父类与子类之间执行顺序及父类子类实例化对象
166 0
Java中类的初始化过程:(静态成员变量,静态代码块,普通成员变量,代码块初始化顺序)
Java中类的初始化过程:(静态成员变量,静态代码块,普通成员变量,代码块初始化顺序)
131 0
Zp
父类静态代码块、非静态代码块、构造方法、子类静态代码块、子类非静态代码块、子类构造方法执行顺序
父类静态代码块、非静态代码块、构造方法、子类静态代码块、子类非静态代码块、子类构造方法执行顺序
Zp
73 0
|
Java
父类 子类 的 静态域,main(),构造代码块,构造方法 如何执行????
父类 子类 的 静态域,main(),构造代码块,构造方法 如何执行????
78 0
|
Java
构造方法,静态代码块,成员变量的加载顺序
构造方法,静态代码块,成员变量的加载顺序
143 0
|
架构师 Java Spring
静态代码块、静态变量,构造代码块、实例变量的执行顺序和继承逻辑(下)
静态代码块、静态变量,构造代码块、实例变量的执行顺序和继承逻辑(下)
静态代码块、静态变量,构造代码块、实例变量的执行顺序和继承逻辑(下)
类、变量、块、构造器、继承初始化顺序,终极解答。
最近发现微信群里面有些群友在讨论类的初始化顺序,如类的静态变量、成员变量、静态代码块、非静态代码块、构造器,及继承父类时,它们的初始化顺序都是怎样的,下面我通过例子来说明这个情况,以免被人误导。
类、变量、块、构造器、继承初始化顺序,终极解答。