可思量用构建器执行初始化历程。这样便可在编程时得到更大的机动水平,因为我们可以在运行期挪用要领和采纳动作,从而“现场”抉择初始化值。但要留意这样一件工作:不行故障自动初始化的举办,它在构建器进入之前就会产生。因此,如果利用下述代码:
class Counter {
int i;
Counter() { i = 7; }
// . . .
那么i首先会初始化成零,然后酿成7。对付所有根基范例以及工具句柄,这种环境都是创立的,个中包罗在界说时已举办了明晰初始化的那些一些。思量到这个原因,编译器不会试着强迫我们在构建器任何特定的场合对元素举办初始化,可能在它们利用之前——初始化早已获得了担保(注释⑤)。
⑤:相反,C++有本身的“构建器初始模块列表”,能在进入构建器主体之前举办初始化,并且它对付工具来说是强制举办的。拜见《Thinking in C++》。
1. 初始化顺序
在一个类里,初始化的顺序是由变量在类内的界说顺序抉择的。纵然变量界说大量遍布于要领界说的中间,那些变量仍会在挪用任何要领之前获得初始化——甚至在构建器挪用之前。譬喻:
//: OrderOfInitialization.java // Demonstrates initialization order. // When the constructor is called, to create a // Tag object, you'll see a message: class Tag { Tag(int marker) { System.out.println("Tag(" + marker + ")"); } } class Card { Tag t1 = new Tag(1); // Before constructor Card() { // Indicate we're in the constructor: System.out.println("Card()"); t3 = new Tag(33); // Re-initialize t3 } Tag t2 = new Tag(2); // After constructor void f() { System.out.println("f()"); } Tag t3 = new Tag(3); // At end } public class OrderOfInitialization { public static void main(String[] args) { Card t = new Card(); t.f(); // Shows that construction is done } } ///:~
在Card中,Tag工具的界说存心处处散布,以证明它们全城市在构建器进入可能产生其他任何工作之前获得初始化。除此之外,t3在构建器内部获得了从头初始化。它的输入功效如下:
Tag(1) Tag(2) Tag(3) Card() Tag(33) f()
因此,t3句柄会被初始化两次,一次在构建器挪用前,一次在挪用期间(第一个工具会被扬弃,所以它厥后可被看成垃圾收掉)。从外貌看,这样做好像效率低下,但它能担保正确的初始化——若界说了一个过载的构建器,它没有初始化t3;同时在t3的界说里并没有划定“默认”的初始化方法,那么会发生什么效果呢?
2. 静态数据的初始化
若数据是静态的(static),那么同样的工作就会产生;假如它属于一个根基范例(主范例),并且未对其初始化,就会自动得到本身的尺度根基范例初始值;假如它是指向一个工具的句柄,那么除非新建一个工具,并将句柄同它毗连起来,不然就会获得一个空值(NULL)。
假如想在界说的同时举办初始化,采纳的要领与非静态值外貌看起来是沟通的。但由于static值只有一个存储区域,所以无论建设几多个工具,都一定会碰着何时对谁人存储区域举办初始化的问题。下面这个例子可将这个问题说更清楚一些:
//: StaticInitialization.java // Specifying initial values in a // class definition. class Bowl { Bowl(int marker) { System.out.println("Bowl(" + marker + ")"); } void f(int marker) { System.out.println("f(" + marker + ")"); } } class Table { static Bowl b1 = new Bowl(1); Table() { System.out.println("Table()"); b2.f(1); } void f2(int marker) { System.out.println("f2(" + marker + ")"); } static Bowl b2 = new Bowl(2); } class Cupboard { Bowl b3 = new Bowl(3); static Bowl b4 = new Bowl(4); Cupboard() { System.out.println("Cupboard()"); b4.f(2); } void f3(int marker) { System.out.println("f3(" + marker + ")"); } static Bowl b5 = new Bowl(5); } public class StaticInitialization { public static void main(String[] args) { System.out.println( "Creating new Cupboard() in main"); new Cupboard(); System.out.println( "Creating new Cupboard() in main"); new Cupboard(); t2.f2(1); t3.f3(1); } static Table t2 = new Table(); static Cupboard t3 = new Cupboard(); } ///:~
Bowl答允我们查抄一个类的建设进程,而Table和Cupboard能建设散布于类界说中的Bowl的static成员。留意在static界说之前,Cupboard先建设了一个非static的Bowl b3。它的输出功效如下:
Bowl(1) Bowl(2) Table() f(1) Bowl(4) Bowl(5) Bowl(3) Cupboard() f(2) Creating new Cupboard() in main Bowl(3) Cupboard() f(2) Creating new Cupboard() in main Bowl(3) Cupboard() f(2) f2(1) f3(1)
#p#分页标题#e#
static初始化只有在须要的时候才会举办。假如不建设一个Table工具,并且永远都不引用Table.b1或Table.b2,那么static Bowl b1和b2永远都不会建设。然而,只有在建设了第一个Table工具之后(可能产生了第一次static会见),它们才会建设。在那今后,static工具不会从头初始化。
初始化的顺序是首先static(假如它们尚未由前一次工具建设进程初始化),接着长短static工具。各人可从输出功效中找到相应的证据。
在这里有须要总结一下工具的建设进程。请思量一个名为Dog的类:
(1) 范例为Dog的一个工具首次建设时,可能Dog类的static要领/static字段首次会见时,Java表明器必需找到Dog.class(在事先设好的类路径里搜索)。
(2) 找到Dog.class后(它会建设一个Class工具,这将在后头学到),它的所有static初始化模块城市运行。因此,static初始化仅产生一次——在Class工具首次载入的时候。
(3) 建设一个new Dog()时,Dog工具的构建历程首先会在内存堆(Heap)里为一个Dog工具分派足够多的存储空间。
(4) 这种存储空间会清为零,将Dog中的所有根基范例设为它们的默认值(零用于数字,以及boolean和char的等价设定)。
(5) 举办字段界说时产生的所有初始化城市执行。
(6) 执行构建器。正如第6章将要讲到的那样,这实际大概要求举办相当多的操纵,出格是在涉及担任的时候。
3. 明晰举办的静态初始化
Java答允我们将其他static初始化事情分别到类内一个非凡的“static构建从句”(有时也叫作“静态块”)里。它看起来象下面这个样子:
class Spoon { static int i; static { i = 47; } // . . .
尽量看起来象个要领,但它实际只是一个static要害字,后头跟从一个要领主体。与其他static初始化一样,这段代码仅执行一次——首次生成谁人类的一个工具时,可能首次会见属于谁人类的一个static成员时(即便从未生成过谁人类的工具)。譬喻:
//: ExplicitStatic.java // Explicit static initialization // with the "static" clause. class Cup { Cup(int marker) { System.out.println("Cup(" + marker + ")"); } void f(int marker) { System.out.println("f(" + marker + ")"); } } class Cups { static Cup c1; static Cup c2; static { c1 = new Cup(1); c2 = new Cup(2); } Cups() { System.out.println("Cups()"); } } public class ExplicitStatic { public static void main(String[] args) { System.out.println("Inside main()"); Cups.c1.f(99); // (1) } static Cups x = new Cups(); // (2) static Cups y = new Cups(); // (2) } ///:~
在标志为(1)的行内会见static工具c1的时候,或在行(1)标志为注释,同时(2)行不标志成注释的时候,用于Cups的static初始化模块就会运行。若(1)和(2)都被标志成注释,则用于Cups的static初始化历程永远不会产生。
4. 非静态实例的初始化
针对每个工具的非静态变量的初始化,Java 1.1提供了一种雷同的语法名目。下面是一个例子:
//: Mugs.java // Java 1.1 "Instance Initialization" class Mug { Mug(int marker) { System.out.println("Mug(" + marker + ")"); } void f(int marker) { System.out.println("f(" + marker + ")"); } } public class Mugs { Mug c1; Mug c2; { c1 = new Mug(1); c2 = new Mug(2); System.out.println("c1 & c2 initialized"); } Mugs() { System.out.println("Mugs()"); } public static void main(String[] args) { System.out.println("Inside main()"); Mugs x = new Mugs(); } } ///:~
各人可看到实例初始化从句:
{ c1 = new Mug(1); c2 = new Mug(2); System.out.println("c1 & c2 initialized"); }
它看起来与静态初始化从句极其相似,只是static要害字从内里消失了。为支持对“匿名内部类”的初始化(拜见第7章),必需回收这一语法名目。