第25篇:Java 初始化块和静态初始化块详解,超详细(案例多、官方教程)

简介: 📜 私有静态方法初始化类变量的好处是:如果你需要重新为类变量进行初始化的时候,私有静态方法可以被重复使用

一、静态初始化块(官方教程)

官方教程地址

public class BedAndBreakfast { 
    // initialize to 10
    public static int capacity = 10;

    // initialize to false
    private boolean full = false;
}

✏️ As you have seen, you can often provide an initial value for a field in its declaration.
📜 正如上面你所看到的,你可以在属性声明的时候就给它提供一个初始值(Initial Value)


✏️ This works well when the initialization value is available and the initialization can be put on one line.
📜 当初始值可用或比较简单,以至于可以一行书写的时候,这样写是很好的。


✏️ However, this form of initialization has limitations because of its simplicity.
📜 然而,由于其简单性(simplicity),这种形式的初始化也有其局限性(limitation)


✏️ If initialization requires some logic (for example:error handling or a for loop to fill a complex array), simple assignment is inadequate.
📜 如果初始化需要一些逻辑处理(如:错误处理,或用 for 循环为复杂数组填充数据),那么简单的赋值是完全不够的。


✏️ Instance variables can be initialized in constructors, where error handling or other logic can be used. To provide the same capability for class variables, the Java programming language includes static initialization blocks.
📜 实例变量(Instance Variable)可以在构造方法中初始化属性,错误处理或其他逻辑也可以在构造方法中完成。为了给类变量(Class Variable)提供相同的功能,Java 语言提供了静态初始化块


public class BedAndBreakfast {
    public static int capacity = 10;

    /*
        whatever code is needed for initialization goes here
        初始化所需的任何代码都放在这里 
     */
    static {
        capacity = 12;
    }

    public static void main(String[] args) {
        // 12
        System.out.println(capacity);
    }
}

✏️ A static initialization block is a normal block of code enclosed in braces { } , and preceded by the static keyword.
📜 如上所示:静态初始化块就是一个普通的代码块,它包含在花括号 { } 中,花括号的前方有一个 static 关键字。


✏️ A class can have any number of static initialization blocks, and they can appear anywhere in the class body. The runtime system guarantees that static initialization blocks are called in the order that they appear in the source code.
📜 一个类中可以有任意数量的静态初始化块,静态初始化块可以出现在类体的任意位置。运行时系统会按照静态初始化块在源代码中出现的顺序挨个调用它们。


✏️ There is an alternative to static blocks — you can write a private static method.
📜 你也可以编写一个私有的静态方法来进行类变量的初始化,以此来替代静态初始化块【如下所示】

public class Whatever {
    public static Double money = initClassVar();

    /**
     * 该静态方法用于初始化某个类变量
     */
    private static Double initClassVar() {
        // initialization code goes here
        // 初始化代码放这儿
        return Math.pow(10, 5);
    }

    public static void main(String[] args) {
        // 100000.0
        System.out.println(money);
    }
}

✏️ The advantage of private static methods is that they can be reused later if you need to reinitialize the class variable.
📜 私有静态方法初始化类变量的好处是:如果你需要重新为类变量进行初始化的时候,私有静态方法可以被重复使用


二、初始化实例成员(官方教程)

✏️ Normally, you would put code to initialize an instance variable in a constructor. There are two alternatives to using a constructor to initialize instance variables: initializer blocks and final methods.
📜 为了初始化一个实例变量,通常情况下,你可能会把代码放在构造方法中。有两种可选的方式可用于在构造方法中初始化实例变量:初始化块和 final 方法。


✏️ Initializer blocks for instance variables look just like static initializer blocks, but without the static keyword.
📜 实例变量的初始化块看起来和静态初始化块有点一样,只是没有 static 关键字罢了。

public class Whatever {
    public int money = 666;

    {
        // 初始化实例变量的代码都放这儿
        money = 9999;
    }

