构造器初始化在运行时刻可以调用方法或执行某些动作来确定初值,这为编程带来了更大的灵活性。
但无法阻止自动初始化的过程,他将在构造器被调用之前发生,例如int类型的成员变量会先初始化为0。
public class F_01{
int x; //0
F_01(){
x = 5; //5
}
}
x首先会被置0
(对于所有基本类型和对象引用,包括定义是已经指定初值的变量,这种情况都成立。),然后由构造器赋5.
由于这是默认的,所以接下来我们着重讨论其他情况
小结:编译器不会强制我们一定要在构造器的某个地方或者在使用之前对变量进行初始化-因为初始化早已经得到保证。
在类的内部,变量定义的先后顺序决定了初始化的顺序,并且它们会在构造器和其他普通方法之前被调用得到初始化
。
class Tyre{//轮胎类
Tyre(int num){
System.out.println("tyre("+num+")");
}
}
class Car{//车类
Tyre t1 = new Tyre(1);
Car(){
System.out.println("car()");
t3 = new Tyre(13); //从这里可以看出t3进行了两次初始化
}
Tyre t2 = new Tyre(2);
void f(){
System.out.println("f()");
}
Tyre t3 = new Tyre(3);
}
public class F_06 { //主函数
public static void main(String[] args) {
Car car = new Car();
car.f();
}
}
执行结果:
tyre(1)
tyre(2)
tyre(3)
car()
tyre(13)
f()
在Car类中,故意把Tyre对象的初始化定义分散,以证明他们仍会在构造器和其他方法之前得到初始化。
并且由输出可知,t3这个引用会被初始化两次,一次在构造器之前,一次在调用期间(第一次引用的对象将被丢弃,并作为垃圾回收)
由上面验证了结论: 变量 > 块(同级情况下,比如 静态变量 >静态代码块)
以上是一般情况,现在我们来看看引入特殊情况——静态数据之后,初始化顺序会发生怎样的变化呢?
无论创建多少个对象,静态数据只占一份存储区域。下面的代码能帮助我们了解静态存储区域是何时初始化的
class Bowl{
Bowl(int maker){
System.out.println("Bowl("+maker+")");
}
void f1(int maker){
System.out.println("f1("+maker+")");
}
}
class Table{
static Bowl b1 = new Bowl(1); //虽然是静态的代码,但是如果注释掉了[1]处的代码,那么Table不会被虚拟机加载进内存
Table(){
System.out.println("Table()");
b2.f1(1);
}
void f2(int maker){
System.out.println("f2("+maker+")");
}
static Bowl b2 = new Bowl(1);
}
class Cupboard{
Bowl b3 = new Bowl(3);
static Bowl b4 = new Bowl(4);
Cupboard(){
System.out.println("Cupboard()");
b4.f1(2);
}
void f3(int maker){
System.out.println("f3("+maker+")");
}
static Bowl b5 =new Bowl(5);
}
public class F_06 {
public static void main(String[] args) {
System.out.println("Creating new Cupboard in main");
new Cupboard();
System.out.println("Creating new Cupboard in main");
new Cupboard();
table.f2(1);
cupboard.f3(1);
}
static Table table = new Table(); //[1]
static Cupboard cupboard = new Cupboard();
}
/**
Bowl(1)
Bowl(1)
Table()
f1(1)
Bowl(4)
Bowl(5)
Bowl(3)
Cupboard()
f1(2)
Creating new Cupboard in main
Bowl(3)
Cupboard()
f1(2)
Creating new Cupboard in main
Bowl(3)
Cupboard()
f1(2)
f2(1)
f3(1)
*/
上面的例子中,Bowl类使得类的创建变得透明化,而在Table类和Cupboard类中加入了Bowl类型的静态数据成员。
注意,在静态数据成员定义前,Cupboard()类先定义了一个Bowl类型的非静态数据成员b3.
静态初始化只有在必要的时刻进行,如果不创建Table对象,也不引用Table b1和Table b2,那么静态的Bowl b1和b2都不会被创建。且静态初始化只执行唯一的一次。即 注释掉 [1]处的代码,那么,Table整体都不会被虚拟机加载
由上面验证了结论: 静态初始化 > 任何对象自身初始化 (但只会初始化一次)
现在我们来看看最后一部分
继承是Java语言的三大特性之一,但是继承并不只是复制父类的接口,当创建出一个子类的对象时,该对象包括一个来自父类的子对象。这个对象和我们直接创建父类对象是一样的。二者的区别在于后者来自于外部,而前者被包装在子类的对象内部。
Java会自动在子类构造器中调用父类构造器
父类初始化 > 子类初始化
1、将分配给对象的存储空间初始化成默认值
2、初始化父类静态变量(域)/静态代码块(变量和代码块按照声明顺序执行初始化)
3、初始化子类静态变量(域)/静态代码块(变量和代码块按照声明顺序执行初始化)
4、初始化父类变量(域)/代码块(变量和代码块按照声明顺序执行初始化)
5、初始化父类构造器
6、初始化子类变量(域)/代码块(变量和代码块按照声明顺序执行初始化)
7、初始化子类构造器
注意:静态变量(域)/静态代码块的初始化,发生在第一次new
这个类的对象时,或者第一次访问该类的静态变量(域)/静态方法。只初始化一次!如果访问的是编译时静态常量,是不会初始化的!
Java初始化顺序