java初始化

一、初始化顺序和类的加载

初始化顺序: 在类的内部,成员变量定义的先后顺序决定了初始化的顺序。即使变量定义散布于方法定义之间,各个变量仍旧会在任何方法(包括构造器)被调用之前得到初始化。初始化的顺序是先静态对象,然后再是“非静态”对象。静态初始化只在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程序初始化工作可以在许多不同的代码块中来完成(例如静态代码块、构造函数等),它们执行的顺序如下:父类静态变量、父类静态代码块、子类静态变量、子类静态代码、父类非静态变量、父类非静态代码块、父类构造函数、子类非静态变量、子类非静态代码、子类构造函数。其中父类静态变量、父类静态代码块、子类静态变量、子类静态代码等四个静态部分只在类加载时执行一次。

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




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值