    public static void main(String[] args) {
        // 9999
        System.out.println(new Whatever().money);
    }
}

✏️ The Java compiler copies initializer blocks into every constructor.
📜 Java 编译器会把初始化块(注意:是初始化块,不是静态初始化块)拷贝到每个构造方法中。
在这里插入图片描述


✏️ Therefore, this approach can be used to share a block of code between multiple constructors.
📜 因此,初始化块可以实现在多个构造方法之间共用代码块的功能。


✏️ A final method cannot be overridden(重写) in a subclass. Here is an example of using a final method for initializing an instance variable.
📜 被 final 关键字修饰的方法不能被子类重写。下面是一个用 final 方法初始化实例变量的例子。

public class Whatever {
    private double money = initInstanceVar();

    /* protected: 保证子类也可以重复使用该方法 */
    protected final double initInstanceVar() {
        // initialization code goes here
        // 初始化代码放这儿
        return Math.pow(Math.PI * 10, 3);
    }

    public static void main(String[] args) {
        // 31006.276680299816
        System.out.println(new Whatever().money);
        // 31006.276680299816
        System.out.println(new CuteSon().getSalary());
    }
}

class CuteSon extends Whatever {
    private double salary = initInstanceVar();

    public double getSalary() {
        return salary;
    }
}

✏️ This is especially useful if subclasses might want to reuse the initialization method.
📜 该方式非常有用,尤其是子类需要重复使用初始化方法的时候。


三、初始化块和静态初始化块

✒️ 初始化块:Initializer block
✒️ 静态初始化块:Static Initialization Block

