第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()");
    }
}

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

相关文章
|
14天前
|
安全 Java 编译器
Kotlin教程笔记(27) -Kotlin 与 Java 共存(二)
Kotlin教程笔记(27) -Kotlin 与 Java 共存(二)
|
15天前
|
jenkins Java 测试技术
如何使用 Jenkins 自动发布 Java 代码,通过一个电商公司后端服务的实际案例详细说明
本文介绍了如何使用 Jenkins 自动发布 Java 代码,通过一个电商公司后端服务的实际案例,详细说明了从 Jenkins 安装配置到自动构建、测试和部署的全流程。文中还提供了一个 Jenkinsfile 示例,并分享了实践经验,强调了版本控制、自动化测试等关键点的重要性。
48 3
|
14天前
|
Java 开发工具 Android开发
Kotlin教程笔记(26) -Kotlin 与 Java 共存(一)
Kotlin教程笔记(26) -Kotlin 与 Java 共存(一)
|
14天前
|
Java 编译器 Android开发
Kotlin教程笔记(28) -Kotlin 与 Java 混编
Kotlin教程笔记(28) -Kotlin 与 Java 混编
|
17天前
|
存储 Java 关系型数据库
在Java开发中,数据库连接是应用与数据交互的关键环节。本文通过案例分析,深入探讨Java连接池的原理与最佳实践
在Java开发中,数据库连接是应用与数据交互的关键环节。本文通过案例分析,深入探讨Java连接池的原理与最佳实践,包括连接创建、分配、复用和释放等操作,并通过电商应用实例展示了如何选择合适的连接池库(如HikariCP)和配置参数,实现高效、稳定的数据库连接管理。
34 2
|
18天前
|
Java 关系型数据库 数据库
面向对象设计原则在Java中的实现与案例分析
【10月更文挑战第25天】本文通过Java语言的具体实现和案例分析,详细介绍了面向对象设计的五大核心原则:单一职责原则、开闭原则、里氏替换原则、接口隔离原则和依赖倒置原则。这些原则帮助开发者构建更加灵活、可维护和可扩展的系统,不仅适用于Java,也适用于其他面向对象编程语言。
12 2
|
23天前
|
安全 Java
Java多线程通信新解:本文通过生产者-消费者模型案例,深入解析wait()、notify()、notifyAll()方法的实用技巧
【10月更文挑战第20天】Java多线程通信新解:本文通过生产者-消费者模型案例,深入解析wait()、notify()、notifyAll()方法的实用技巧,包括避免在循环外调用wait()、优先使用notifyAll()、确保线程安全及处理InterruptedException等,帮助读者更好地掌握这些方法的应用。
15 1
|
23天前
|
JSON Java Maven
实现Java Spring Boot FCM推送教程
本指南介绍了如何在Spring Boot项目中集成Firebase云消息服务(FCM),包括创建项目、添加依赖、配置服务账户密钥、编写推送服务类以及发送消息等步骤,帮助开发者快速实现推送通知功能。
61 2
|
8天前
|
安全 Java 测试技术
Java并行流陷阱:为什么指定线程池可能是个坏主意
本文探讨了Java并行流的使用陷阱,尤其是指定线程池的问题。文章分析了并行流的设计思想,指出了指定线程池的弊端,并提供了使用CompletableFuture等替代方案。同时,介绍了Parallel Collector库在处理阻塞任务时的优势和特点。
|
5天前
|
安全 Java 开发者
深入解读JAVA多线程:wait()、notify()、notifyAll()的奥秘
在Java多线程编程中,`wait()`、`notify()`和`notifyAll()`方法是实现线程间通信和同步的关键机制。这些方法定义在`java.lang.Object`类中,每个Java对象都可以作为线程间通信的媒介。本文将详细解析这三个方法的使用方法和最佳实践,帮助开发者更高效地进行多线程编程。 示例代码展示了如何在同步方法中使用这些方法,确保线程安全和高效的通信。
25 9