天天看点

Java:初始化类、变量、程序块加载探讨

<b>1.</b><b>基本类型数据的初始值</b>

InitialValues.java

<b>public</b> <b>class</b> InitialValues {

    <b>boolean</b> t;

    <b>char</b> c;

    <b>byte</b> b;

    <b>short</b> s;

    <b>int</b> i;

    <b>long</b> l;

    <b>float</b> f;

    <b>double</b> d;

    <b>void</b> print(String s) {

       System.out.println(s);

    }

    <b>void</b> printInitialValues() {

       print("boolean  " + t);

       print("char  " + c);

       print("byte  " + b);

       print("short  " + s);

       print("int  " + i);

       print("long  " + l);

       print("float  " + f);

       print("double  " + d);

    <b>public</b> <b>static</b> <b>void</b> main(String[] args) {

       InitialValues iv = <b>new</b> InitialValues();

       iv.printInitialValues();

}

结果:

boolean  false

char  _

byte  0

short  0

int  0

long  0

float  0.0

double  0.0

<b>2.</b><b>变量初始化</b>

在类的内部,变量定义的先后顺序决定了初始化的顺序。即使变量定义散布于方法定义之间,它们仍旧在任何方法(包括构造器)被调用之前得到初始化。看下面的代码:

OrderOfInitialzation.java(执行顺序在代码中已标出,按类标注,罗马字母标注主类中执行顺序。)

<b>class</b> Tag {

    Tag(<b>int</b> marker) {

       System.out.println("Tag(" + marker + ")");

<b>class</b> Card {

    Tag t1 = <b>new</b> Tag(1);// Ⅰ①

    Card() {

       System.out.println("Card()");// Ⅰ④

       t3 = <b>new</b> Tag(33);// Ⅰ⑤

    Tag t2 = <b>new</b> Tag(2);// Ⅰ②

    <b>void</b> f() {

       System.out.println("f()");// Ⅱ⑥

    Tag t3 = <b>new</b> Tag(3);// Ⅰ③

<b>public</b> <b>class</b> OrderOfInitialzation {

       Card t = <b>new</b> Card();// Ⅰ

       t.f();// Ⅱ

Tag(1)

Tag(2)

Tag(3)

Card()

Tag(33)

f()

<b>3.</b><b>静态数据初始化</b>

看下面的代码:

StaticInitialization .java

<b>class</b> Bowl {

    Bowl(<b>int</b> marker) {

       System.out.println("Bowl(" + marker + ")");

    <b>void</b> f(<b>int</b> marker) {

       System.out.println("f(" + marker + ")");

<b>class</b> Table {

    <b>static</b> Bowl b1 = <b>new</b> Bowl(1);// Ⅰ①

    Table() {

       System.out.println("Table()");// Ⅰ③

       b2.f(1);// Ⅰ④

    <b>void</b> f2(<b>int</b> marker) {

       System.out.println("f2(" + marker + ")");

    <b>static</b> Bowl b2 = <b>new</b> Bowl(2);// Ⅰ②

<b>class</b> Cupboard {

    Bowl b3 = <b>new</b> Bowl(3);// Ⅱ③,Ⅳ①,Ⅵ①

    <b>static</b> Bowl b4 = <b>new</b> Bowl(4);// Ⅱ①

    Cupboard() {

       System.out.println("Cupboard");// Ⅱ④,Ⅳ②,Ⅵ②

       b4.f(2);// Ⅱ⑤,Ⅳ③,Ⅵ③

    <b>void</b> f3(<b>int</b> marker) {

       System.out.println("f3(" + marker + ")");

    <b>static</b> Bowl b5 = <b>new</b> Bowl(5);// Ⅱ②

<b>public</b> <b>class</b> StaticInitialization {

       System.out.println("Creating new Cupboard() in main");// Ⅲ

       <b>new</b> Cupboard();// Ⅳ

       System.out.println("Creating new Cupboard() in main");// Ⅴ

       <b>new</b> Cupboard();// Ⅵ

       t2.f2(1);// Ⅶ

       t3.f3(1);// Ⅷ

    <b>static</b> Table t2 = <b>new</b> Table();// Ⅰ

    <b>static</b> Cupboard t3 = <b>new</b> Cupboard();// Ⅱ

Bowl(1)

Bowl(2)

Table()

f(1)

Bowl(4)

Bowl(5)

Bowl(3)

Cupboard

f(2)

Creating new Cupboard() in main

f2(1)

f3(1)

由输出可见,静态初始化只有在必要时刻才会进行。如果不创建Table 对象,也不引用Table.b1或Table.b2,那么静态的Bowl b1 和b2 永远都不会被创建。只有在第一个Table 对象被创建(或者第一次访问静态数据)的时候,它们才会被初始化。此后,静态对象不会再次被初始化。

初始化的顺序是先“静态”,(如果它们尚未因前面的对象创建过程而被初始化),后“非静态”。从输出结果中可以观察到这一点。

<b>4.</b><b>静态块的初始化</b>

Java 允许你将多个静态初始化动作组织成一个特殊的“静态子句”(有时也叫作“静态块”)。与其他静态初始化动作一样,这段代码仅执行一次:当你首次生成这个类的一个对象时,或者首次访问属于那个类的一个静态成员时(即便从未生成过那个类的对象)。看下面的代码:

<b>class</b> Cup {

    Cup(<b>int</b> marker) {

       System.out.println("Cup(" + marker + ")");

<b>class</b> Cups {

    <b>static</b> Cup c1;

    <b>static</b> Cup c2;

    <b>static</b> {

       c1 = <b>new</b> Cup(1);

       c2 = <b>new</b> Cup(2);

    Cups() {

       System.out.println("Cups()");

<b>public</b> <b>class</b> ExpilicitStatic {

       System.out.println("Inside main()");

       Cups.c1.f(99);// (1)

    // static Cups x=new Cups();//(2)

    // static Cups y=new Cups();//(2)

Inside main()

Cup(1)

Cup(2)

f(99)

无论是通过标为(1)的那行程序访问静态的 c1对象,还是把(1)注释掉,让它去运行标为(2) 的那行,Cups 的静态初始化动作都会得到执行。如果把(1)和(2)同时注释掉,Cups 的静态初始化动作就不会进行。此外,激活一行还是两行(2)代码都无关紧要,静态初始化动作只进行一次。

<b>5.</b><b>非静态实例初始化</b>

<b>class</b> Mug {

    Mug(<b>int</b> marker) {

       System.out.println("Mug(" + marker + ")");

<b>public</b> <b>class</b> Mugs {

    Mug c1;

    Mug c2;

    {

       c1 = <b>new</b> Mug(1);

       c2 = <b>new</b> Mug(2);

       System.out.println("c1&amp;c2 initialized");

    Mugs() {

       System.out.println("Mugs()");

       <b>new</b> Mugs();

       System.out.println("===new Mugs again===");

Mug(1)

Mug(2)

c1&amp;c2 initialized

Mugs()

===new Mugs again===

从结果可以看到,非静态的代码块被执行了2次。所以只要实例化一个类,该类中的非静态代码块就会被执行一次。

<b>6.</b><b>数组初始化</b>

注意区分基本类型数据与类数据的初始化。看以下代码:

<b>int</b>[] a; 

a = <b>new</b> <b>int</b>[rand.nextInt(20)];

Integer[] a = <b>new</b> Integer[rand.nextInt(20)]; 

<b>for</b>(<b>int</b> i = 0; i &lt; a.length; i++) { 

     a[i] = <b>new</b> Integer(rand.nextInt(500)); 

对于类数据类型的初始化,每个数组子成员都要重新new一下。

<b>7.</b><b>涉及继承关系的初始化</b>

当创建一个导出类的对象时,该对象包含了一个基类的子对象。看下面代码:

<b>class</b> Art {

    Art() {

       System.out.println("Art constructor");

<b>class</b> Drawing <b>extends</b> Art {

    Drawing() {

       System.out.println("Drawing constructor");

<b>public</b> <b>class</b> Cartoon <b>extends</b> Drawing {

    <b>public</b> Cartoon() {

       System.out.println("Cartoon constructor");

       <b>new</b> Cartoon();

Art constructor

Drawing constructor

Cartoon constructor

可以发现,构建过程是从基类“向外”扩散的,所以基类在导出类构造器可以访问它之前,就已经完成了初始化。

如果类没有缺省的参数,或者想调用一个带参数的基类构造器,就必须用关键字super显示地调用基类构造器的语句,并且配以适当的参数列表。看下面代码:

<b>class</b> Game {

    Game(<b>int</b> i) {

       System.out.println("Game constructor");

<b>class</b> BoardGame <b>extends</b> Game {

    BoardGame(<b>int</b> i) {

       <b>super</b>(i);

       System.out.println("BoardGame constructor");

<b>public</b> <b>class</b> Chess <b>extends</b> BoardGame {

    Chess() {

       <b>super</b>(11);

       System.out.println("Chess constructor");

       <b>new</b> Chess();

Game constructor

BoardGame constructor

Chess constructor

如果不在BoardGame()中调用基类构造器,编译器将无法找到符合Game()形式的构造器。而且,调用基类构造器必须是你在导出类构造器中要做的第一件事。

<b>8.</b><b>构造器与多态</b>

8.1构造器的调用顺序

基类的构造器总是在导出类的构造过程中被调用的,而且按照继承层次逐渐向上链接,以使每个基类的构造器都能得到调用。这样做是有意义的,因为构造器具有一项特殊的任务:检查对象是否被正确地构造。导出类只能访问它自己的成员,不能访问基类中的成员(基类成员通常是private类型)。只有基类的构造器才具有恰当的知识和权限来对自己的元素进行初始化。因此,必须令所有构造器都得到调用,否则就不可能正确构造完整对象。这正是编译器为什么要强制每个导出类部分都必须调用构造器的原因。

8.2构造器内部的多态方法的行为

看下面代码:

<b>abstract</b> <b>class</b> Glyph {

    <b>abstract</b> <b>void</b> draw();

    Glyph() {

       System.out.println("Glyph() before draw()");

       draw();

       System.out.println("Glyph() after draw()");

<b>class</b> RoundGlyph <b>extends</b> Glyph {

    <b>private</b> <b>int</b> radius = 1;

    RoundGlyph(<b>int</b> r) {

       radius = r;

       System.out.println("RoundGlyph.RoundGlyph(),radius=" + radius);

    <b>void</b> draw() {

       System.out.println("RoundGlyph.draw(),radius=" + radius);

<b>public</b> <b>class</b> PolyConstructors {

       <b>new</b> RoundGlyph(5);

Glyph() before draw()

RoundGlyph.draw(),radius=0

Glyph() after draw()

RoundGlyph.RoundGlyph(),radius=5

在Glyph中,draw()方法是抽象的,这样设计是为了覆盖该方法。我们确实在RoungGlyph中强制覆盖了draw()。但是Glyph构造器会调用这个方法,结果导致了对RoundGlyph.draw()的调用,这看起来似乎是我们的目的。但是如果看到输出结果,我们会发现当Glyph的构造器调用draw()方法时,radius不是默认初始值1,而是0。

解决这个问题的关键是初始化的实际过程:

1)在其他任何事物发生之前,将分配给对象的存储空间初始化成二进制零。

2)如前所述那样调用基类构造器。此时,调用被覆盖后的draw()方法(要在调用RoundGlyph构造器之前调用),由于步骤1的缘故,我们此时会发现radius的值为0。

3)按照声明的顺序调用成员的初始化方法。

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

<b>9.</b><b>初始化及类的加载</b>

看以下代码:

<b>class</b> Insect {

    <b>private</b> <b>int</b> i = 9;

    <b>protected</b> <b>int</b> j, m;

    Insect() {

       System.out.println("i = " + i + ", j = " + j);

       j = 39;

    <b>private</b> <b>static</b> <b>int</b> x1 = print("static Insect.x1 initialized");

    <b>static</b> <b>int</b> print(String s) {

       <b>return</b> 47;

    Tag t1 = <b>new</b> Tag(1);

<b>public</b> <b>class</b> Beetle <b>extends</b> Insect {

    <b>private</b> <b>int</b> k = print("Beetle.k initialized");

    <b>public</b> Beetle() {

       System.out.println("k = " + k);

       System.out.println("j = " + j);

       System.out.println("m = " + m);

    <b>private</b> <b>static</b> <b>int</b> x2 = print("static Beetle.x2 initialized");

       System.out.println("Beetle constructor");

       Beetle b = <b>new</b> Beetle();

    Tag t2 = <b>new</b> Tag(2);

static Insect.x1 initialized

static Beetle.x2 initialized

Beetle constructor

i = 9, j = 0

Beetle.k initialized

k = 47

j = 39

m = 0

你在Beetle 上运行Java 时,所发生的第一件事情就是你试图访问Beetle.main( ) (一个static 方法),于是加载器开始启动并找出 Beetle 类被编译的程序代码(它被编译到了一个名为Beetle .class 的文件之中)。在对它进行加载的过程中,编译器注意到它有一个基类(这是由关键字 extends  告知的),于是它继续进行加载。不管你是否打算产生一个该基类的对象,这都要发生。

如果该基类还有其自身的基类,那么第二个基类就会被加载,如此类推。接下来,根基类中的静态初始化(在此例中为Insect)即会被执行,然后是下一个导出类,以此类推。这种方式很重要,因为导出类的静态初始化可能会依赖于基类成员能否被正确初始化的。

至此为止,必要的类都已加载完毕(静态变量和静态块),对象就可以被创建了。

首先,对象中所有的原始类型都会被设为缺省值,对象引用被设为null——这是通过将对象内存设为二进制零值而一举生成的。然后,基类的构造器会被调用。在本例中,它是被自动调用的。但你也可以用super 来指定对基类构造器的调用。基类构造器和导出类的构造器一样,以相同的顺序来经历相同的过程,即向上寻找基类构造器。在基类构造器完成之后(即根部构造器找到之后),实例变量(instance variables )按其次序被初始化(注意观察代码中的Tag())。最后,构造器的其余部分被执行。

本文转自zhangjunhd51CTO博客,原文链接:http://blog.51cto.com/zhangjunhd/20927,如需转载请自行联系原作者