代码复用能够大大简化我们的工作。面向对象的语言中一般是通过对类的重复使用来达到代码复用的目的的,Java也不例外。在Java中,复用类有两种方式,合成(has-a)与继承(is-a)。合成就是在新的类中直接创建旧类的对象,这里我们复用的只是代码的功能而不是它的形式。而继承是在原有的类的基础上建立一个新类,新类具有旧类的形式,但也加入了一些新的特性。这一章介绍的主要就是合成与继承方面的知识。

一、合成所使用的语法

合成的语法很简单,只要把要复用的类的对象的引用直接放到新类里就可以了。当然仅仅这样还是不够的,我们还要创建这个类的对象让那个引用来指向它。因为Java不会帮我们自动创建一个缺省的对象,它只会自动替我们把字段中的引用初始化为null。为引用赋值可以在三个地方,一个就是在定义这个引用的时候,另一个是在构造函数中,第三个地方就是在即将要使用这个对象之前。为了防止忘记在使用前为引用赋值,我们一般应该在前两种场合来创建对象。如果我们要创建的这个对象会花费很大开销,而且又可能不是每次都需要创建它的话,我们可以考虑第三种方式来创建这个对象。

二、继承所使用的语法

继承是Java中的重要部分,因为Java是使用单根体系的(C++不是这样,因为它要保持向C的兼容),所以我们定义的每一个类都是继承自Java中的根类Object类。在定义一个继承自已有的类的类时,要使用extends关键字,其后跟上基类的名字,这样表示新定义的这个类是继承自那个基类。在Java中不允许多重继承(C++中允许),也就是说它不允许一个类拥有多于一个的基类,这点劣势可以用接口来弥补,因为Java允许一个类实现任意多个接口。

一个子类会自动获得基类中的全部字段与方法(那些由访问控制符控制的对子类而言不可见的成员也会获得,只是不可见,用不了),这也就是对基类中代码的复用。除了自动获得自基类的代码外,子类中还可定义新的成员,也可以覆写基类中的方法(所谓覆写指的是方法的声明部分一样但实现不一样),这样可以让相同签名的方法拥有不一样的形为。

因为子类自动拥有了基类的成员,因此在子类中自然就可以调用基类的方法。如果这个方法在子类中被覆写过,那编译器知道你是要调用哪个方法呢?Java提供了super关键字在类中表示该类的基类的引用,我们可以通过这个关键字来明确表示我们要用到的是基类中的成员。如果不写super的话,那编译器将会理解为嵌套调用。

这里有个题外话。在Java程序中常常是用public类中的main()方法做为整个程序的入口。这样的静态main()方法并不是非得要在public类中才能出现的,静态的main()方法可以做所有类的入口(但只能是main(),而不能是其它名字的什么静态方法)。比如一个程序有多个class组成,我们要对其中的某个class进行单元测试时,我们就可以在这个class文件中加入main(),编译后生成这个类的.class文件,在控制台通过java来运行它就是了。

子类继承了一个基类后便拥有了基类中的成员,也就可以通过创建的子类对象来访问基类中可见的成员。Java是怎样做到这一点的呢?在我们创建一个子类对象的时候,这里创建的已经不是一个类的对象了,它还会创建这个类的基类的对象,这个基类的对象创建后被包括在子类的对象中。也就是说创建的子类的对象拥有其基类全部的成员(从这就可以知道为什么可以上传),但是子类对象只能访问基类中它可见的成员。那么在创建一个这样的对象时,子类和基类对象创建的顺序是怎么样的呢?为了能够正确的初始化基类,一般会调用基类的构造函数来进行初始化。Java中在调用子类的构造函数时首先会自动的调用基类的构造函数,并且这样的过程是层层传递的。比如C继承了B,而B又继承了A,在创建C的对象时,C的构造函数会首先调用B的构造函数,这时B的构造函数又会首先调用A的构造函数。(如果基类中没有默认构造函数,编译时就会报错。)但是这里自动调用的都是基类的默认构造函数(无参的),如果我们想调用基类的某个带参数的构造函数又该怎么办呢?上面提到可以用super来代替基类的引用,与在构造函数中通过this调用本类其它构造函数的形式一样,我们可以通过super来调用基类带参数的构造函数,比如“super(i, j)”。