✏️ 编译器会按照初始化块在源代码中的出现顺序把初始化块拷贝到每个构造方法的方法体的最前面(先执行初始化块中的代码,后执行构造器中的代码
✏️ 在源代码中越靠前的初始化块放在构造器方法体的越前面

public class Whatever {
    private double money = 9;
    private int age = 9;

    {
        money = 1;
        age = 3;
    }

    public static void main(String[] args) {
        // 6.0
        System.out.println(new Whatever().money);
        // 666
        System.out.println(new Whatever().age);
    }

    {
        money = 0;
    }

    private Whatever() {
        age = 666;
    }

    {
        money = 7;
        age = 2;
    }

    {
        money = 6;
    }
}

✏️ 初始化块会被拷贝到每个构造器方法体的最前面,所以每当创建一个对象,初始化块中的代码就会被执行一次

public class Whatever {
    // 记录 Whatever 对象的个数
    public static int instanceNum;

    {
        System.out.println("第" + ++instanceNum + "次调用初始化块");
    }

    public static void main(String[] args) {
        Whatever whatever1 = new Whatever();
        Whatever whatever2 = new Whatever();
        Whatever whatever3 = new Whatever();
        Whatever whatever4 = new Whatever();
        Whatever whatever5 = new Whatever();
        
        /*
            第1次调用初始化块
            第2次调用初始化块
            第3次调用初始化块
            第4次调用初始化块
            第5次调用初始化块
         */
    }
}

public class Whatever {
    private static double money = 9;
    private int age = 9;

    {
        System.out.println("2.Initializer 初始化块");
        money = 2;
        age = 888;
    }

    private Whatever() {
        System.out.println("3.Constructor 构造器");
    }

    static {
        System.out.println("1.Static 静态初始化块");
        money = 1;
        // ERROR: Non-static field cannot be referenced from a static context
        // 非静态变量不能再在静态(static)环境中被引用
        // age = 7;
    }

    public static void main(String[] args) {
        Whatever whatever = new Whatever();
        // 6.0
        System.out.println("4." + whatever.money);
        // 666
        System.out.println("5." + whatever.age);
    }
    
    /*
        1.Static 静态初始化块
        2.Initializer 初始化块
        3.Constructor 构造器
        4.2.0
        5.888
     */
}

📕 从上面的代码中可以知道:
✏️ 1. 静态初始化块只能对类变量做初始化操作;初始化块可以对实例变量和类变量做初始化操作
✏️ 2. 静态初始化块、初始化块、构造器三者的执行顺序是:① 静态初始化块;② 初始化块;③ 构造器【静态初始化块与静态初始化块之间按照在源代码中的出现顺序依次执行,初始化块和初始化块之间按照在源代码中的出现顺序依次执行】


✏️ 当类被加载的时候,静态初始化块会被调用静态初始化块中的代码只会被执行一次 (因为类加载只有一次)

❓ 类什么时候被加载 ❓
✒️ ① 创建本类对象的时候(new)
✒️ ② 使用本类的静态成员(静态变量、静态方法)的时候
✒️ ③ 创建子类对象或使用子类的静态成员

❓【调用】这个词一般用在方法上,但官方教程中对初始化块也使用了【调用】这个词:
在这里插入图片描述

类什么时候会被加载?

public class Whatever {
    private static final double PI = 3.14;
    private static String poem = "人生自古谁无死?";
    private int num;

    static {
        System.out.println("static{}: 静态初始化块在被类被加载的时候被调用一次(仅一次)");
    }

    {
        System.out.println("{}: 创建第 " + ++num + " 个 Whatever 对象");
    }

    public static void staticMethod() {
        System.out.println("call staticMethod()");
    }

    public static void main(String[] args) {
        /*
            static{}: 静态初始化块在被类被加载的时候被调用一次(仅一次)
            call staticMethod()
         */
        // staticMethod();


        /*
            static{}: 静态初始化块在被类被加载的时候被调用一次(仅一次)
            3.14
         */
        // System.out.println(PI);


        /*
            static{}: 静态初始化块在被类被加载的时候被调用一次(仅一次)
            人生自古谁无死?
         */
        // System.out.println(poem);


        /*
            static{}: 静态初始化块在被类被加载的时候被调用一次(仅一次)
            {}: 创建第 1 个 Whatever 对象
            HandsomeSon()
         */
        // new HandsomeSon();


        /*
            static{}: 静态初始化块在被类被加载的时候被调用一次(仅一次)
            大头儿子
         */
        // System.out.println(HandsomeSon.sonName);


        /*
            static{}: 静态初始化块在被类被加载的时候被调用一次(仅一次)
            调用了子类的静态方法
         */
        // System.out.println(HandsomeSon.sonStaticMethod());


        /*
            static{}: 静态初始化块在被类被加载的时候被调用一次(仅一次)
            {}: 创建第 1 个 Whatever 对象
         */
        new Whatever();
    }
}

class HandsomeSon extends Whatever {
    public static String sonName = "大头儿子";

    public HandsomeSon() {
        System.out.println("HandsomeSon()");
    }

    public static String sonStaticMethod() {
        return "调用了子类的静态方法";
    }
}

静态初始化块中的代码只会在类被加载的时候被执行一次(静态初始化块只会在类被加载的时候被调用一次)

public class Whatever {
    private static final double PI = 3.14;
    private static String poem = "人生自古谁无死?";
    private int num;

    static {
        System.out.println("static{}: 静态初始化块在被类被加载的时候被调用一次(仅一次)");
    }

    {
        System.out.println("{}: 创建第 " + ++num + " 个 Whatever 对象");
    }

    public static void staticMethod() {
        System.out.println("call staticMethod()");
    }

    public static void main(String[] args) {
        /*
            下面的代码都会致使类被加载 
            静态代码块只会在类被加载的时候被执行一次
         */
        staticMethod();
        System.out.println(PI);
        System.out.println(poem);
        new HandsomeSon();
        System.out.println(HandsomeSon.sonName);
        System.out.println(HandsomeSon.sonStaticMethod());
        new Whatever();
    }
    
    /*
        static{}: 静态初始化块在被类被加载的时候被调用一次(仅一次)
        call staticMethod()
        3.14
        人生自古谁无死?
        {}: 创建第 1 个 Whatever 对象
        HandsomeSon()
        大头儿子
        调用了子类的静态方法
        {}: 创建第 1 个 Whatever 对象
     */
}

class HandsomeSon extends Whatever {
    public static String sonName = "大头儿子";

    public HandsomeSon() {
        System.out.println("HandsomeSon()");
    }

    public static String sonStaticMethod() {
        return "调用了子类的静态方法";
    }
}

✏️ 初始化块会被编译器拷贝到每一个构造方法的最前部,随着构造方法被调用而执行【只是使用类的静态成员并不会导致初始化块中的代码被执行】

四、创建对象调用顺序

(1) 介绍本节标题

❓ 创建一个类的对象的时候,类中的静态初始化块、 静态变量初始化、初始化块、 实例变量初始化、构造器的调用顺序...

📕 ① 静态变量初始化:在本篇文章第一节介绍过,可以编写一个私有的静态方法来进行类变量的初始化,以此来替代静态初始化块
在这里插入图片描述

📕 ② 实例变量初始化:在本篇文章第二节介绍过,可以使用被 final 修饰的实例方法实现实例变量的初始化(如下图)
在这里插入图片描述


(2) 结论和测试

✏️ 调用静态初始化块和静态属性初始化(当有多个静态初始化块和多个静态变量初始化的时候,按照它们在源代码中的顺序依次调用)

public class Whatever {
    static {
        System.out.println("静态初始化块 Three");
    }

    // getCount() 是静态属性初始化
    private static double count = getCount();

    private static double getCount() {
        System.out.println("静态属性初始化_getCount()");
        return 1;
    }

    static {
        System.out.println("静态初始化块 One");
    }

    public static void main(String[] args) {
        System.out.println(count); // 3.14159 
        /*
            静态初始化块 Three
            静态属性初始化_getCount()
            静态初始化块 One
            静态初始化块 Two
            3.14159
         */
    }

    static {
        System.out.println("静态初始化块 Two");
        count = 3.14159;
    }
}
public class Whatever {
    private static double getCount1() {
        System.out.println("静态属性初始化_getCount()1");
        return 12580; // 【幺儿我帮你】
    }

    static {
        System.out.println("静态初始化块 Three");
    }


    static {
        System.out.println("静态初始化块 One");
    }

    public static void main(String[] args) {
        System.out.println(count); // 209420.0
        /*
            静态初始化块 Three
            静态初始化块 One
            静态属性初始化_getCount()1
            静态初始化块 Two
            静态属性初始化_getCount2()
            209420.0
         */
    }

    private static double getCount2() {
        System.out.println("静态属性初始化_getCount2()");
        return 209420; // 【爱你就是爱你】
    }

    // getCount() 是静态属性初始化
    private static double count = getCount1();

    static {
        System.out.println("静态初始化块 Two");
        count = 3.14159; // 【山巅一寺一壶酒】

        // 静态属性初始化
        count = getCount2();
    }
}
✏️ It is not necessary to declare fields at the beginning of the class definition, although this is the most common practice. It is only necessary that they be declared and initialized before they are used.
📜 在类定义的开头声明字段并不是必须的,它只是一种常见的做法而已。必须的是:在使用它们之前,必须声明和初始化。

✏️ 调用初始化块和实例变量初始化(当有多个初始化块和多个实例变量初始化的时候,按照它们在源代码中的顺序依次调用)

public class Whatever {

    public int getNum1() {
        System.out.println("getNum1()");
        return 6;
    }

    private int num = getNum1(); // getNum1 初始化块1 初始化块2 getNum2 getNum1

    {
        System.out.println("初始化块1");
        num = 2;
    }

    public static void main(String[] args) {
        // num = 528
        System.out.println("num = " + new Whatever().num);

        /*
            getNum1()
            初始化块1
            初始化块2
            getNum2()
            getNum1()
            num = 528
         */
    }

    {
        System.out.println("初始化块2");
        num = getNum2() + getNum1() + num; // 520 + 6 + 2
    }

    public int getNum2() {
        System.out.println("getNum2()");
        return 520;
    }
}

✏️ 调用构造方法

思考下面代码的打印结果:

public class Whatever {
    private int num;

    private int getNum1() {
        System.out.println("getNum1()");
        return 1;
    }

    static {
        System.out.println("静态初始化块1");
        brand = "小米";
    }

    private static String brand = getBrand();

    static {
        System.out.println("静态初始化块2");
        brand = "三星";
    }

    public Whatever() {
        System.out.println("public Whatever()");
    }

    {
        num = 2;
    }

    public static void main(String[] args) {
        /*
            静态初始化块1
            getBrand()
            静态初始化块2
            public Whatever()
            8
            三星
         */
        System.out.println(new Whatever().num);
        System.out.println(Whatever.brand);
    }

    private int getNum2() {
        return (int) Math.pow(num, 3);
    }

    private static String getBrand() {
        System.out.println("getBrand()");
        return "华为";
    }

    {
        num = getNum2();
    }
}

五、有继承关系的时候的调用顺序

✏️ 构造器方法体的最顶部隐含了 super() 和初始化块
✏️ 静态初始化块和静态属性初始化在类加载时执行完毕

class Apple {
    public Apple() {
        // (1) 隐含 super();
        // (2) 隐含初始化块
        System.out.println("public Apple() { }");
    }
}

思考下面代码的执行结果:

public class Whatever {
    public static int n = getN(); // n = 27

    public Whatever() {
        // 1.super()
        // 2.初始化块
        n = getN() + 3; // n = 30
        System.out.println("4.Whatever_n_" + n);
        System.out.println("5.public Whatever()");
    }

    private static int getN() {
        return (int) Math.pow(3, 3);
    }

    {
        System.out.println("3.愿万事顺心");
        cnt = 56;
    }

    static {
        System.out.println("1.n = getN() + n");
        n = getN() + n; // n = 27 + 27 = 54
    }

    private int cnt = 2;

    public static void main(String[] args) {
        new Apple();
    }
}

class Apple extends Whatever { // num = 5
    private static int num = 5;

    {
        System.out.println("6.Apple 初始化块");
    }

    private int count;

    public static int getNum(int n) {
        return n;
    }

    public Apple() {
        // 1.super()
        // 2.初始化块
        System.out.println("7." + (getCount() + num + count + n));   // 31 + 5 + 2 + 30 = 68
        System.out.println("8.public Apple()");
    }

    {
        count = 1;
    }

    static {
        System.out.println("2.getNum(6 + num)");
        getNum(6 + num);
    }

    public int getCount() {
        return n + count++;
    }
}
🥤 自己总结的过程:
父静态初始化块(静态属性初始化)
子静态初始化块(静态属性初始化)
父初始化块(实例属性初始化)
父构造器
子初始化块(实例属性初始化)
子构造器
父静 子静 父初 父构 子初 子构

在这里插入图片描述
🍀 父静、子静、父初、父构、子初、子构
🍀 【谐音】父进、子进、父出、父go、子出、子go
🍀 看着上面的图片,并想象一个场景:
✒️ 亲先入电梯 类的态初始化块和态属性初始化)
✒️ 孩入电梯 类的态初始化块和态属性初始化)
✒️ 亲先电梯 类的始化块和实例属性始化)
✒️ go类的造器)
✒️ 孩电梯类的始化块和实例属性始化)
✒️孩go类的造器)

