[疯狂Java]面向对象:初始化块、初始化代码、初始化顺序

时间:2021-02-06 12:00:43

1. 初始化块:

    1) 在类中可以定义和方法平行的代码块,例如:public class A { { /* 初始化块 */ } ... },即直接用花括号括起来的代码块;

    2) 这种代码块就是初始化代码块,用于给数据成员初始化,里面可以调用方法,使用if、for等Java语句,也就是说初始化块本质上和普通方法没有区别;

    3) 初始化块会在构造器之前执行,对数据成员初始化,但它跟构造器和其它普通方法有区别的地方是,初始化块不能传入参数!这是非常明显的一个特征;

         i. 这是个非常聪明的设计,如果需要对所有的对象全部进行完全相同的初始化处理那么初始化块就再合适不过了;

         ii. 因为初始化块不接受任何参数,这就意味着初始化块中的代码永远是固定的,任何对象的初始化结果都是相同的,初始化块就是为了满足这样的需求;

         iii. 初始化块是使编程更加简洁方便:

             a. 不难看出初始化块完成的工作就是无参构造器的任务,那为什么不直接使用无参构造器还要设计一个初始化块呢?会不会多此一举呢?

             b. 其实不是,考虑到如下情形:所有构造器都要先执行一段相同的无参初始化行为,为了防止代码冗余,在没有初始化块的情况下会把所有无参初始化行为都写在无参构造器中,然后在其它构造器的开始处调用这个无参构造器版本this();就行了;

!!但是现在有了初始化块,而初始化块都是在构造器之前执行的,因此现在就可以省略各个构造器开始的this()了;

!!跟何况有些构造器可能还想调用非无参版本的其它构造器,比如this(5, "lala");之类的,但是每个构造器最多只能用一次this调用其它构造器,对于这种情况就又麻烦了,可能需要将无参初始化行为写在另外一个单独的方法中,然后再调用它了!

!!所以初始化块是一个非常非常成功的设计;

              c. 有了初始化块就只要提供给一个空的无参构造器即可!虽然初始化块可以完全取代无参构造器,但是养成良好的习惯,还是要提供一个的!


2. 初始化代码:

    1) 还记得在定义数据成员的时候可以直接提供一个默认值吗?例如:private int m_data = 5;这种直接提供默认值其实也是一种初始化;

    2) 直接默认值和初始化块合并属于初始化代码,而构造器不属于初始化代码,构造器和初始化代码是两个平行的概念;

    3) 初始化代码的特性:

         i. 在构造器之前执行;

         ii. 初始化代码按照在源代码中的定义顺序依次执行(初始化块可以有多个),例如:

{ a = 6; }
private int a = 9;
private int b = 10;
{ a = 7; b = 11; }
!这个顺序很奇怪,初始化块有多个,并且初始化块可以放在数据成员定义之前(Java不像C++,需要前向引用声明,编译时会先识别所有的数据成员符号,然后再执行初始化代码);

!!实际初始化时会按照从上到下的顺序一条条执行,这里a先是6,再是9,最后再是7,因此最终结果是7,而b最终结果是11;

!!这里只是演示一下极端情况,为了说明初始化代码执行是按从上到下的顺序的,但实际编程中初始化代码只需要一个就行了,并且都是放在数据定义之后的,这样逻辑清晰并且美观;

    4) 初始化代码的编写规范:

         i. 具有显而易见的默认初始值的数据用直接默认值给出,例如刚创建的银行账号,余额必定是0,因此应该选择直接默认值初始化:private double account = 0.0;

         ii. 对于需要使用一定逻辑(可以是非常复杂的逻辑、算法,特别是通过一个方法调用产生一个初始值)计算出初始值的数据则应该放在初始化块中:例如用随机数产生一个初始值;

!!由于某些方法可能会抛出异常,如果用这类方法产生初始值那就只能放在初始化块了,因为初始化块中可以加入异常处理逻辑,而直接默认值初始化显然不能解决抛出异常的问题,所以直接默认值初始化一般都是直接给出一个常量,很少使用一个方法来给定直接默认值的!

         iii. 使用直接默认值初始化的数据就不要再在初始化块中重复初始化了,即两者不要重复,各自的分工是明确的!


3. 静态初始化块:

    1) 就是在普通初始化块之前加一个static,即static { /* 静态初始化代码 */ }

    2) 专门用来初始化静态数据成员的,而普通初始化块是用来初始化对象成员的;

    3) 同样,普通初始化块隐藏着一个this引用,所有的非局部变量默认使用this.引导,而静态初始化块没有隐藏的this引用,因此静态初始化块不能访问非静态成员!

    4) 静态初始化块和静态成员的直接默认初始化合并称为静态初始化代码,静态初始化代码会在类第一次加载时执行,会在所有对象创建之前执行,并且只执行一次;


4. 一个完整的初始化过程的伪代码描述:

static_init() { // 静态成员初始化
if (当前类有父类) {
当前类的父类.static_init();
}
if (!当前类加载过) {
执行当前类的初始化代码;
}
}

init() { // 非静态成员初始化
static_init(); // 静态初始化最先执行

if (this有父类) {
super.init();
}
执行当前对象this的初始化代码;
}
!!这是一个递归的描述,因此下面示例:

class A {
static { out.println("static A"); }
{ out.println("A"); }
public A() { out.println("cons A"); }
}

class B extends A {
static { out.println("static B"); }
{ out.println("B"); }
public B() { out.println("cons B"); }
}

class C extends B {
static { out.println("static C"); }
{ out.println("C"); }
public C() { out.println("cons C"); }
}

public class Test {
public static void main(String[] args) {
new C();
}
}
!!结果是:

static A
static B
static C
A
cons A
B
cons B
C
cons C
!!Java还是和其它语言保持一致的,即对一个对象初始化时一定要先保证其父类部分初始化再初始化自己的部分;