Tips第6章继承继承是复用程序代码的有力手段,当多个类(Sub1,Sub2…Sub100)之间存在相同的属性和方法时,可从这些类中抽象出父类Base。在父类Base中定义这些相同的属性和方法,所有的Sub类无须重新定义这些属性和方法,而只需要通过extends语句来声明继承Base类:publicclassSubextendsBase{…}Sub类就会自动拥有在Base类中定义的属性和方法。本章首先介绍了继承的基本语法,然后介绍了两个重要的概念——方法重载和方法覆盖,随后介绍了多态的各种特征,最后介绍了正确使用继承关系的原则,以及和组合关系的区别。6.1继承的基本语法在Java语言中,用extends关键字来表示一个类继承了另一个类,例如:publicclassSubextendsBase{…}以上代码表明Sub类继承了Base类。那么Sub类到底继承了Base类的哪些东西呢?这需要分为以下两种情况。当Sub类和Base类位于同一个包中:Sub类继承Base类中public、protected和默认访问级别的成员变量和成员方法。当Sub类和Base类位于不同的包中:Sub类继承Base类中public和protected访问级别的成员变量和成员方法。在本章及其他章节,为了叙述方便,有时会采用“子类继承父类的属性和方法”这样笼统的说法。假定Sub和Base类位于同一个包中,以下程序演示在Sub类中可继承Base类的哪些成员变量和方法。publicclassBase{publicintpublicVarOfBase=1;//public访问级别privateintprivateVarOfBase=1;//private访问级别intdefaultVarOfBase=1;//默认访问级别protectedvoidmethodOfBase(){}//protected访问级别}publicclassSubextendsBase{Java面向对象编程162JavaObject-OrientedProgrammingpublicvoidmethodOfSub(){publicVarOfBase=2;//合法,可以访问Base类的public类型的变量defaultVarOfBase=2;//合法,可以访问Base类的默认访问级别的变量privateVarOfBase=2;//非法,不能访问Base类的private类型的变量methodOfBase();//合法,可以访问Base类的protected类型的方法}publicstaticvoidmain(Stringargs[]){Subsub=newSub();sub.publicVarOfBase=3;//合法,Sub类继承了Base类的public类型的变量sub.privateVarOfBase=3;//非法,Sub类不能继承Base类的private类型的变量sub.defaultVarOfBase=3;//合法,Sub类继承了Base类的默认访问级别的变量sub.methodOfBase();//合法,Sub类继承了Base类的protected类型的方法sub.methodOfSub();//合法,这是Sub类本身的实例方法}}Java语言不支持多继承,即一个类只能直接继承一个类。例如以下代码会导致编译错误。classSubextendsBase1,Base2,Base3{…}尽管一个类只能有一个直接的父类,但是它可以有多个间接的父类,例如以下代码表明Base1类继承Base2类,Sub类继承Base1类,Base2类是Sub类的间接父类。classBase1extendsBase2{…}classSubextendsBase1{…}所有的Java类都直接或间接地继承了java.lang.Object类。Object类是所有Java类的祖先,在这个类中定义了所有的Java对象都具有的相同行为,本书第19章的19.1节(Object类)归纳了在Object类中定义的方法。在Java类框图中,具有继承关系的类形成了一棵继承树。图6-1显示了一棵由生物Creature、动物Animal、植物Vegetation和狗Dog等组成的继承树。在以上继承树中,Dog类的直接父类为Animal类,它的间接父类包括Creature和Object类。Object、Creature、Animal和Dog类形成了一个继承树分支,在这个分支上,位于下层的子类会继承上层所有直接或间接父类的属性和方法。如果两个类不在同一个继承树分支上,就不会存在继承关系。例如Dog类和Vegetation类,它们不在一个继承树分支上,因此不存在继承关系。假如在定义一个类时,没有使用extends关键字,那么这个类直接继承Object类。例如以下Sample类的直接父类为Object类。publicclassSample{…}ObjectVegetationCreatureDogCatTigerAnimal图6-1一棵继承树第6章继承JavaObject-OrientedProgramming1636.2方法重载(Overload)有时候,类的同一种功能有多种实现方式,到底采用哪种实现方式,取决于调用者给定的参数。例如杂技师能训练动物,对于不同的动物有不同的训练方式。publicvoidtrain(Dogdog){//训练小狗站立、排队、做算术…}publicvoidtrain(Monkeymonkey){//训练小猴敬礼、翻筋斗、骑自行车…}再例如某个类的一个功能是比较两个城市是否相同,一种方式是按两个城市的名字进行比较,另一种方式是按两个城市的名字,以及城市所在国家的名字进行比较。publicbooleanisSameCity(Stringcity1,Stringcity2){returncity1.equals(city2);}publicbooleanisSameCity(Stringcity1,Stringcity2,Stringcountry1,Stringcountry2){returnisSameCity(city1,city2)&&country1.equals(country2);}再例如java.lang.Math类的max()方法能够从两个数字中取出最大值,它有多种实现方式。publicstaticintmax(inta,intb)publicstaticintmax(longa,longb)publicstaticintmax(floata,floatb)publicstaticintmax(doublea,doubleb)以下程序多次调用Math类的max()方法,运行时,Java虚拟机先判断给定参数的类型,然后决定到底执行哪个max()方法。//参数均为int类型,因此执行max(inta,intb)方法Math.max(1,2);//参数均为float类型,因此执行max(floata,floatb)方法Math.max(1.0F,2.0F);//参数中有一个是double类型,自动把另一个参数2转换为double类型,//执行max(doublea,doubleb)方法Math.max(1.0,2);对于类的方法(包括从父类中继承的方法),如果有两个方法的方法名相同,但参数不一致,那么可以说,一个方法是另一个方法的重载方法。重载方法必须满足以下条件:方法名相同。Java面向对象编程164JavaObject-OrientedProgramming方法的参数类型、个数、顺序至少有一项不相同。方法的返回类型可以不相同。方法的修饰符可以不相同。在一个类中不允许定义两个方法名相同,并且参数签名也完全相同的方法。因为假如存在这样的两个方法,Java虚拟机在运行时就无法决定到底执行哪个方法。参数签名是指参数的类型、个数和顺序。例如以下Sample类中已经定义了一个amethod()方法。publicclassSample{publicvoidamethod(inti,Strings){}//加入其他方法}下面哪些方法可以加入到Sample类中,并且保证编译正确呢?A)publicvoidamethod(Strings,inti){}(可以)B)publicintamethod(inti,Strings){return0;}(不可以)C)privatevoidamethod(inti,Stringmystring){}(不可以)D)publicvoidAmethod(inti,Strings){}(可以)E)abstractvoidamethod(inti);(不可以)选项A的amethod()方法的参数顺序和已有的不一样,所以能作为重载方法加入到Sample类中。选项B和选项C的amethod()方法的参数签名和已有的一样,所以不能加入到Sample类中。对于选项C,尽管String类型的参数的名字和已有的不一样,但比较参数签名无须考虑参数的具体名字。选项D的方法名为Amethod,与已有的不一样,所以能加入到Sample类中。选项E的方法的参数数目和已有的不一样,因此是一种重载方法。但由于此处的Sample类不是抽象类,所以不能包含这个抽象方法。假如把Sample类改为抽象类,就能把这个方法加入到Sample类中了。再例如,以下Sample类中已经定义了一个作为程序入口的main()方法。abstractpublicclassSample{publicstaticvoidmain(String[]s){}//加入其他方法}下面哪些方法可以加入到Sample类中,并且保证编译正确呢?A)abstractpublicvoidmain(Strings,inti);(可以)B)publicfinalstaticintmain(String[]s){}(不可以)C)privatevoidmain(inti,Stringmystring){}(可以)D)publicvoidmain(Strings)throwsException{}(可以)作为程序入口的main()方法也可以被重载。以上选项A、C和D都可以被加入到Sample类中。选项B与已有的main()方法有相同的方法签名,因此不允许再加入到Sample类中。第6章继承JavaObject-OrientedProgramming1656.3方法覆盖(Override)假如有100个类,分别为Sub1,Sub2…Sub100,它们的一个共同行为是写字,除了Sub1类用脚写字外,其余的类都用手写字。可以抽象出一个父类Base,它有一个表示写字的方法write(),那么这个方法到底如何实现呢?从尽可能提高代码可重用性的角度看,write()方法应该采用适用于大多数子类的实现方式,这样就可以避免在大多数子类中重复定义write()方法。因此Base类的write()方法的定义如下:publicvoidwrite(){//Base类的write()方法//用手写字…}由于Sub1类的写字的实现方式与Base类不一样,因此在Sub1类中必须重新定义write()方法。publicvoidwrite(){//Sub1类的write()方法//用脚写字…}如果在子类中定义的一个方法,其名称、返回类型及参数签名正好与父类中某个方法的名称、返回类型及参数签名相匹配,那么可以说,子类的方法覆盖了父类的方法。覆盖方法必须满足多种约束,下面分别介绍。(1)子类方法的名称、参数签名和返回类型必须与父类方法的名称、参数签名和返回类型一致。例如以下代码将导致编译错误。publicclassBase{publicvoidmethod(){…}}publicclassSubextendsBase{publicintmethod(){//编译错误,返回类型不一致return0;}}Java编译器首先判断Sub类的method()方法与Base类的method()方法的参数签