Java里有局部变量、实例变量、静态变量,它们的初始化表现也不尽相同。下面来总结一下。
局部变量
void f() {
int i;
i++; // Error -- i not initialized
}
局部变量应该是最简单的情景,在一个方法内定义一个变量,如果不为它赋予初始值,编译器则会报错。所以声明局部变量时必须同时赋予其初始值。
实例变量
每一个实例变量都会保证有一个初始值。
public class InitialiValues {
boolean t;
char c;
byte b;
short s;
int i;
long l;
float f;
double d;
InitialiValues initialization;
void printInitialValues() {
System.out.println("boolean " + t);
System.out.println("char " + c + "");
System.out.println("byte " + b);
System.out.println("short " + s);
System.out.println("int " + i);
System.out.println("long " + l);
System.out.println("float " + f);
System.out.println("double " + d);
}
public static void main(String[] args) {
new InitialiValues().printInitialValues();
}
}
/* Output */
boolean false
char null
byte 0
short 0
int 0
long 0
float 0.0
double 0.0
reference null
即使不指定初始值,实例变量会自动拥有一个默认初始值
构造器初始化
public class Counter {
int i;
public Counter() {
System.out.println("before: " + i);
i = 7;
System.out.println("after: " + i);
}
public static void main(String[] args) {
new Counter();
}
}
//Output:
before: 0
after: 7
我们可以利用构造器来进行初始化,但是在构造器进行初始化之前,counter已经自动初始化了一次。
初始化顺序
在一个类中,成员变量的初始化顺序是由变量在类中定义的顺序决定的,而且总会在类中的任何方法被调用前就已经初始化好了。
public class Window {
Window(int marker) {
System.out.println("Window(" + marker + ")");
}
}
public class House {
Window w1 = new Window(1);
House() {
System.out.println("House()");
w3 = new Window(33);
}
Window w2 = new Window(2);
void f() {
System.out.println("f()");
}
Window w3 = new Window(3);
}
public static void main(String[] args) {
House h = new House();
h.f();
}
//Output:
Window(1)
Window(2)
Window(3)
House()
Window(33)
f()
可以看到,虽然Window对象的定义被打散了,但确实是等到所有的Windown变量初始化完成后,才执行构造器。
继承关系下的初始化顺序
在上面的基础上加上一个子类继承关系后,情况又会怎样?
public class SonHouse extends House{
Window sonWindow = new Window(4);
SonHouse() {
sonWindow = new Window(44);
}
}
}
public static void main(String[] args) {
House h = new SonHouse();
h.f();
}
//Output:
Window(1)
Window(2)
Window(3)
House()
Window(33)
Window(4)
Window(44)
f()
事实证明,孝顺是一种优良品质,先让父类初始化完成后,再轮到子类
static变量的初始化时机
public class Bowl {
Bowl(int marker) {
System.out.println("Bowl(" + marker + ")");
}
void f1(int marker) {
System.out.println("f1(" + marker + ")");
}
}
public class Table {
static Bowl bowl1 = new Bowl(1);
Table() {
System.out.println("Table()");
bowl2.f1(1);
}
void f2(int marker) {
System.out.println("f2(" + marker + ")");
}
static Bowl bowl2 = new Bowl(2);
}
public class Cupboard {
Bowl bowl3 = new Bowl(3);
static Bowl bowl4 = new Bowl(4);
Cupboard() {
System.out.println("Cupboard");
bowl4.f1(2);
}
void f3(int marker) {
System.out.println("f3(" + marker + ")");
}
static Bowl bowl5 = new Bowl(5);
}
public class StaticInitialization {
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(2);
}
static Table table = new Table();
static Cupboard cupboard = new Cupboard();
}
//Output:
Bowl(1)
Bowl(2)
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(2)
可以看出,static变量总是最先初始化。要注意的是,static变量只会初始化一次,而且只会在第一次需要用到的时候才进行
初始化。然后才到 non-static变量初始化
就像上述示例,要执行main(),StaticInitialization类就需要被加载,然后其static变量 table和cupboad这个时候
同样需要被加载上,又因为它们都有static Bowl 变量,所以这是Bowl类也同时被加载。所以这些类在执行main方法前都已经
加载好了。
其实,学习过JVM话就会知道,一个class只有在第一次需要引用的时候才会被加载,以后需要用到这个class就直接从classloader
获取不需要加载了。而 static变量的初始化就是在class第一次被加载到classloader的时候发生的。所以不难理解为什么static变量
只会初始化一次。
总结
对于实例变量,按照声明的顺序逐个初始化,先父类,后子类
static 成员在类第一次被加载时进行初始化,并且只初始化这么一次