java初始化
成员初始化
java中的局部变量是必须要进行初始化,如果在没有进行初始化的前提下进行使用,会导致编译不通过,因为不会为局部变量赋予默认值.如下列情况,就会导致编译不通过.
void {
int i;
i++;
}
这种方式也是逼迫程序员提供一个默认值,否则可能会导致程序员遇到一些关于局部变量的Bug问题.
相反,编译器会为成员变量赋予默认值,这是与局部变量所不同的一个地方.成员变量的默认值是多少呢?
public class InitialValues {
boolean t;
char c;
byte b;
short s;
int i;
long l;
float f;
double d;
InitialValues reference;
void printInitialValues() {
System.out.println("Data type Initial value");
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);
System.out.println("reference " + reference);
}
public static void main(String[] args) {
new InitialValues().printInitialValues();
}
//上述程序的运行结果
Data type Initial value
boolean false
char[NUL]
byte 0
short 0
int 0
long 0
float 0.0
double 0.0
reference null
初始化的顺序
类中变量定义的顺序决定了初始化的顺序,虽然有一些变量定义分布在方法定义之间,但这些变量仍然会在类中的方法(包括构造方法)被调用之前得到初始化.如下面程序所示.
class Window {
Window(int marker) {
System.out.println("Window(" + marker + ")");
}
}
class House {
Window w1 = new Window(1); // Before constructor
House() {
// Show that we're in the constructor:
System.out.println("House()");
w3 = new Window(33); // Reinitialize w3
}
Window w2 = new Window(2); // After constructor
void f() {
System.out.println("f()");
}
Window w3 = new Window(3); // At end
}
public class OrderOfInitialization {
public static void main(String[] args) {
House h = new House();
h.f(); // Shows that construction is done
}
}
//执行结果
Window(1)
Window(2)
Window(3)
House()
Window(33)
f()
通过上述执行结果可以看出,在正式执行House类的构造方法前,分别依次按照前后顺序执行了Window的三条创建对象语句,所以再次证明成员变量一定是在构造方法方法之前就已经初始化完成了.
java类初始化
在java中,类的初始化会加载父类的变量和构造方法(如果该类有继承),子类的变量和构造方法.其具体的加载顺序如下.从上往下依次调用.
父类的静态变量
父类的静态代码块
子类静态变量
子类的静态代码块
父类非静态实例化变量
父类构造函数
子类非静态实例化变量
子类构造函数
下面举个例子进行说明.
//父类
class InitialParent {
public static String a = "abc";
public String b = "def";
//静态初始化块
static {
System.out.println("父类静态初始化块"+a);
}
//初始化块
{
System.out.println("父类初始化块"+b);
}
//构造方法
public InitialParent() {
System.out.println("父类构造器"+b);
}
}
//继承于InitialParent
public class Initial extends InitialParent{
public static String aa = "aabbcc";
public String bb = "ddeeff";
//静态初始化块
static {
//调用静态变量
System.out.println("子类静态初始化块"+aa);
}
//初始化块
{
System.out.println("子类初始化块"+bb);
}
//构造方法
public Initial() {
System.out.println("子类构造器"+bb);
}
public static void main(String[] args) {
new Initial();//新建一个实例
new Initial();//再次新建一个实例
}
}
//上述程序的运行结果是
父类静态初始化块abc
子类静态初始化块aabbcc
父类初始化块def
父类构造器def
子类初始化块ddeeff
子类构造器ddeeff
父类初始化块def
父类构造器def
子类初始化块ddeeff
子类构造器ddeeff
通过上述程序的运行结果可以看出,是按照前面所说的加载顺序执行的.但是通过上述结果会发现,当再次新建一个实例时,不调用父类和子类的静态变量和静态代码块了.原因是因为,这些无论是静态变量还是静态代码块均是属于类本身的,不属于某一个对象存在,所以只需要初始化一次就可以了.
其实,如果将上述new Initial()
均注释掉,会发现运行结果如下:
父类静态初始化块abc
子类静态初始化块aabbcc
所以从上述运行结果可以看出,静态变量和静态代码块的初始化与对象的创建没有关系,即使不创建对象,只要类被加载了,静态变量和静态代码块的执行语句就会执行,并且只会执行一次.
那么什么时候类会被加载呢?第一种情况就是当main方法被放在某一个类中时,当在执行该main方法之前就会加载包含该方法的类,还有一种情况就是,当创建某一个类的实例化对象时,也会加载该类.
静态数据的初始化
下面对静态数据的初始化过程进行说明.无论创建多少个对象,静态数据均只会占用一份存储区域,static关键字,不能应用于局部变量,所以只能作用于属性,即变量和方法.如果一个变量是静态的基本类型,但是没有对其进行初始化,这种情况下会获得基本类型的默认值,如果是一个对象引用,则就会获得默认对象初值null.
class Bowl {
Bowl(int marker) {
System.out.println("Bowl(" + marker + ")");
}
void f1(int marker) {
System.out.println("f1(" + marker + ")");
}
}
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);
}
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("main creating new Cupboard()");
new Cupboard();
System.out.println("main creating new Cupboard()");
new Cupboard();
table.f2(1);
cupboard.f3(1);
}
static Table table = new Table();
static Cupboard cupboard = new Cupboard();
}
//运行结果
Bowl(1)
Bowl(2)
Table()
f1(1)
Bowl(4)
Bowl(5)
Bowl(3)
Cupboard()
f1(2)
main creating new Cupboard()
Bowl(3)
Cupboard()
f1(2)
main creating new Cupboard()
Bowl(3)
Cupboard()
f1(2)
f2(1)
f3(1)
通过上述结果可以看出,首先加载了创建静态Table对象的语句,但是Table类中,包括两个静态Bowl对象的创建语句,所以会首先依次执行这两条语句,然后执行Table类的构造方法;
然后创建静态Cupboard对象,包括两个静态Bowl对象,一个Bowl非静态实例对象创建语句,所以会首先会依次执行创建静态对象的语句,接着会执行非静态实例对象的创建语句,最后才会执行Cupboard的构造方法;
最后才会执行main方法,在该方法中
- 先执行一次创建Cupboard对象的语句,由于在前面的过程中,已经加载过Cupboard类中的静态对象的创建语句,所以这里不会再次创建了,只会加载非静态实例化对象的创建语句
Bowl bowl3 = new Bowl(3);
,接着加载该类的构造方法. - 第二次执行创建Cupboard对象的语句,过程同上述一样.
- 执行
table.f2(1);
,这时候直接调用该方法即可. - 执行
cupboard.f3(1);
,同上.
综上可以看出,在执行main方法之前,首先要加载StaticInitialization类,则该类的静态对象就会被初始化,所以也会导致被创建静态对象的类也会被加载.
非静态实例初始化
实例化初始化,顾名思义,其并不像静态对象/变量一样,静态的属性属于一个类本身的,实例化的属性是属于每一个对象自己的,这才是面向对象啊,如果都属于类本身的,那谈何面向对象呢?看下面的例子.
// housekeeping/Mugs.java
// Instance initialization
class Mug {
Mug(int marker) {
System.out.println("Mug(" + marker + ")");
}
}
public class Mugs {
Mug mug1;
Mug mug2;
{ // [1]
System.out.println(mug1);
System.out.println(mug2);
mug1 = new Mug(1);
mug2 = new Mug(2);
System.out.println("mug1 & mug2 initialized");
}
Mugs() {
System.out.println("Mugs()");
}
Mugs(int i) {
System.out.println("Mugs(int)");
}
public static void main(String[] args) {
System.out.println("Inside main()");
new Mugs();
System.out.println("new Mugs() completed");
new Mugs(1);
System.out.println("new Mugs(1) completed");
}
}
//执行结果
Inside main()
null
null
Mug(1)
Mug(2)
mug1 & mug2 initialized
Mugs()
new Mugs() completed
null
null
Mug(1)
Mug(2)
mug1 & mug2 initialized
Mugs(int)
new Mugs(1) completed
通过上述结果反映出,不管创建一个对象还是创建两个对象,非静态实例域初始化过程在每一次对象创建过程中均会执行.此外,实力域的初始化过程是在构造方法执行之前就执行了,先是执行了非静态成员变量的创建并默认初始化,接着再执行初始化代码块中的语句.