- Java语言GUI程序设计
- 赵满来
- 4427字
- 2025-02-28 01:27:57
4.3 类的继承
继承是面向对象程序设计技术的又一基本特征,Java语言通过继承机制构建了类的层次结构,实现代码的复用。
4.3.1 继承的概念
Java语言基于对象之间的相似性构建其类型——类,Java程序通过类的定义对所涉及的对象进行了分类。事实上,一些类之间具有一定程度的相似性。例如,2.5节使用过的简单Swing GUI组件窗口、标签、文本字段、按钮、“口令”字段和单选按钮等都具有位置、大小、前景颜色、背景颜色等共同属性,也都具有定位——setLocation(int x, int y)、设置和改变大小——setSize(int width, int height)、显示——setVisible(boolean b)、设置和改变前景颜色——setForeground(Color c)及设置和改变背景颜色——setBackground(Color c)等共同方法,因此可以从定义这些组件的类JFrame、JLabel、JTextField、JButton、JPasswordField和JRadioButton中抽取它们共同的属性和方法形成一个新的类定义——Component。更为重要的是定义了Component之后,原来那些类中就不必定义这些共同的属性和方法,把Component作为它们的基础类(就称为基类),把它们自身作为导出类只定义基类中没有的属性和方法。显然,这种组织代码的方法可以使多个导出类共享基类的代码,从而实现了基类代码的复用,使整个程序的代码更加简洁。
Java语言提供了继承机制,使程序从已有的类中导出(也称为派生)新的类,新的类吸收(称为继承)了已有类的属性和方法,同时可以添加在已有类中没有的属性和方法扩展新类。在这种情况下,已有的类称为父类(也称基类、超类),派生出的新类称为子类(也称派生类)。
Java语言采用单继承机制,即一个子类只能有一个父类,但是一个父类可以有多个子类。当然,一个类的子类也可以是另一个类的父类。例如,B类是A类的子类,B类还可以是C类的父类。在UML中用箭头()表示类的继承关系,箭头指向父类,箭尾指向子类。上面三个类的继承关系可以标记为A
B
C,此时,B类继承了A类的属性和方法,C类继承了B类的属性和方法,同时继承了B类从A类中继承来的属性和方法。
Java语言的类通过继承构建了一个单根树型层次结构,树的根是Object类。也就是说,Java中的所有类都是Object类的直接或间接派生类。图4.9是Swing库的主要GUI组件继承层次图。

