您的位置:首页 > 编程语言 > Java开发

java初始化

2016-01-11 11:04 381 查看
一、初始化顺序和类的加载

初始化顺序: 在类的内部,成员变量定义的先后顺序决定了初始化的顺序。即使变量定义散布于方法定义之间,各个变量仍旧会在任何方法(包括构造器)被调用之前得到初始化。初始化的顺序是先静态对象,然后再是“非静态”对象。静态初始化只在Class对象首次加载的时候进行一次。

首先如果有静态变量和静态代码则先初始化父类静态变量、父类静态代码块、子类静态变量、子类静态代码。然后如果有创建对象,则按照执行顺序父类非静态变量、父类非静态代码块、父类构造函数、子类非静态变量、子类非静态代码、子类构造函数等依次进行初始化操作。

类的初始化过程与所继承的类有关系。了解包括继承在内的初始化全过程,以对所发生的一切有个全局性的把握,是很有益的。

代码样例来自于《java编程思想》。

public class Insect {
private int i =9;
protected int j;
Insect(){
System.out.println("i = "+i+" j = "+j);
j = 39;
}
private static int x1 = printInit("Static Insect.x1 initialized");
static int printInit(String s){
System.out.println(s);
return 47;
}
}
public class Beetle extends Insect{
private int k = printInit("Beetle.k initialized");
public Beetle(){
System.out.println("k = "+k);
System.out.println("j = "+j);
}
private static int x2 = printInit("Static Insect.x2 initialized");

public static void main(String[] args){
System.out.println("Beetle constructor");
Beetle b =  new Beetle();
}
}


运行结果如下所示:



分析:

在java中代码开始运行之前,首先进行加载过程,加载的时候首先加载它的基类,类加载的时候就会首先运行它包含的static部分,所以我们在代码中先看到了Static Insect.x1initialized

Static Insect.x2 initialized

的输出,接着开始运行main函数得到了Beetle constructor的输出,顺着main函数运行到了新建得到Beetle对象b,调用这个类的构造函数,又由于在类的构造函数内部会相应的调用其父类中的构造函数,所以我们先看到了基类中i = 9 j = 0的输出。然后再得到Beetle的对象,首先初始化得到k的值,所以会看到Beetle.k initialized的输出,接着就输出了Beetle构造函数中的输出。k = 47 j = 39。

二、java初始化

Java尽力保证:所有变量在使用前都能得到恰当的初始化。

2.1 方法的局部变量

对于方法的局部变量,java以编译时错误的而形式来贯彻这种保证。所以,如果在某个方法内写成如下所示就会报出变量未初始化的错误。

int n;

n++;//会报错,n未初始化

2.2 类的成员变量

但是如果是类的成员变量是基本类型,该成员就会被自动得到一个初始化的值。如

class Book{

int i; //类的成员变量,没有初始化

boolean checkedOut = false;

Book(boolean checkOut){

i++;//使用时不出错,因为在定义的时候得到一个默认的初始化

checkedOut = checkOut;

}

void checkIn(){

i++;//使用时不出错,因为在定义的时候得到一个默认的初始化

checkedOut = false;

}

2.3 调用构造器时的顺序

构造器的调用顺序是很重要的。

1)调用基类构造器:这个步骤会不断反复递归下去,首先是构造这种层次结构的根,然后是下一层导出类,等等,直到最底层的导出类;(即得到该基类的一个对象)。即依次得到基类的各个对象。初始化的时候分配给对象的存储空间初始化为二进制的0.

2)按声明顺序调用成员的初始化方法;

3)调用导出类构造器的主体。

具体例子如下所示:

package com.sse.zk.chapter8;

/**@author zk
* @date 20160104
* 验证构造器的调用顺序
*/
class Meal{
Meal(){
System.out.println("Meal()");
}
}

class Bread{
Bread(){
System.out.println("Bread()");
}
}
class Cheese{
Cheese(){
System.out.println("Cheese()");
}
}
class Lettuce{
Lettuce(){
System.out.println("Lettuce()");
}
}
class Lunch extends Meal{
private int i = f();
public int f(){
System.out.println("初始化i的方法");
return 1;
}
Lunch(){
System.out.println("Lunch()");
}
}
class PortableLunch extends Lunch{
public void draw(){
}
PortableLunch(){
System.out.println("PortableLunch()  ");
draw();
System.out.println();
}
}

public class Sandwich extends PortableLunch{

private Bread b = new Bread();
private Cheese c = new Cheese();
private Lettuce l = new Lettuce();
private int radis = 5;
public Sandwich(){
System.out.println("Sandwich()   ");
draw();
System.out.println();
}
public void draw(){
System.out.print("Sandwich radis = "+radis);
}
public static void main(String[] args){
Sandwich s = new Sandwich();
}
}
运行结果如下:



由代码可知是在main方法中声明Sandwich对象s,那么在执行的时候会依次封装基类对象,封装的顺序根据继承的先后顺序为主,则依次得到每个基类对象,如

,最后生成Sandwich对象,根据5.3介绍得初始化顺序依次初始化该对象内的各个变量的值



初始化的时候分配给对象的存储空间初始化为二进制的0,所以当PortableLunch类中的draw方法被Sandwich覆盖后,在PortableLunch类构造器中调用draw方法输出radis的值为0.

总之,java程序初始化工作可以在许多不同的代码块中来完成(例如静态代码块、构造函数等),它们执行的顺序如下:父类静态变量、父类静态代码块、子类静态变量、子类静态代码、父类非静态变量、父类非静态代码块、父类构造函数、子类非静态变量、子类非静态代码、子类构造函数。其中父类静态变量、父类静态代码块、子类静态变量、子类静态代码等四个静态部分只在类加载时执行一次。

即首先如果有静态变量和静态代码则先初始化父类静态变量、父类静态代码块、子类静态变量、子类静态代码。然后如果有创建对象,则按照执行顺序父类非静态变量、父类非静态代码块、父类构造函数、子类非静态变量、子类非静态代码、子类构造函数等依次进行初始化操作。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: