ThinkingInJava笔记-类再生(第六章)(一)

2014-11-24 01:34:37 · 作者: · 浏览: 2

类再生分为两种方式:

合成,在新类里简单创建原有类的对象。

继承,它创建一个新类,将其视作现有类的一个“类型”,我们可以原样采取现有类的形式,并在其中加入新代码,同时不会对现有类产生影响。

由于这儿涉及到两个类——基础类及衍生类,而不再是以前的一个,所以在想象衍生类的结果对象时,可能会产生一些迷惑。从外部看,似乎新类拥有与基础类相同的接口,而且可包含一些额外的方法和字段。但继承并非仅仅简单地复制基础类的接口了事。创建衍生类的一个对象时,它在其中包含了基础类的一个“子对象”。这个子对象就象我们根据基础类本身创建了它的一个对象。从外部看,基础类的子对象已封装到衍生类的对象里了。

当然,基础类子对象应该正确地初始化,而且只有一种方法能保证这一点:在构建器中执行初始化,通过调

用基础类构建器,后者有足够的能力和权限来执行对基础类的初始化。在衍生类的构建器中,Java 会自动插

入对基础类构建器的调用。下面这个例子向大家展示了对这种三级继承的应用:

Java代码

public class Art {

Art(){

System.out.println("art");

}

}

public class Drawing extends Art {

Drawing(){

System.out.println("drawing");

}

public static void main(String[] args) {

Drawing drawing = new Drawing();

}

}

输出结果为:

art

drawing

上述例子有自己默认的构建器;也就是说,它们不含任何自变量。编译器可以很容易地调用它们,因为不存

在具体传递什么自变量的问题。如果类没有默认的自变量,或者想调用含有一个自变量的某个基础类构建

器,必须明确地编写对基础类的调用代码。这是用super 关键字以及适当的自变量列表实现的,如下所示:

Java代码

class Game {

Game(int i) {

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

}

}

class BoardGame extends Game {

BoardGame(int i) {

super(i);

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

}

}

class Chess extends BoardGame {

Chess() {

super(11);

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

}

public static void main(String[] args) {

Chess x = new Chess();

}

}

输出结果为:

Game constructor

BoardGame constructor

Chess constructor

尽管编译器会强迫我们对基础类进行初始化,并要求我们在构建器最开头做这一工作,但它并不会监视我们

是否正确初始化了成员对象。所以对此必须特别加以留意。

继承的一个好处是它支持“累积开发”,允许我们引入新的代码,同时不会为现有代码造成错误。这样可将

新错误隔离到新代码里。通过从一个现成的、功能性的类继承,同时增添成员新的数据成员及方法(并重新

定义现有方法),我们可保持现有代码原封不动(另外有人也许仍在使用它),不会为其引入自己的编程

误。一旦出现错误,就知道它肯定是由于自己的新代码造成的。这样一来,与修改现有代码的主体相比,改

正错误所需的时间和精力就可以少很多。

继承最值得注意的地方就是它没有为新类提供方法。继承是对新类和基础类之间的关系的一种表达。可这样

总结该关系:“新类属于现有类的一种类型”。

这种表达并不仅仅是对继承的一种形象化解释,继承是直接由语言提供支持的。作为一个例子,大家可考虑

一个名为Instrument 的基础类,它用于表示乐器;另一个衍生类叫作Wind。由于继承意味着基础类的所有

方法亦可在衍生出来的类中使用,所以我们发给基础类的任何消息亦可发给衍生类。若Instrument 类有一个

play()方法,则Wind 设备也会有这个方法。这意味着我们能肯定地认为一个Wind 对象也是Instrument的一

种类型。下面这个例子揭示出编译器如何提供对这一概念的支持:

Java代码

public class Instrument {

public void play(){

System.out.println("hello");

}

static void tune(Instrument i){

i.play();

}

}

public class Wind extends Instrument {

public static void main(String[] args) {

Wind wind = new Wind();

Instrument.tune(wind);

}

}

运行结果:

hello

这个例子中最有趣的无疑是tune()方法,它能接受一个Instrument句柄。但在Wind.main()中,tune()方法

是通过为其赋予一个Wind 句柄来调用的。由于Java 对类型检查特别严格,所以大家可能会感到很奇怪,为

什么接收一种类型的方法也能接收另一种类型呢?但是,我们一定要认识到一个Wind 对象也是一个

Instrument对象。而且对于不在Wind 中的一个Instrument(乐器),没有方法可以由tune()调用。在

tune()中,代码适用于Instrument以及从Instrument 衍生出来的任何东西。在这里,我们将从一个Wind 句

柄转换成一个Instrument 句柄的行为叫作“上溯造型”。

由于造型的方向是从衍生类到基础类,箭头朝上,所以通常把它叫作“上溯造型 ”,即Upcasting。上溯造

型肯定是安全的,因为我们是从一个更特殊的类型到一个更常规的类型。换言之,衍生类是基础类的一个超

集。它可以包含比基础类更多的方法,但它至少包含了基础类的方法。进行上溯造型的时候,类接口可能出

现的唯一一个问题是它可能丢失方法,而不是赢得这些方法。这便是在没有任何明确的造型或者其他特殊标

注的情况下,编译器为什么允许上溯造型的原因所在。

继承中对于final关键字的解释:

final数据

(1) 编译期常数,它永远不会改变

(2) 在运行期初始化的一个值,我们不希望它发生变化

Java代码

final int s = 1;//s为常数

final Object obj = new Object();//obj为不可变的句柄,但是obj内部变量可以变

final int s;//s为常量,但是使用前必须初始化

void method(fi