图4.9 Swing组件继承层次图
4.3.2 子类的创建
Java语言使用关键字extends创建已有类的子类。基本格式如下:
class 子类名 extends 父类名 { … // 子类添加的属性定义 … // 子类添加的方法定义 … // 对父类方法的重新定义 }
例如,2.1节创建的简单加法计算器程序窗口NumberAddition类就是javax.swing.JFrame类的子类,生成的代码是“public class NumberAddition extends javax.swing.JFrame{…}”(见图4.10)。又如,例2.7设计的用户登录程序的UserLogin窗口也是javax.swing.JFrame类的子类,生成的代码是“public class UserLogin extends javax.swing.JFrame {…}”。应用程序的顶层窗口具有大量的共同属性和行为,在javax.swing.JFrame类中已经有了良好的定义,但不同的应用程序窗口内包含的组件和程序的具体行为是有差别的,因此应该定义为javax.swing.JFrame类的子类。

图4.10 简单加法计算器程序的窗口类NumberAddition的定义
如果一个类的直接父类就是java.lang.Object,则不需要指出父类而直接定义即可,也就是不需要写出“extends Object”。
4.3.3 派生类对基类成员的访问性
当创建了一个类的子类后,子类就继承了父类的所有属性和方法。但是,在子类中对继承来的成员的访问性取决于父类成员的访问修饰限定:正如表4.1所示,子类可以直接访问父类的public成员、同一个包中的默认访问性成员以及不同包中的protected成员,但不能访问父类中的private成员。
例如,对于继承关系为AB
C的三个类,如果采用如下定义:

程序清单4.2:

程序清单4.3:

在NetBeans IDE的项目窗口右击项目chap04|“源包”|book.inheritdemos下的TestABC.java文件,在快捷菜单中单击“编译文件”菜单项,再次右击该文件,执行“运行文件”菜单项,程序输出如图4.11所示。

图4.11 运行TestABC.java文件的输出
类C是类A的派生类且在不同的包中,A类的成员变量x采用protected修饰,在类C中可以直接访问变量x——程序清单4.2的第18行;类C是类B的子类且在不同的包中,B类的成员变量y采用默认修饰具有包访问性,如果C中直接访问变量y,则会发生错误,如程序清单4.2的第20行;在类C中可以访问其内部定义的private变量z——程序清单4.2的第23行。TestABC类与A类和B类处于同一个包中,与C类处于不同的包,且与A类、B类和C类没有继承关系,TestABC类可以访问的A类、B类和C类的成员如图4.12的弹出式提示框所示,程序清单4.3中第22行能够直接访问A类定义的protected成员变量x。值得注意的是,TestABC类中不能直接访问B类中定义的包访问性成员变量y(见图4.12的弹出式提示框),原因在于objc是C类对象,objc.y是C类继承自父类B的成员变量,由于TestABC类与C类不在同一个包中,自然就不能访问C类对象的包访问性成员变量objc.y。如果在TestABC类中创建一个B类的对象,由于这两个类位于同一个包中,因此可以直接访问具有包访问性的成员变量objb.y(见图4.12的右边弹出式提示框)。

图4.12 TestABC类对A类、B类和C类成员的访问性示例
4.3.4 抽象方法与抽象类
许多情况下,基类中的一些方法只是为其派生类提供一个统一的调用接口——统一的方法名和参数表定义,方法体中具体的实现代码在其不同的子类中各不相同,基类中无法给出此类方法的具体实现代码,Java语言提供了关键字abstract在程序中把此类方法定义为抽象方法。抽象方法只有方法头而没有方法体,方法头定义了返回值类型、方法名和参数表,定义形式如下:
[访问修饰符] abstract 返回值类型 方法名(参数表);
其中,[访问修饰符]可以是public、protected和private,如果没有此项则为默认的包访问性;返回值类型可以是任意合法的基本数据类型和引用类型,取决于方法的处理逻辑;参数表是“参数类型 参数名,参数类型 参数名,…”格式,也可以只给出参数类型而省略参数名,也可以是空的参数表——括号内什么都不写。应特别注意,参数表的闭括号“)”之后是语句结束符分号“;”。
如果一个类中包含抽象方法,该类就是抽象类,在类定义时头部加上abstract关键字。抽象类的定义形式如下:
[访问修饰符] abstract class 类名 [extends 父类名] { // 属性定义,可选 // 方法定义,可选 // 抽象方法定义,可选 }
Java程序中不能创建抽象类的对象。如果在类中没有抽象方法而定义为抽象类,则目的就是禁止创建该类的对象。
例如,平面圆、矩形、三角形都属于平面几何形状,都具有坐标位置等属性,都可以计算面积和周长。因此,可以先定义一个平面几何形状类MyShape,而将它们分别定义为MyShape的子类。不同具体几何形的面积和周长计算方法是不相同的,在MyShape类中难以具体计算,因此可以将MyShape类定义为抽象类,只在其中定义面积和周长计算方法的方法头,而方法的具体实现留到子类完成。这些类的定义如程序清单4.4~4.7所示。
程序清单4.4:

程序清单4.5:

程序清单4.6:

程序清单4.7:

在程序清单4.4中所定义的抽象类MyShape中包含抽象方法getArea()和getPerimeter(),其子类(如MyCircle类,见程序清单4.5)中必须实现继承的所有抽象方法,NetBeans IDE中会有提示(见图4.13),单击子类定义行前面的图标,会给出有关建议(见图4.14)。
如果子类中没有实现所有的抽象方法,则该子类也必须指定为抽象类。

图4.13 NetBeans IDE抽象类的子类定义提示

图4.14 NetBeans IDE抽象类的子类实现建议
程序清单4.5~4.7所定义的子类MyCircle、MyRectangle和MyTriangle中所实现的getArea()和getPerimeter()各不相同,但是这些类的对象对这两个方法的调用具有相同的代码。如计算面积可以采用如图4.15所示的代码调用——见图4.15的第18和19行、23和24行、28和29行。该程序的运行输出结果见图4.16,可见程序能正确运行。

图4.15 子类对getArea()和getPerimeter()方法的调用

图4.16 平面圆、矩形和三角形的面积和周长计算程序输出
4.3.5 子类的构造方法
子类与其直接父类在构造方法方面存在约束关系,即子类的构造方法必须调用其父类的构造方法,且必须是子类构造方法体中的第一条语句,调用是使用Java语言关键字super实现的,调用格式如下:
super(实参表) ;
其中,实参表是传递给父类构造方法的参数,多个参数使用“,”分隔。此实参表必须与父类的某个构造方法形参表匹配。如果子类的构造方法没有对父类构造方法的调用语句,而父类又存在不含参数的构造方法,编译器会给子类构造方法隐式添加对其父类无参数构造方法的调用语句——super()。
在程序清单4.4中定义的MyShape类中由于其构造方法带有参数,定义它的子类时,NetBeans IDE会提示在子类中添加构造函数(见图4.17),且选取该建议之后会自动创建调用父类构造方法的语句。例如,为MyTriangle类生成构造方法并生成语句“super(x, y);”(见程序清单4.7)。

图4.17 NetBeans IDE提示添加构造函数以定义其父类构造方法
super关键字还用于访问父类的属性和方法。如果子类的某个属性名与父类的相同,可以使用“super.属性名”在子类中引用父类的这个属性;如果父类的某个方法被子类覆盖,可以使用“super.方法名(实参表)”在子类中调用父类的这个方法。
this是Java语言中非常常用的一个关键字,它的基本含义就是当前对象的一个引用。就好像张三跟好多人谈话,使用“我”指代他自身一样,this在其所处的程序之中就相当于指代词“我”。在前面多次见到this的应用,通常有下面几种用法。
(1)在局部变量与实例变量同名的情况下,使用this引用当前对象的实例变量。例如,程序清单4.1中User类构造方法的形参与实例变量同名,因此自动生成的方法体中在赋值号左边通过this引用实例变量,赋值号右边引用的是形参变量,语句形如“this.name =name;”。
(2)类中存在多个构造方法时,在一个构造方法中可以通过this引用其他的构造方法,且this引用的构造方法是其方法体的第一条语句。例如,程序清单4.1中定义的用户类User,如果规定每个用户的用户名是唯一的,就可以定义如下构造方法。
public User(String name) { this.name = name; }
该程序中原来定义的构造方法可以修改为以下定义。
public User(String name, String password, int job) { this(name ); this.password = password; this.job = job; }
(3)使用this引用当前对象。例如,以下程序:

第9行语句中的调用obj.getObjA(),此时obj是当前对象,它的方法getObjA()中的this引用的对象就是obj自身。
如果在Aclass类中再定义一个方法methodB(),可以在其中使用this调用getObjA()方法,例如:void methodB() { this.getObjA(); }。
在不出现二义性的情况下可以省略this引用,这正如我们日常说话在许多情况下可以省略“我”一样。在NetBeans IDE中如果输入this.可以出现代码帮助窗口,从而可以获得帮助。
4.3.6 方法的覆盖与final方法及final类
对于父类中定义的非抽象方法,在其子类中也可以重新实现,即保持该方法的头部在父类中的定义不变,重新编写与父类不同的方法体代码,这就是方法的覆盖(Override,也称为重写)。例如,如果将程序清单4.4所定义的MyShape类中的方法getArea()方法定义为:
public double getArea() { return 0.0; }
则程序清单4.5~4.7所定义的其子类MyCircle、MyRectangle和MyTriangle中所实现的getArea()方法与其保持了相同的方法头——具有相同的方法名、参数表和返回值类型,但是具有不同的方法体代码,因此这三个子类覆盖了父类MyShape中的getArea()方法。
覆盖的方法在子类和父类中必须具有相同的方法名和参数表;返回值类型与父类中保持相同或是其子类型;可访问性也与父类保持相同或更加公开,例如,父类中方法是protected访问性,子类中可以是protected或public。子类不能覆盖父类的private方法,如果子类中出现了与父类同名的private方法,也只是子类的一个新方法,即使参数表和返回值类型相同,也与父类的同名方法无关。
Java语言中可以使用final关键字拒绝对一个方法的覆盖,就是在定义一个方法时在方法头部添加final修饰符,这种方法称为最终方法。定义形式如下:
[访问修饰符] final 返回值类型 方法名(参数表) { … }
定义类时可以在头部添加final修饰符,这种类称为最终类。最终类的定义形式如下:
final class 类名 [extends 父类名] { … }
final类不能作为父类而派生子类,其中的所有方法都不可能被覆盖。
final关键字与abstract关键字是互斥的,就是定义方法和类的时候如果使用了其中一个就不能使用另外一个。