今天我总结了一下java中静态代码块 构造代码块 构造方法的执行顺序及其注意问题
首先要知道静态代码块是随着类的加载而加载,而构造代码块和构造方法都是随着对象的创建而加载
当时做了这么一个小案例(想必这个大多数学java的都做过,不知道是否都理解了)
class Fu{ static { System.out.println("Fu static code"); } { System.out.println("Fu code"); } public Fu(){ System.out.println("Fu GouZao"); } } class Zi extends Fu{ static { System.out.println("Zi static code"); } { System.out.println("Zi code"); } public Zi(){ System.out.println("Zi GouZao"); } } public class Text{ public static void main(String[] args) { Zi zi = new Zi(); } }
1,在编译Text.java时,先加载了Fu类,因此Fu类的静态代码块首先执行,而后加载Zi类,Zi类的静态代码块执行,这没什么好说的
2,然后创建Zi的对象,大家都知道构造代码块优先于构造方法执行,这时候问题来了,这时应该先看Zi类的构造方法,Zi类里的构造方法里有一句隐式的super()首先被执行,所以找到Fu类的构造方法,而Fu类的构造方法中也有一句隐式的super()执行(调用Object类的构造方法),并没有什么返回结果,接下来才是在执行Fu类构造方法的方法体前先执行了Fu类的构造代码块(Fu code),再执行Fu类构造方法的方法体(也就是Fu GouZao),最后又回到Zi类的构造方法中,这时Zi类的super()已经执行完了,在执行Zi类构造方法的方法体前先执行Zi类的构造代码块(Zi code),再执行Zi类构造方法的方法体(Zi GouZao)
最后的结果是:
Fu static code Zi static code Fu code Fu GouZao Zi code Zi GouZao
不知道大家绕晕了没有,我在这再总结一下:静态的是与类有关,肯定先加载,而构造代码块执行前要先看构造方法中是否有this()或super(),有的话在其之后执行,最后执行构造方法的方法体
题目如下,请写出程序运行结果
程序的运行结果这里不说了,这道题考察两个知识点:代码块(静态、非静态)、继承。
首先讲一下代码块:
代码块定义很简单,就是用{}包裹的一块代码,如果是在大括号外加static 则表示的是静态代码块。这里要提一下类的装载步骤。
在Java中,类装载器把一个类装入Java虚拟机中,要经过三个步骤来完成:装载、链接和初始化,其中链接又可以分成校验、准备和解析三步,除了解析外,其它步骤是严格按照顺序完成的,各个步骤的主要工作如下:
装载:查找和导入类或接口的二进制数据;
链接:执行下面的校验、准备和解析步骤,其中解析步骤是可以选择的;
校验:检查导入类或接口的二进制数据的正确性;
准备:给类的静态变量分配并初始化存储空间;
解析:将符号引用转成直接引用;
初始化:激活类的静态变量的初始化Java代码和静态Java代码块。
初始化类中属性是静态代码块的常用用途,但只能使用一次。
所以静态代码块的优先级要高于非静态代码块、构造方法(在一个类中非静态代码块优先级高于构造方法)。
其次讲一下继承:
继承是从已有的类中派生出新的类,新的类能吸收已有类的数据属性和行为,并能扩展新的能力。
在上面的程序中,子类(Child)继承了父类(Parent)并且对父类的out()方法进行了重写。
所以当对象p在调用out()的时候,用的是子类(Child)的out()方法。
如果子类(Child)没有对父类的out()方法进行了重写,那么对象p用的将是父类(Parent)的out()方法。
正如上面所说,因为p调用的是子类(Child)的out()方法,所以输出结果中i,j的值是Child类赋值的值。
因为p是父类(Parent)的实例对象,所以p中i,j的值一定是Parent中的i,j的值。
综上所诉:
对象的初始化顺序:首先执行父类静态的内容,然后去执行子类的静态的内容,然后再去看父类有没有非静态代码块,如果有就执行父类的非静态代码块,执行完毕后,接着执行父类的构造方法;父类的构造方法执行完毕之后,它接着去看子类有没有非静态代码块,如果有就执行子类的非静态代码块。执行完毕后再去执行子类的构造方法。总之一句话,静态代码块内容最先执行,当然父类的静态代码块一定先于子类的静态代码块执行,接着执行父类非静态代码块和构造方法,然后执行子类非静态代码块和构造方法。
注意:子类的构造方法,不管这个构造方法带不带参数,默认的它都会先去寻找父类的不带参数的构造方法。如果父类没有不带参数的构造方法,那么子类必须用supper关键子来调用父类带参数的构造方法,否则编译不能通过。
运行下面这段代码,观察其结果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
package
com.test;
public
class
HelloB
extends
HelloA {
public
HelloB() {
}
{
System.out.println(
"I'm B class"
);
}
static
{
System.out.println(
"static B"
);
}
public
static
void
main(String[] args) {
new
HelloB();
}
}
class
HelloA {
public
HelloA() {
}
{
System.out.println(
"I'm A class"
);
}
static
{
System.out.println(
"static A"
);
}
}
|
结果如下:
1
2
3
4
|
static
A
static
B
I'm A
class
I'm B
class
|
解析:
1.静态代码块:是在类的加载过程的第三步初始化的时候进行的,主要目的是给类变量赋予初始值。
2.构造代码块:是独立的,必须依附载体才能运行,Java会把构造代码块放到每种构造方法的前面,用于实例化一些共有的实例变量,减少代码量。
3.构造方法:用于实例化变量。
总结:
1是类级别的,2、3是实例级别的,所以1要优先2、3.
它们的执行顺序是1>2>3;