六、Exercise

(1) 第一题

思考下面代码的打印结果:

public class Whatever {
    public static void main(String[] args) {
        /*
            1.getVal1()
            2.A 静态初始化块1
            3.getVal3()
            4.B 的静态初始化块1
            5.A 初始化块1
            6.getVal2()
            7.A 构造器
            8.getVal4()
            9.B 的初始化块1
            10.B 的构造器
         */
        new B();
    }
}

/**
 * 父类
 */
class A {
    private static int n1 = getVal1();

    static {
        System.out.println("2.A 静态初始化块1");
    }

    {
        System.out.println("5.A 初始化块1");
    }

    public int n3 = getVal2();

    public static int getVal1() {
        System.out.println("1.getVal1()");
        return 10;
    }

    public static int getVal2() {
        System.out.println("6.getVal2()");
        return 10;
    }

    public A() {
        System.out.println("7.A 构造器");
    }
}

/**
 * 子类
 */
class B extends A {
    private static int n3 = getVal3();

    static {
        System.out.println("4.B 的静态初始化块1");
    }

    public int n5 = getVal4();

    {
        System.out.println("9.B 的初始化块1");
    }

    public static int getVal3() {
        System.out.println("3.getVal3()");
        return 10;
    }

    public static int getVal4() {
        System.out.println("8.getVal4()");
        return 10;
    }

