按键盘上方向键 ← 或 → 可快速上下翻页,按键盘上的 Enter 键可回到本书目录页,按键盘上方向键 ↑ 可回到本页顶部!
————未阅读完?加入书签已便下次继续阅读!
现的唯一一个问题是它可能丢失方法,而不是赢得这些方法。这便是在没有任何明确的造型或者其他特殊标
注的情况下,编译器为什么允许上溯造型的原因所在。
也可以执行下溯造型,但这时会面临第 11章要详细讲述的一种困境。
1。 再论合成与继承
在面向对象的程序设计中,创建和使用代码最可能采取的一种做法是:将数据和方法统一封装到一个类里,
并且使用那个类的对象。有些时候,需通过“合成”技术用现成的类来构造新类。而继承是最少见的一种做
法。因此,尽管继承在学习OOP 的过程中得到了大量的强调,但并不意味着应该尽可能地到处使用它。相
反,使用它时要特别慎重。只有在清楚知道继承在所有方法中最有效的前提下,才可考虑它。为判断自己到
底应该选用合成还是继承,一个最简单的办法就是考虑是否需要从新类上溯造型回基础类。若必须上溯,就
需要继承。但如果不需要上溯造型,就应提醒自己防止继承的滥用。在下一章里(多形性),会向大家介绍
必须进行上溯造型的一种场合。但只要记住经常问自己“我真的需要上溯造型吗”,对于合成还是继承的选
择就不应该是个太大的问题。
6。8 final 关键字
由于语境(应用环境)不同,final 关键字的含义可能会稍微产生一些差异。但它最一般的意思就是声明
“这个东西不能改变”。之所以要禁止改变,可能是考虑到两方面的因素:设计或效率。由于这两个原因颇
有些区别,所以也许会造成final 关键字的误用。
在接下去的小节里,我们将讨论final 关键字的三种应用场合:数据、方法以及类。
6。8。1 final 数据
许多程序设计语言都有自己的办法告诉编译器某个数据是“常数”。常数主要应用于下述两个方面:
(1) 编译期常数,它永远不会改变
(2) 在运行期初始化的一个值,我们不希望它发生变化
对于编译期的常数,编译器(程序)可将常数值“封装”到需要的计算过程里。也就是说,计算可在编译期
间提前执行,从而节省运行时的一些开销。在 Java 中,这些形式的常数必须属于基本数据类型
(Primitives),而且要用final 关键字进行表达。在对这样的一个常数进行定义的时候,必须给出一个
152
…………………………………………………………Page 154……………………………………………………………
值。
无论 static还是 final字段,都只能存储一个数据,而且不得改变。
若随同对象句柄使用final,而不是基本数据类型,它的含义就稍微让人有点儿迷糊了。对于基本数据类
型,final 会将值变成一个常数;但对于对象句柄,final 会将句柄变成一个常数。进行声明时,必须将句柄
初始化到一个具体的对象。而且永远不能将句柄变成指向另一个对象。然而,对象本身是可以修改的。Java
对此未提供任何手段,可将一个对象直接变成一个常数(但是,我们可自己编写一个类,使其中的对象具有
“常数”效果)。这一限制也适用于数组,它也属于对象。
下面是演示 final字段用法的一个例子:
//: FinalData。java
// The effect of final on fields
class Value {
int i = 1;
}
public class FinalData {
// Can be pile…time constants
final int i1 = 9;
static final int I2 = 99;
// Typical public constant:
public static final int I3 = 39;
// Cannot be pile…time constants:
final int i4 = (int)(Math。random()*20);
static final int i5 = (int)(Math。random()*20);
Value v1 = new Value();
final Value v2 = new Value();
static final Value v3 = new Value();
//! final Value v4; // Pre…Java 1。1 Error:
// no initializer
// Arrays:
final int'' a = { 1; 2; 3; 4; 5; 6 };
public void print(String id) {
System。out。println(
id + 〃: 〃 + 〃i4 = 〃 + i4 +
〃; i5 = 〃 + i5);
}
public static void main(String'' args) {
FinalData fd1 = new FinalData();
//! fd1。i1++; // Error: can't change value
fd1。v2。i++; // Object isn't constant!
fd1。v1 = new Value(); // OK …not final
for(int i = 0; i 《 fd1。a。length; i++)
fd1。a'i'++; // Object isn't constant!
//! fd1。v2 = new Value(); // Error: Can't
//! fd1。v3 = new Value(); // change handle
//! fd1。a = new int'3';
fd1。print(〃fd1〃);
System。out。println(〃Creating new FinalData〃);
153
…………………………………………………………Page 155……………………………………………………………
FinalData fd2 = new FinalData();
fd1。print(〃fd1〃);
fd2。print(〃fd2〃);
}
} ///:~
由于i1和 I2都是具有 final 属性的基本数据类型,并含有编译期的值,所以它们除了能作为编译期的常数
使用外,在任何导入方式中也不会出现任何不同。I3是我们体验此类常数定义时更典型的一种方式:public
表示它们可在包外使用;Static 强调它们只有一个;而 final 表明它是一个常数。注意对于含有固定初始化
值(即编译期常数)的 fianl static基本数据类型,它们的名字根据规则要全部采用大写。也要注意 i5 在
编译期间是未知的,所以它没有大写。
不能由于某样东西的属性是final,就认定它的值能在编译时期知道。i4 和 i5 向大家证明了这一点。它们在
运行期间使用随机生成的数字。例子的这一部分也向大家揭示出将final 值设为 static 和非 static 之间的
差异。只有当值在运行期间初始化的前提下,这种差异才会揭示出来。因为编译期间的值被编译器认为是相
同的。这种差异可从输出结果中看出:
fd1: i4 = 15; i5 = 9
Creating new FinalData
fd1: i4 = 15; i5 = 9
fd2: i4 = 10; i5 = 9
注意对于fd1 和 fd2 来说,i4 的值是唯一的,但 i5 的值不会由于创建了另一个FinalData 对象而发生改
变。那是因为它的属性是 static,而且在载入时初始化,而非每创建一个对象时初始化。
从v1 到v4 的变量向我们揭示出final 句柄的含义。正如大家在main()中看到的那样,并不能认为由于v2
属于final,所以就不能再改变它的值。然而,我们确实不能再将v2 绑定到一个新对象,因为它的属性是
final。这便是final 对于一个句柄的确切含义。我们会发现同样的含义亦适用于数组,后者只不过是另一种
类型的句柄而已。将句柄变成 final 看起来似乎不如将基本数据类型变成 final那么有用。
2。 空白final
Java 1。1 允许我们创建“空白final”,它们属于一些特殊的字段。尽管被声明成 final,但却未得到一个
初始值。无论在哪种情况下,空白 final 都必须在实际使用前得到正确的初始化。而且编译器会主动保证这
一规定得以贯彻。然而,对于 final 关键字的各种应用,空白 final 具有最大的灵活性。举个例子来说,位
于类内部的一个final 字段现在对每个对象都可以有所不同,同时依然保持其“不变”的本质。下面列出一
个例子:
//: BlankFinal。java
// 〃Blank〃 final data members