提交 8577ac5a 编写于 作者: W wizardforcel

4.4~7

上级 f5375ad3
......@@ -9,7 +9,7 @@ i++;
}
```
就会收到一条出错提示消息,告诉你i可能尚未初始化。当然,编译器也可为i赋予一个默认值,但它看起来更象一个程序员的失误,此时默认值反而会“帮倒忙”。若强迫程序员提供一个初始值,就往往能够帮他/她纠出程序里的“臭虫”。
就会收到一条出错提示消息,告诉你`i`可能尚未初始化。当然,编译器也可为`i`赋予一个默认值,但它看起来更象一个程序员的失误,此时默认值反而会“帮倒忙”。若强迫程序员提供一个初始值,就往往能够帮他/她纠出程序里的“臭虫”。
然而,若将基本类型(基本类型)设为一个类的数据成员,情况就会变得稍微有些不同。由于任何方法都可以初始化或使用那个数据,所以在正式使用数据前,若还是强迫程序员将其初始化成一个适当的值,就可能不是一种实际的做法。然而,若为其赋予一个垃圾值,同样是非常不安全的。因此,一个类的所有基本类型数据成员都会保证获得一个初始值。可用下面这段小程序看到这些值:
......@@ -65,13 +65,13 @@ float 0.0
double 0.0
```
其中,Char值为空(NULL),没有数据打印出来。
其中,`Char`值为空(`NULL`),没有数据打印出来。
稍后大家就会看到:在一个类的内部定义一个对象引用时,如果不将其初始化成新对象,那个引用就会获得一个空值。
4.4.1 规定初始化
如果想自己为变量赋予一个初始值,又会发生什么情况呢?为达到这个目的,一个最直接的做法是在类内部定义变量的同时也为其赋值(注意在C++里不能这样做,尽管C++的新手们总“想”这样做)。在下面,Measurement类内部的字段定义已发生了变化,提供了初始值:
如果想自己为变量赋予一个初始值,又会发生什么情况呢?为达到这个目的,一个最直接的做法是在类内部定义变量的同时也为其赋值(注意在C++里不能这样做,尽管C++的新手们总“想”这样做)。在下面,`Measurement`类内部的字段定义已发生了变化,提供了初始值:
```
class Measurement {
......@@ -86,7 +86,7 @@ class Measurement {
//. . .
```
亦可用相同的方法初始化非基本(主)类型的对象。若Depth是一个类,那么可象下面这样插入一个变量并进行初始化:
亦可用相同的方法初始化非基本(主)类型的对象。若`Depth`是一个类,那么可象下面这样插入一个变量并进行初始化:
```
class Measurement {
......@@ -95,7 +95,7 @@ boolean b = true;
// . . .
```
若尚未为o指定一个初始值,同时不顾一切地提前试用它,就会得到一条运行期错误提示,告诉你产生了名为“异常”(Exception)的一个错误(在第9章详述)。
若尚未为`o`指定一个初始值,同时不顾一切地提前试用它,就会得到一条运行期错误提示,告诉你产生了名为“异常”(`Exception`)的一个错误(在第9章详述)。
甚至可通过调用一个方法来提供初始值:
```
......@@ -126,7 +126,7 @@ int i = f();
```
这正是编译器对“向前引用”感到不适应的一个地方,因为它与初始化的顺序有关,而不是与程序的编译方式有关。
这种初始化方法非常简单和直观。它的一个限制是类型Measurement的每个对象都会获得相同的初始化值。有时,这正是我们希望的结果,但有时却需要盼望更大的灵活性。
这种初始化方法非常简单和直观。它的一个限制是类型`Measurement`的每个对象都会获得相同的初始化值。有时,这正是我们希望的结果,但有时却需要盼望更大的灵活性。
4.4.2 构造器初始化
......@@ -181,7 +181,7 @@ public class OrderOfInitialization {
} ///:~
```
Card中,Tag对象的定义故意到处散布,以证明它们全都会在构造器进入或者发生其他任何事情之前得到初始化。除此之外,t3在构造器内部得到了重新初始化。它的输入结果如下:
`Card`中,`Tag`对象的定义故意到处散布,以证明它们全都会在构造器进入或者发生其他任何事情之前得到初始化。除此之外,`t3`在构造器内部得到了重新初始化。它的输入结果如下:
```
Tag(1)
......@@ -192,13 +192,13 @@ Tag(33)
f()
```
因此,t3引用会被初始化两次,一次在构造器调用前,一次在调用期间(第一个对象会被丢弃,所以它后来可被当作垃圾收掉)。从表面看,这样做似乎效率低下,但它能保证正确的初始化——若定义了一个重载的构造器,它没有初始化t3;同时在t3的定义里并没有规定“默认”的初始化方式,那么会产生什么后果呢?
因此,`t3`引用会被初始化两次,一次在构造器调用前,一次在调用期间(第一个对象会被丢弃,所以它后来可被当作垃圾收掉)。从表面看,这样做似乎效率低下,但它能保证正确的初始化——若定义了一个重载的构造器,它没有初始化`t3`;同时在`t3`的定义里并没有规定“默认”的初始化方式,那么会产生什么后果呢?
2. 静态数据的初始化
若数据是静态的(static),那么同样的事情就会发生;如果它属于一个基本类型(基本类型),而且未对其初始化,就会自动获得自己的标准基本类型初始值;如果它是指向一个对象的引用,那么除非新建一个对象,并将引用同它连接起来,否则就会得到一个空值(NULL)。
若数据是静态的(`static`),那么同样的事情就会发生;如果它属于一个基本类型(基本类型),而且未对其初始化,就会自动获得自己的标准基本类型初始值;如果它是指向一个对象的引用,那么除非新建一个对象,并将引用同它连接起来,否则就会得到一个空值(`NULL`)。
如果想在定义的同时进行初始化,采取的方法与非静态值表面看起来是相同的。但由于static值只有一个存储区域,所以无论创建多少个对象,都必然会遇到何时对那个存储区域进行初始化的问题。下面这个例子可将这个问题说更清楚一些:
如果想在定义的同时进行初始化,采取的方法与非静态值表面看起来是相同的。但由于`static`值只有一个存储区域,所以无论创建多少个对象,都必然会遇到何时对那个存储区域进行初始化的问题。下面这个例子可将这个问题说更清楚一些:
```
//: StaticInitialization.java
......@@ -255,7 +255,7 @@ public class StaticInitialization {
} ///:~
```
Bowl允许我们检查一个类的创建过程,而Table和Cupboard能创建散布于类定义中的Bowl的static成员。注意在static定义之前,Cupboard先创建了一个非static的Bowl b3。它的输出结果如下:
`Bowl`允许我们检查一个类的创建过程,而`Table``Cupboard`能创建散布于类定义中的`Bowl``static`成员。注意在`static`定义之前,`Cupboard`先创建了一个非`static``Bowl b3`。它的输出结果如下:
```
Bowl(1)
......@@ -279,18 +279,18 @@ f2(1)
f3(1)
```
static初始化只有在必要的时候才会进行。如果不创建一个Table对象,而且永远都不引用Table.b1或Table.b2,那么static Bowl b1和b2永远都不会创建。然而,只有在创建了第一个Table对象之后(或者发生了第一次static访问),它们才会创建。在那以后,static对象不会重新初始化。
初始化的顺序是首先static(如果它们尚未由前一次对象创建过程初始化),接着是非static对象。大家可从输出结果中找到相应的证据。
`static`初始化只有在必要的时候才会进行。如果不创建一个`Table`对象,而且永远都不引用`Table.b1``Table.b2`,那么`static Bowl b1``b2`永远都不会创建。然而,只有在创建了第一个`Table`对象之后(或者发生了第一次`static`访问),它们才会创建。在那以后,`static`对象不会重新初始化。
初始化的顺序是首先`static`(如果它们尚未由前一次对象创建过程初始化),接着是非`static`对象。大家可从输出结果中找到相应的证据。
在这里有必要总结一下对象的创建过程。请考虑一个名为Dog的类:
在这里有必要总结一下对象的创建过程。请考虑一个名为`Dog`的类:
(1) 类型为Dog的一个对象首次创建时,或者Dog类的static方法/static字段首次访问时,Java解释器必须找到Dog.class(在事先设好的类路径里搜索)。
(1) 类型为`Dog`的一个对象首次创建时,或者`Dog`类的`static`方法/`static`字段首次访问时,Java解释器必须找到`Dog.class`(在事先设好的类路径里搜索)。
(2) 找到Dog.class后(它会创建一个Class对象,这将在后面学到),它的所有static初始化模块都会运行。因此,static初始化仅发生一次——在Class对象首次载入的时候。
(2) 找到`Dog.class`后(它会创建一个`Class`对象,这将在后面学到),它的所有`static`初始化模块都会运行。因此,`static`初始化仅发生一次——在`Class`对象首次载入的时候。
(3) 创建一个new Dog()时,Dog对象的构建进程首先会在内存堆(Heap)里为一个Dog对象分配足够多的存储空间。
(3) 创建一个`new Dog()`时,`Dog`对象的构建进程首先会在内存堆(Heap)里为一个`Dog`对象分配足够多的存储空间。
(4) 这种存储空间会清为零,将Dog中的所有基本类型设为它们的默认值(零用于数字,以及boolean和char的等价设定)。
(4) 这种存储空间会清为零,将`Dog`中的所有基本类型设为它们的默认值(零用于数字,以及`boolean``char`的等价设定)。
(5) 进行字段定义时发生的所有初始化都会执行。
......@@ -298,7 +298,7 @@ static初始化只有在必要的时候才会进行。如果不创建一个Table
3. 明确进行的静态初始化
Java允许我们将其他static初始化工作划分到类内一个特殊的“static构建从句”(有时也叫作“静态块”)里。它看起来象下面这个样子:
Java允许我们将其他`static`初始化工作划分到类内一个特殊的“`static`构建从句”(有时也叫作“静态块”)里。它看起来象下面这个样子:
```
class Spoon {
......@@ -309,7 +309,7 @@ class Spoon {
// . . .
```
尽管看起来象个方法,但它实际只是一个static关键字,后面跟随一个方法主体。与其他static初始化一样,这段代码仅执行一次——首次生成那个类的一个对象时,或者首次访问属于那个类的一个static成员时(即便从未生成过那个类的对象)。例如:
尽管看起来象个方法,但它实际只是一个`static`关键字,后面跟随一个方法主体。与其他`static`初始化一样,这段代码仅执行一次——首次生成那个类的一个对象时,或者首次访问属于那个类的一个`static`成员时(即便从未生成过那个类的对象)。例如:
```
//: ExplicitStatic.java
......@@ -343,11 +343,11 @@ public class ExplicitStatic {
Cups.c1.f(99); // (1)
}
static Cups x = new Cups(); // (2)
static Cups y = new Cups(); // (2)
static Cups y = new Cups(); // (2)
} ///:~
```
在标记为(1)的行内访问static对象c1的时候,或在行(1)标记为注释,同时(2)行不标记成注释的时候,用于Cups的static初始化模块就会运行。若(1)和(2)都被标记成注释,则用于Cups的static初始化进程永远不会发生。
在标记为(1)的行内访问`static`对象`c1`的时候,或在行(1)标记为注释,同时(2)行不标记成注释的时候,用于`Cups``static`初始化模块就会运行。若(1)和(2)都被标记成注释,则用于`Cups``static`初始化进程永远不会发生。
4. 非静态实例的初始化
......@@ -394,4 +394,4 @@ public class Mugs {
}
```
它看起来与静态初始化从句极其相似,只是static关键字从里面消失了。为支持对“匿名内部类”的初始化(参见第7章),必须采用这一语法格式。
它看起来与静态初始化从句极其相似,只是`static`关键字从里面消失了。为支持对“匿名内部类”的初始化(参见第7章),必须采用这一语法格式。
......@@ -3,7 +3,7 @@
在C中初始化数组极易出错,而且相当麻烦。C++通过“集合初始化”使其更安全(注释⑥)。Java则没有象C++那样的“集合”概念,因为Java中的所有东西都是对象。但它确实有自己的数组,通过数组初始化来提供支持。
数组代表一系列对象或者基本数据类型,所有相同的类型都封装到一起——采用一个统一的标识符名称。数组的定义和使用是通过方括号索引运算符进行的([])。为定义一个数组,只需在类型名后简单地跟随一对空方括号即可:
数组代表一系列对象或者基本数据类型,所有相同的类型都封装到一起——采用一个统一的标识符名称。数组的定义和使用是通过方括号索引运算符进行的(`[]`)。为定义一个数组,只需在类型名后简单地跟随一对空方括号即可:
```
int[] al;
......@@ -15,9 +15,9 @@ int[] al;
int al[];
```
这种格式与C和C++程序员习惯的格式是一致的。然而,最“通顺”的也许还是前一种语法,因为它指出类型是“一个int数组”。本书将沿用那种格式。
这种格式与C和C++程序员习惯的格式是一致的。然而,最“通顺”的也许还是前一种语法,因为它指出类型是“一个`int`数组”。本书将沿用那种格式。
编译器不允许我们告诉它一个数组有多大。这样便使我们回到了“引用”的问题上。此时,我们拥有的一切就是指向数组的一个引用,而且尚未给数组分配任何空间。为了给数组创建相应的存储空间,必须编写一个初始化表达式。对于数组,初始化工作可在代码的任何地方出现,但也可以使用一种特殊的初始化表达式,它必须在数组创建的地方出现。这种特殊的初始化是一系列由花括号封闭起来的值。存储空间的分配(等价于使用new)将由编译器在这种情况下进行。例如:
编译器不允许我们告诉它一个数组有多大。这样便使我们回到了“引用”的问题上。此时,我们拥有的一切就是指向数组的一个引用,而且尚未给数组分配任何空间。为了给数组创建相应的存储空间,必须编写一个初始化表达式。对于数组,初始化工作可在代码的任何地方出现,但也可以使用一种特殊的初始化表达式,它必须在数组创建的地方出现。这种特殊的初始化是一系列由花括号封闭起来的值。存储空间的分配(等价于使用`new`)将由编译器在这种情况下进行。例如:
```
int[] a1 = { 1, 2, 3, 4, 5 };
......@@ -58,11 +58,11 @@ public class Arrays {
} ///:~
```
大家看到a1获得了一个初始值,而a2没有;a2将在以后赋值——这种情况下是赋给另一个数组。
大家看到`a1`获得了一个初始值,而`a2`没有;`a2`将在以后赋值——这种情况下是赋给另一个数组。
这里也出现了一些新东西:所有数组都有一个本质成员(无论它们是对象数组还是基本类型数组),可对其进行查询——但不是改变,从而获知数组内包含了多少个元素。这个成员就是length。与C和C++类似,由于Java数组从元素0开始计数,所以能索引的最大元素编号是“length-1”。如超出边界,C和C++会“默默”地接受,并允许我们胡乱使用自己的内存,这正是许多程序错误的根源。然而,Java可保留我们这受这一问题的损害,方法是一旦超过边界,就生成一个运行期错误(即一个“异常”,这是第9章的主题)。当然,由于需要检查每个数组的访问,所以会消耗一定的时间和多余的代码量,而且没有办法把它关闭。这意味着数组访问可能成为程序效率低下的重要原因——如果它们在关键的场合进行。但考虑到因特网访问的安全,以及程序员的编程效率,Java设计人员还是应该把它看作是值得的。
这里也出现了一些新东西:所有数组都有一个本质成员(无论它们是对象数组还是基本类型数组),可对其进行查询——但不是改变,从而获知数组内包含了多少个元素。这个成员就是`length`。与C和C++类似,由于Java数组从元素0开始计数,所以能索引的最大元素编号是`length-1`。如超出边界,C和C++会“默默”地接受,并允许我们胡乱使用自己的内存,这正是许多程序错误的根源。然而,Java可保留我们这受这一问题的损害,方法是一旦超过边界,就生成一个运行期错误(即一个“异常”,这是第9章的主题)。当然,由于需要检查每个数组的访问,所以会消耗一定的时间和多余的代码量,而且没有办法把它关闭。这意味着数组访问可能成为程序效率低下的重要原因——如果它们在关键的场合进行。但考虑到因特网访问的安全,以及程序员的编程效率,Java设计人员还是应该把它看作是值得的。
程序编写期间,如果不知道在自己的数组里需要多少元素,那么又该怎么办呢?此时,只需简单地用new在数组里创建元素。在这里,即使准备创建的是一个基本数据类型的数组,new也能正常地工作(new不会创建非数组的基本类型):
程序编写期间,如果不知道在自己的数组里需要多少元素,那么又该怎么办呢?此时,只需简单地用`new`在数组里创建元素。在这里,即使准备创建的是一个基本数据类型的数组,`new`也能正常地工作(`new`不会创建非数组的基本类型):
```
//: ArrayNew.java
......@@ -87,7 +87,7 @@ public class ArrayNew {
} ///:~
```
由于数组的大小是随机决定的(使用早先定义的pRand()方法),所以非常明显,数组的创建实际是在运行期间进行的。除此以外,从这个程序的输出中,大家可看到基本数据类型的数组元素会自动初始化成“空”值(对于数值,空值就是零;对于char,它是null;而对于boolean,它却是false)。
由于数组的大小是随机决定的(使用早先定义的`pRand()`方法),所以非常明显,数组的创建实际是在运行期间进行的。除此以外,从这个程序的输出中,大家可看到基本数据类型的数组元素会自动初始化成“空”值(对于数值,空值就是零;对于`char`,它是`null`;而对于`boolean`,它却是`false`)。
当然,数组可能已在相同的语句中定义和初始化了,如下所示:
......@@ -95,7 +95,7 @@ public class ArrayNew {
int[] a = new int[pRand(20)];
```
若操作的是一个非基本类型对象的数组,那么无论如何都要使用new。在这里,我们会再一次遇到引用问题,因为我们创建的是一个引用数组。请大家观察包装器类型Integer,它是一个类,而非基本数据类型:
若操作的是一个非基本类型对象的数组,那么无论如何都要使用`new`。在这里,我们会再一次遇到引用问题,因为我们创建的是一个引用数组。请大家观察包装器类型`Integer`,它是一个类,而非基本数据类型:
```
//: ArrayClassObj.java
......@@ -121,13 +121,13 @@ public class ArrayClassObj {
} ///:~
```
在这儿,甚至在new调用后才开始创建数组:
在这儿,甚至在`new`调用后才开始创建数组:
```
Integer[] a = new Integer[pRand(20)];
```
它只是一个引用数组,而且除非通过创建一个新的Integer对象,从而初始化了对象引用,否则初始化进程不会结束:
它只是一个引用数组,而且除非通过创建一个新的`Integer`对象,从而初始化了对象引用,否则初始化进程不会结束:
```
a[i] = new Integer(pRand(500));
......@@ -135,7 +135,7 @@ a[i] = new Integer(pRand(500));
但若忘记创建对象,就会在运行期试图读取空数组位置时获得一个“异常”错误。
下面让我们看看打印语句中String对象的构成情况。大家可看到指向Integer对象的引用会自动转换,从而产生一个String,它代表着位于对象内部的值。
下面让我们看看打印语句中`String`对象的构成情况。大家可看到指向`Integer`对象的引用会自动转换,从而产生一个`String`,它代表着位于对象内部的值。
亦可用花括号封闭列表来初始化对象数组。可采用两种形式,第一种是Java 1.0允许的唯一形式。第二种(等价)形式自Java 1.1才开始提供支持:
......@@ -163,7 +163,7 @@ public class ArrayInit {
这种做法大多数时候都很有用,但限制也是最大的,因为数组的大小是在编译期间决定的。初始化列表的最后一个逗号是可选的(这一特性使长列表的维护变得更加容易)。
数组初始化的第二种形式(Java 1.1开始支持)提供了一种更简便的语法,可创建和调用方法,获得与C的“变量参数列表”(C通常把它简称为“变参表”)一致的效果。这些效果包括未知的参数数量以及未知的类型(如果这样选择的话)。由于所有类最终都是从通用的根类Object中继承的,所以能创建一个方法,令其获取一个Object数组,并象下面这样调用它:
数组初始化的第二种形式(Java 1.1开始支持)提供了一种更简便的语法,可创建和调用方法,获得与C的“变量参数列表”(C通常把它简称为“变参表”)一致的效果。这些效果包括未知的参数数量以及未知的类型(如果这样选择的话)。由于所有类最终都是从通用的根类`Object`中继承的,所以能创建一个方法,令其获取一个`Object`数组,并象下面这样调用它:
```
//: VarArgs.java
......@@ -178,8 +178,8 @@ public class VarArgs {
System.out.println(x[i]);
}
public static void main(String[] args) {
f(new Object[] {
new Integer(47), new VarArgs(),
f(new Object[] {
new Integer(47), new VarArgs(),
new Float(3.14), new Double(11.11) });
f(new Object[] {"one", "two", "three" });
f(new Object[] {new A(), new A(), new A()});
......@@ -187,7 +187,7 @@ public class VarArgs {
} ///:~
```
此时,我们对这些未知的对象并不能采取太多的操作,而且这个程序利用自动String转换对每个Object做一些有用的事情。在第11章(运行期类型标识或RTTI),大家还会学习如何调查这类对象的准确类型,使自己能对它们做一些有趣的事情。
此时,我们对这些未知的对象并不能采取太多的操作,而且这个程序利用自动`String`转换对每个`Object`做一些有用的事情。在第11章(运行期类型标识或RTTI),大家还会学习如何调查这类对象的准确类型,使自己能对它们做一些有趣的事情。
4.5.1 多维数组
......@@ -263,7 +263,7 @@ public class MultiDimArray {
} ///:~
```
用于打印的代码里使用了length,所以它不必依赖固定的数组大小。
用于打印的代码里使用了`length`,所以它不必依赖固定的数组大小。
第一个例子展示了基本数据类型的一个多维数组。我们可用花括号定出数组内每个矢量的边界:
```
......@@ -274,8 +274,12 @@ int[][] a1 = {
```
每个方括号对都将我们移至数组的下一级。
第二个例子展示了用new分配的一个三维数组。在这里,整个数组都是立即分配的:
第二个例子展示了用`new`分配的一个三维数组。在这里,整个数组都是立即分配的:
```
int[][][] a2 = new int[2][2][4];
```
但第三个例子却向大家揭示出构成矩阵的每个矢量都可以有任意的长度:
```
......@@ -287,9 +291,9 @@ int[][][] a2 = new int[2][2][4];
}
```
对于第一个new创建的数组,它的第一个元素的长度是随机的,其他元素的长度则没有定义。for循环内的第二个new则会填写元素,但保持第三个索引的未定状态——直到碰到第三个new
对于第一个`new`创建的数组,它的第一个元素的长度是随机的,其他元素的长度则没有定义。`for`循环内的第二个`new`则会填写元素,但保持第三个索引的未定状态——直到碰到第三个`new`
根据输出结果,大家可以看到:假若没有明确指定初始化值,数组值就会自动初始化成零。
可用类似的表式处理非基本类型对象的数组。这从第四个例子可以看出,它向我们演示了用花括号收集多个new表达式的能力:
可用类似的表式处理非基本类型对象的数组。这从第四个例子可以看出,它向我们演示了用花括号收集多个`new`表达式的能力:
```
Integer[][] a4 = {
......@@ -311,4 +315,4 @@ int[][][] a2 = new int[2][2][4];
}
```
i*j只是在Integer里置了一个有趣的值。
`i*j`只是在`Integer`里置了一个有趣的值。
......@@ -2,6 +2,6 @@
作为初始化的一种具体操作形式,构造器应使大家明确感受到在语言中进行初始化的重要性。与C++的程序设计一样,判断一个程序效率如何,关键是看是否由于变量的初始化不正确而造成了严重的编程错误(臭虫)。这些形式的错误很难发现,而且类似的问题也适用于不正确的清除或收尾工作。由于构造器使我们能保证正确的初始化和清除(若没有正确的构造器调用,编译器不允许对象创建),所以能获得完全的控制权和安全性。
在C++中,与“构建”相反的“析构”(Destruction)工作也是相当重要的,因为用new创建的对象必须明确地清除。在Java中,垃圾收集器会自动为所有对象释放内存,所以Java中等价的清除方法并不是经常都需要用到的。如果不需要类似于构造器的行为,Java的垃圾收集器可以极大简化编程工作,而且在内存的管理过程中增加更大的安全性。有些垃圾收集器甚至能清除其他资源,比如图形和文件引用等。然而,垃圾收集器确实也增加了运行期的开销。但这种开销到底造成了多大的影响却是很难看出的,因为到目前为止,Java解释器的总体运行速度仍然是比较慢的。随着这一情况的改观,我们应该能判断出垃圾收集器的开销是否使Java不适合做一些特定的工作(其中一个问题是垃圾收集器不可预测的性质)。
在C++中,与“构建”相反的“析构”(Destruction)工作也是相当重要的,因为用`new`创建的对象必须明确地清除。在Java中,垃圾收集器会自动为所有对象释放内存,所以Java中等价的清除方法并不是经常都需要用到的。如果不需要类似于构造器的行为,Java的垃圾收集器可以极大简化编程工作,而且在内存的管理过程中增加更大的安全性。有些垃圾收集器甚至能清除其他资源,比如图形和文件引用等。然而,垃圾收集器确实也增加了运行期的开销。但这种开销到底造成了多大的影响却是很难看出的,因为到目前为止,Java解释器的总体运行速度仍然是比较慢的。随着这一情况的改观,我们应该能判断出垃圾收集器的开销是否使Java不适合做一些特定的工作(其中一个问题是垃圾收集器不可预测的性质)。
由于所有对象都肯定能获得正确的构建,所以同这儿讲述的情况相比,构造器实际做的事情还要多得多。特别地,当我们通过“创作”或“继承”生成新类的时候,对构建的保证仍然有效,而且需要一些附加的语法来提供对它的支持。大家将在以后的章节里详细了解创作、继承以及它们对构造器造成的影响。
......@@ -3,11 +3,11 @@
(1) 用默认构造器创建一个类(没有参数),用它打印一条消息。创建属于这个类的一个对象。
(2) 在练习1的基础上增加一个重载的构造器,令其采用一个String参数,并随同自己的消息打印出来。
(2) 在练习1的基础上增加一个重载的构造器,令其采用一个`String`参数,并随同自己的消息打印出来。
(3) 以练习2创建的类为基础上,创建属于它的对象引用的一个数组,但不要实际创建对象并分配到数组里。运行程
序时,注意是否打印出来自构造器调用的初始化消息。
(4) 创建同引用数组联系起来的对象,最终完成练习3。
(5) 用参数“before”,“after”和“none”运行程序,试验Garbage.java。重复这个操作,观察是否从输出中看出了一些固定的模式。改变代码,使System.runFinalization()在System.gc()之前调用,再观察结果。
\ No newline at end of file
(5) 用参数`before``after``none`运行程序,试验`Garbage.java`。重复这个操作,观察是否从输出中看出了一些固定的模式。改变代码,使`System.runFinalization()``System.gc()`之前调用,再观察结果。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册