    public B() {
        // super();
        // 初始化块
        System.out.println("10.B 的构造器");
    }
}

(2) 第二题

看下面的代码,思考打印结果;

public class Whatever {
    public static void main(String[] args) { 
        // Static Initialization Block
        // total: 8989
        System.out.println("total: " + Money.total);

        // total: 8989
        System.out.println("total: " + Money.total);
    }
}

class Money {
    public static int total;

    static {
        total = 8989;
        System.out.println("Static Initialization Block");
    }
}
📕 使用类中的静态成员会导致类加载,进而静态初始化块中的代码会执行
📕 静态初始化块中的代码会在类加载的时候执行 一次

(3) 第三题

public class Whatever {
    public static void main(String[] args) {
        // 【output:】
        // Money(String): StaticMoney
        // Static Initialization Block
        // Money(String): Money
        // House()
        new House();
    }
}

class House {
    Money money1 = new Money("Money");
    static Money money2 = new Money("StaticMoney");

    static {
        System.out.println("Static Initialization Block");
        if (money2 == null) System.out.println("money is null");
    }

    House() {
        // super()
        // 初始化块
        System.out.println("House()");
    }
}

class Money {
    Money(String s) {
        System.out.println("Money(String): " + s);
    }

    Money() {
        System.out.println("Money()");
    }
}

