1、Java中类的加载:
每个类的编译代码都存在于他自己的独立文件中,该文件只有在需要使用程序代码的时候才会被加载。
i)何时会触发类加载?
类的代码在初次使用时才加载。
a)发生于创建类的第一个对象;
b)当访问static域或static方法时;
ii)加载类会发生的动作是什么?
所有的static对象和static代码段都会在加载时依程序中的顺序(即,定义类时的书写顺序)而依次初始化,静态初始化只在Class对象首次加载的时候儿进行一次。
2、对象(没有继承关系,即:没有基类的对象(除Object))的初始化(以名为Dog的类为例)
a)首次创建类型为Dog的对象(即使没有显式地使用static关键字,构造器上也是静态方法)时,或者Dog类的静态方法/静态域首次被访问时,Java解释器必须查找类路径,以定位Dog.class文件;
b)然后载入Dog.class,有关静态初始化的所有动作都会执行;
c)当使用new Dog()创建对象时,首先在堆上为Dog对象分配足够的存储空间;
d)这块儿存储空间会被清零(设为二进制零值),即将Dog对象中的所有基本类型数据都设置为了默认值(数字型和Charater为0,boolean为false),引用则被设置为null;
e)执行所有出现于字段定义处的初始化动作(与有继承关系的类不同,由于没有基类则不需要去调用基类的构造器,相当于到达递归的出口);
f)执行构造器的正文部分。
3、继承与初始化
了解包括继承在内的初始化全过程。结合程序示例来进行说明。
例程:
import static net.mindview.util.Print.*;
class Insect {
private int i = 9;
protected int j;
Insect() {
print("i = " + i + ", j = " + j);
j = 39;
}
private static int x1 =
printInit("static Insect.x1 initialized");
static int printInit(String s) {
print(s);
return 47;
}
}
public class Beetle extends Insect {
private int k = printInit("Beetle.k initialized");
public Beetle() {
print("k = " + k);
print("j = " + j);
}
private static int x2 =
printInit("static Beetle.x2 initialized");
public static void main(String[] args) {
print("Beetle constructor");
Beetle b = new Beetle();
}
}
/* Output:
static Insect.x1 initialized
static Beetle.x2 initialized
Beetle constructor
i = 9, j = 0
Beetle.k initialized
k = 47
j = 39
*///:~
1)试图访问Beetle.main()(一个static方法),于是加载器开始启动并找出Beetle类的编译代码(在名为Beetle.class的文件中);
2)在加载Beetle.class的过程中,编译器发现他有一个基类Insert,于是继续继续加载Insert.class(如果Insert类还有基类,那么第二个基类还会被继续加载,如此递归类推,直至根基类);
本例中:在加载Beetle.class的过程中注意到,Beetle继承了Insert且Insert为根基类(即:出口),所以继续加载Insert.class。
3)加载递归到了根基类,到达了递归的出口–根基类中的static初始化(本例为Insert)即会被执行,然后是下一个导出类的static初始化。(为什么时递归的形式?因为导出类的static初始化可能会依赖于基类成员能否被正确初始化);
本例中:
先对根基类Insert.class的static初始化,即:private static int x1 = printInit(“static Insect.x1 initialized”)。然后是对导出类Beetle的static初始化,即:private static int x2=printInit(“static Beetle.x2 initialized”);
4)至此必要的类已经加载完毕(具备了生成对象的条件);
本例中:具备了生成对象的条件后没有立即生成对象,而是执行main函数的print(“Beetle constructor”)代码(即:此时的main函数还没有进行对象的生成,先执行这部分生成对象之前的代码);
5)开始创建导出类对象,首先在堆中给对象分配足够的空间,并且将这部分对象空间设为二进制零值(即:基本数据类型设为默认值,对象引用设为null);
本例中:new Beetle()则在堆上为Beetle对象创建足够的空间,并将Beetle的k域初始化为0,即:private int k=0。
6)基类的构造器会被调用(不论你创不创建改基类对象,都会调用。由于基类的一个子对象会嵌入到导出类,可以在导出类中显式的调用基类的构造器,如果不显式的调用,编译器会默认加上)。如果基类还有基类,依次递归(递归从第6步到7步)直到递归出口(根基类为出口),到达出后口后执行第8步;
本例中:
调用基类的构造器Insert(),且该类为根基类即为递归出口。在堆空间为Insert对象分配足够的存储空间;并且清零,即:private int i = 0;执行构载器中的剩余部分类容,即;print(“i = ” + i + “, j = ” + j);j = 39;
7)基类构造器完成之后,实例变量安其次序被初始化。
本例中:Insert基类构造完毕之后,给Beetle对象的实例变量初始化,即:k = printInit(“Beetle.k initialized”);
8)构造器的其余部分被执行。
本例中:
执行构造器中剩余代码:print(“k = ” + k);print(“j = ” + j);
4、具有组合、继承及多态的复杂类的构建顺序(例子的初始化过程可参照3的例子)
a)调用基类构造器。这个步骤会不断的反复递归下去,首先是构造这种层次结构的根,然后是下一导出类,
直到最低层的导出类;
b)按照声明顺序调用成员的初始化方法(例如:private int i = 1;);
c)调用导出类的构造器主题;
例程
//: polymorphism/Sandwich.java
// Order of constructor calls.
package polymorphism;
import static net.mindview.util.Print.*;
class Meal {
Meal() { print("Meal()"); }
}
class Bread {
Bread() { print("Bread()"); }
}
class Cheese {
Cheese() { print("Cheese()"); }
}
class Lettuce {
Lettuce() { print("Lettuce()"); }
}
class Lunch extends Meal {
Lunch() { print("Lunch()"); }
}
class PortableLunch extends Lunch {
PortableLunch() { print("PortableLunch()");}
}
public class Sandwich extends PortableLunch {
private Bread b = new Bread();
private Cheese c = new Cheese();
private Lettuce l = new Lettuce();
public Sandwich() { print("Sandwich()"); }
public static void main(String[] args) {
new Sandwich();
}
}
/* Output:
Meal()
Lunch()
PortableLunch()
Bread()
Cheese()
Lettuce()
Sandwich()
*///:~
步骤:
1)new Sandwich(),编译器执行面触发加载器去加载Sandwich.class;
2)编译器注意到Sandwich有基类PortableLunch,继续加载PortableLunch.class;PortableLunch有基类Lunch,继续加载Lunch.class;Lunch有基类Meal,继续加载Meal.class,至此Meal为根基类,到达递归出口;
3)应该可以进行静态部分的初始化,但是本例中没有静态成员或者静态块,所以不进行;
4)依次调用基类的构造器,递归出口为根基类器。所以构造器调用顺序为:Meal->Lunch->PortableLunch;
5)然后进行对象中非Static域的初始化,执行所有出现于字段定义处的初始化动作。即完成这些动作:b = new Bread(); c = new Cheese();e l = new Lettuce();
6)之后执行导出类构造器中剩余部分,即:print(“Sandwich()”)。