再见!如有错误,请不吝赐教

相关文章
|
2天前
|
XML 算法 搜索推荐
Java 中文官方教程 2022 版(四十九)(4)
Java 中文官方教程 2022 版(四十九)
25 0
|
2天前
|
XML 自然语言处理 安全
Java 中文官方教程 2022 版(四十九)(3)
Java 中文官方教程 2022 版(四十九)
16 0
|
2天前
|
XML Java 编译器
Java 中文官方教程 2022 版(四十九)(2)
Java 中文官方教程 2022 版(四十九)
17 0
|
2天前
|
XML 网络协议 Java
Java 中文官方教程 2022 版(四十八)(3)
Java 中文官方教程 2022 版(四十八)
7 0
|
2天前
|
小程序 安全 Java
Java 中文官方教程 2022 版(四十七)(3)
Java 中文官方教程 2022 版(四十七)
8 0
|
2天前
|
安全 Java 编译器
Java 中文官方教程 2022 版(四十六)(2)
Java 中文官方教程 2022 版(四十六)
16 0
|
2天前
|
存储 安全 Java
Java 中文官方教程 2022 版(四十五)(4)
Java 中文官方教程 2022 版(四十五)
16 0
|
2天前
|
存储 Java 编译器
Java 中文官方教程 2022 版(四十四)(4)
Java 中文官方教程 2022 版(四十四)
6 0
|
2天前
|
安全 Java 编译器
Java 中文官方教程 2022 版(四十三)(3)
Java 中文官方教程 2022 版(四十三)
16 1
|
2天前
|
安全 数据可视化 Java
Java 中文官方教程 2022 版(四十三)(1)
Java 中文官方教程 2022 版(四十三)
25 1