类的重 用 要点导读 本章内容需要与配套的主教材《Java语言程序设计》(第3版)第3章配合学习。 主教材第3章介绍了类的重用。继承是面向对象程序设计的基石之一,是一种由已有 类创建新类的机制。Java要求声明的每个类都有超类,当没有显式指定超类时,超类隐含 为jaa.ang.jecaa不支持类的多重继承 , 即每个子类只能有 vlObt类。Jv只支持类的单继承 , 一个直接超类。一个对象继承的内容取决于此对象所属的类在类层次中的位置。一个对象 从其所有的直接和间接超类中继承属性及行为。 隐藏和覆盖是指子类对从超类继承来的属性变量及方法可以重新定义。如果子类对从 超类继承来的属性变量重新定义,则从超类继承的属性将被隐藏。如果子类在声明方法时 , 使用相同的方法名及参数表,但执行不同的功能,这种情况称为方法覆盖。 有继承时的构造方法遵循以下的原则:子类不能从超类继承构造方法;好的程序设计 方法是在子类的构造方法中调用某一个超类构造方法;super关键字也可以用于构造方法 中,其功能为调用超类的构造方法;如果在子类的构造方法的声明中没有明确调用超类的构 造方法,则系统在执行子类的构造方法时会自动调用超类的默认构造方法(即无参的构造方 法) ; 如果在子类的构造方法的声明中调用超类的构造方法,则调用语句必须出现在子类构 造方法的第一行。 Object类是Java程序中所有类的直接或间接超类,也是类库中所有类的超类,处在类 层次最高点。如果两个对象具有相同的类型及相同的属性值,则称这两个对象相等 equaidentica ( l) ; 如果两个引用变量指向的是同一个对象,则称这两个对象同一(l)。 final类是指被final修饰的类。final类不可能有子类。final( ) 方法是被final修饰的方 法,不能被当前类的子类覆盖。 抽象类是指被abstract修饰的类,表示不能使用new 进行实例化,即不能有具体实例 对象。抽象类既可以包含抽象方法,也可以包含非抽象方法。抽象方法使用abstract修饰 , 有方法的声明,而没有方法的实现,并将在抽象类的子类中实现。不能在非抽象的类中声明 抽象方法。 泛型的本质是参数化类型,即所操作的数据类型被指定为一个参数。这种数据类型的 指定可以使用在类、接口以及方法中,分别称为泛型类、泛型接口和泛型方法。通配符泛型 用参数“? ” 表示,代表任意一种类型。有限制的泛型是指对类型参数加上一定的限制,例如 , 必须是某类的子类或者实现了某接口。 Java的类中可以有其他类的对象作为成员,这便是类的组合。 Java提供了用于语言开发的类库,称为应用程序编程接口(ApplicationProgramming Interface,API),分别放在不同的包中。Java提供的包主要有:java.lang、java.io、java. math、java.util、java.applet、java.awt、java.awt.datatransfer、java.awt.event、java.awt.image、 java.beans、java.net、java.rmi、java.security、java.sql等。 实验3 类的重用 一、实验目的 (1)理解类的组合与继承,知道何时使用哪种重用机制。 (2)了解final类、final()方法、抽象类、抽象方法的概念。 (3)熟练掌握主教材第3章提到的Java基础类库中的一些常见类,学会查阅JavaDoc。 (4)了解Java包的概念。知道为什么要用包,养成良好的命名习惯。 (5)初步了解JAR文件的概念和jar命令的格式。 二、实验任务 1.继承关系举例 举出两个例子,各包含一个超类和它的三个子类。对于超类,要给出两个成员变量,一 个方法的定义。要求两个例子一个是具体事物,另一个是抽象事物。不要使用教材中已有 的例子,最好能和自己所学的专业有些关系。 例如: 具体事物:主教材3.4.1节中的Shape、Circle、Triangle、Rectangle的例子。超类成员 变量可以有周长、面积。方法可以有draw(),功能是把图形画出来。 抽象事物:超类,如银行相关交易类。子类,如存款交易、贷款交易、外汇交易。超类成 员变量可以有交易时间、交易金额。方法可以有save(),功能是把交易记录保存。 2.equals()方法 下面的代码实现了复数类和复数的减法。equals()方法是比较两个对象是否相等,每 个Java类都有一个继承自Object类的默认的equals()方法。下面的类有main()方法,那 它的执行结果是什么? 为什么会有这样的结果? 这个执行结果真的是我们期待的结果吗? 重写ComplexNumber类的equals()方法,使得它真正能够比较两个复数是否相等。你添 加的equals()方法应该使这个main()方法返回应有的结果(truetruetruetruefalsefalse)。 注意:equals()方法的参数是Object对象,所以应该考虑如果输入其他对象(例如 String)或者空对象(null)应该如何处理,应该避免equals()方法出现什么样的异常。也可 以使用自己正确编写的复数类。 public class ComplexNumber { double real = 0; double imagine = 0; 第3章 类的重用·21· public ComplexNumber(double real, double imagine) { this.real = real; this.imagine = imagine; } public ComplexNumber minus(ComplexNumber operand) { return new ComplexNumber(this.real - operand.real, this.imagine - operand.imagine); } public static void main(String[]args) { ComplexNumber complex1 = new ComplexNumber(2.02d, 3.1d); ComplexNumber complex2 = new ComplexNumber(2d, 3d); ComplexNumber complex3 = complex2; ComplexNumber complex4 = new ComplexNumber(2d, 3d); ComplexNumber complex5 = new ComplexNumber(0.02d, 0.1d); ComplexNumber complex6 = complex1.minus(complex2); System.out.println(complex2 == complex3); System.out.println(complex2.equals(complex3)); System.out.println(complex2.equals(complex4)); System.out.println(complex6.equals(complex5)); System.out.println(complex1.equals(null)); System.out.println(complex1.equals(new String("abc"))); } } 3.Java类库常用包使用练习1 输入一个包括4个小数的字符串,数之间用分号分隔,格式为“a;b;c;d”。计算如下表 达式: sina ×cosb× cd 返回与结果最接近的整数并按照格式输出计算时间。例如: 输入: 0.5;-0.8;3;6.3 输出: 2021-10-28 8:00:06 result = 11 如果输入的数字格式不正确,输出“InvalidInput”字符串。 提示: 使用java.util.StringTokenizer,java.text.SimpleDateFormat,java.lang. Math类。 4.定义并实现一个记账软件的类 设计、定义、实现一个交易管理软件中会使用到的类。该软件可以记录和管理如下 信息。 ·22· Java语言程序设计实践教程 第3章类的重用·23· (1)交易,包括日常收支交易、转账交易、投资买卖交易(外汇、股票)。 (2)人员机构,包括人员、机构。 (3)账户,包括活期账户、定期账户、信用卡账户、投资买卖交易账户。 定义所需类的超类、子类;合理地使用抽象类、final类;合理地定义每个类的成员变量 及其类型;合理地给出几个类的方法(无须实现方法)。 5.Java类库常用包使用练习 2 用两种方法实现encode()、decode()方法给字符串加解密。输出原始字符串、加密后的 字符串和解密后的字符串。测试字符串“Wewilbreakoutofprisonatdawn”。 方法一:按照下列规则对字符串进行替换加密,包括大小写字母,“_”代表空格字符。 abcdefghijklmnopqrstuvwxyz_ veknohzf_iljxdmygbrcswqupta 方法二:就像在JavaDoc中看到的一样,计算机生成的是伪随机数。给Random对象 设置一个固定的种子Radstlng), nom.eSed(o它就能输出一个固定的随机序列出来。控制 这个随机序列的范围,然后用这个序列和字符串序列进行可逆的运算,求得加密后的串。解 密时通过相同的sed再现那个随机序列,并使用逆运算恢复原来的字符串。 提示:字符串里的字符是以数字保存的,它们可以进行加减求余等的运算。 读者也可以使用自己的方法来替代方法二并附加简要说明。可以考虑如何将这个功能 打包成一个jar文件给别人使用。 三、实验步骤 1.复习理解类的继承并创建本章的工程 (1)复习、理解类的继承,区分类的继承与组合关系。 (2)描述出实验任务1要求的例子。 (3)使用InteliJIDEA创建工程exp3,本章的包都在exp3中。 说明:此后各章的实验代码都分别以“exp章号”为工程名,创建工程的步骤在后续各 章的实验步骤叙述中将略去。后续各章的实验不再使用命令行编译的方式,建议使用 InteliJIDEA作为开发环境,读者也可以选择自己熟悉的集成开发环境。 2.实现Complex类的equals()方法 (1)创建包“roblem2”,在“roblem2”包下创建ComplexNumber.ava,实现实验任务 2中的代码。 ppj (2)理解Object类在整个Java语言中的地位,以及它所提供的常用方法。 (3)不实现equals()方法,使用Object的默认实现,运行main()方法,看所得结果与自 己的预测是否一致。 (4)按照要求给ComplexNumber类实现equals()方法,并通过main()方法的测试,返 回正确结果。或者使用自己编写的ComplexNumber类。 注意:浮点数在计算机中的表示是不精确的。因此,如果要判断两个浮点数是否相等, 最好不要使用符号“== ”,而是要判断二者的差的绝对值是否很小,例如,是否小于10-5, 如果很小,则可以认为这两个浮点数是相等的。 ·24· Java语言程序设计实践教程 3.熟悉常用Ja类库一 (1)打开JavI文档,熟悉各部分的内容,能够快速找到所需的类。aA(a) P(v) (2)查看jav.tlSrneieaa.etDtFrmaaalnMah类,熟悉其 方法。 aui.tigToknzr,jvtx.aeot,jv.ag.t (3)建立名为“p”的包,在包pem3下创建Cj实现实验任务3 的要求。 roblem3roblompute.ava, 4.设计并实现记账软件的类 (1)按照实验任务4的要求,设计记账软件的类。 (2)考虑设计的合理性,合理使用抽象类、final类等。 (3)设计类的成员变量、常用方法。 (4)新建Java包MyAcount,实现自己设计的类 。 记账软件的参考类图见图3-1 。 图3- 1 记账软件的参考类图 5.熟悉常用Ja类库二 (1)打开JavI文档,查看jvlnSrnur、jaa.tlHahMap等类,熟悉其P(v) aA(a) aa.ag.tigBfevui.s 方法。也可以使用其他类来协助完成实验要求。 (2)新建名为“problem5”的包,在problem5包下创建StringEncryptor.java。创建方法 encode1()、decode1()、encode2()、decode2(),实现实验任务要求。自己在main()里写测试 方法,以验证正确性。 提示:检查加密后的字符串能否通过解密复原。 (3)复习jar文件的概念。可以尝试写一个处理Stringargs[]参数的main()方法。然 后将这个类打包成jar包,供他人使用。 习题解答 1.子类将继承超类所有的属性和方法吗? 为什么? 解: 子类不从超类继承构造方法,但可通过“super.方法名”访问;除此之外,子类从超类继 承其他所有属性和方法,但private属性/方法不能直接访问。 2.方法覆盖与方法重载有何不同? 解: 方法覆盖是指如果子类不需要使用从超类继承来的方法的功能,则可以声明自己的方 法。在声明时,使用相同的方法名及参数表,但执行不同的功能。而重载是指名字一样但参 数表不一样的方法。二者的不同主要在于:方法覆盖时,子类的参数表和父类一样,方法重 载时,参数表不一样。 3.泛型的本质是什么? 泛型可以使用在哪些场合? 解: 其本质是参数化类型,即所操作的数据类型被指定为一个参数。 泛型可以使用在类、接口以及方法中,分别称为泛型类、泛型接口和泛型方法。 4.声明两个带有无参构造方法的类A 和B,声明A 的子类C,并且声明B为C的一个 成员,不声明C的构造方法。编写测试代码,生成类C的实例对象,并观察结果。 解: 新建Exe3_4.java文件,其内容为: class A { public A() { System.out.println("In Constructor of A"); } }c lass B { public B() { System.out.println("In Constructor of B"); } }c lass C extends A { B b; } 第3章 类的重用·25· public class Exe3_4 { public static void main(String[]args) { C c = new C(); } } 程序的输出结果为: In Constructor of A 从输出结果可以看出,在通过默认构造方法创建类C的对象c时,自动调用了类C的 超类A 的无参数构造方法,而没有初始化C的成员B。 5.声明一个超类A,它只有一个非默认构造方法;声明A 的子类B,B具有默认方法及 非默认方法,并在B的构造方法中调用超类A 的构造方法。 解: 新建Exe3_5.java文件,其内容为: class A { int i; A (int i) { this.i = i; System.out.println("In A (int i), i = " + i); } }c lass B extends A { int j; B () { super(0); System.out.println("In B ()"); j = 0; } B (int j) { super(j); System.out.println("In B (int j)"); this.j = j; } }p ublic class Exe3_5 { public static void main (String[]args) { B b1 = new B(); System.out.println("*********************"); B b2 = new B(5); } } ·26· Java语言程序设计实践教程 程序的输出结果为: In A (int i), i = 0 In B () ********************* In A (int i), i = 5 In B (int j) 读者可以尝试在类B的无参数构造方法中不调用“super(0)”,此时会有编译错误,即找 不到类A 的无参数构造方法。 6.声明一个类,它具有一个方法,此方法被重载三次,派生一个新类,并增加一个新的 重载方法,编写测试类验证四个方法对于子类都有效。 解: 新建Exe3_6.java文件,其内容为: class Base { int i; public void setValue(int i) { this.i = i; System.out.println("In setValue(int i), i = " + this.i); } public void setValue(float f) { this.i = (int)f; System.out.println("In setValue(float f), i = " + this.i); } public void setValue(double d) { this.i = (int)d; System.out.println("In setValue(double d), i = " + this.i); } }c lass Sub extends Base{ //在派生类中重载setValue()方法 public void setValue(byte b) { this.i = (int)b; System.out.println("In setValue(byte b), i = " + this.i); } }p ublic class Exe3_6 { public static void main(String args[]) { Sub s = new Sub(); s.setValue(2); s.setValue(3.5f); s.setValue(2.8d); s.setValue((byte)8); } } 第3章 类的重用·27· 程序的输出结果为: In setValue(int i), i = 2 In setValue(float f), i = 3 In setValue(double d), i = 2 In setValue(byte b), i = 8 7.声明一个具有final方法的类,声明一个子类,并试图对这个方法进行覆盖 (override),观察会有什么结果。 解: 出现编译错误,因为final()方法不能在子类中对其进行覆盖。 8.声明一个final类,并试图声明其子类,观察会有什么结果。 解: 将会出现编译错误,并提示无法从final类继承。 9.什么是抽象类? 抽象类中是否一定要包括抽象方法? 解: 抽象类就是不能使用new进行实例化的类,即没有具体实例对象的类。抽象类中不一 定要包括抽象方法。 10.this和super分别有哪些特殊含义? 都有哪些用法? 解: 关键词this是当前对象的引用,关键词super表示超类。二者都可以用于:调用本类 或超类的方法,访问本类或超类的属性,调用本类或超类的构造方法。 11.完成下面超类及子类的声明: (1)声明Student类。 属性包括:学号、姓名、英语成绩、数学成绩、计算机成绩和总成绩。 方法包括:构造方法、getter()方法、setter()方法、toString()方法、equals()方法、 compare()方法(比较两个学生的总成绩,结果可以是大于、小于、等于),sum()方法(计算总 成绩)和testScore()方法(计算评测成绩)。 注:评测成绩可以取三门课成绩的平均分,另外,任何一门课的成绩的改变都需要对总 成绩进行重新计算,因此,在每一个setter()方法中应调用sum()方法计算总成绩。 (2)声明StudentXW(学习委员)类为Student类的子类。 在StudentXW 类中增加责任属性,并覆盖testScore()方法(计算评测成绩,评测成 绩=三门课的平均分+3)。 (3)声明StudentBZ(班长)类为Student类的子类。 在StudentBZ类中增加责任属性,并重写testScore()方法(计算评测成绩,评测成绩= 三门课的平均分+5)。 (4)声明测试类,生成若干个Student类、StudentXW 类及StudentBZ类对象,并分别 计算它们的评测成绩。 解: 新建Exe3_11.java文件,其内容为: ·28· Java语言程序设计实践教程 class Student { int ID; String name; int englishScore; int mathScore; int computerScore; int sumScore; //构造方法 Student(int ID, String name, int englishScore, int mathScore, int computerScore) { this.ID = ID; this.name = name; this.englishScore = englishScore; this.mathScore = mathScore; this.computerScore = computerScore; sum(); } //设置属性的方法 public void setID(int ID) { this.ID = ID; } public void setName(String name) { this.name = name; } public void setEnglishScore(int englishScore) { this.englishScore = englishScore; sum(); } public void setMathScore(int mathScore) { this.mathScore = mathScore; sum(); } public void setComputerScore(int computerScore) { this.computerScore = computerScore; sum(); } //得到属性的方法 public int getID() { return ID; } public String getName() { return name; } public int getEnglishScore() { 第3章 类的重用·29· return englishScore; } public int getMathScore() { return mathScore; } public int getComputerScore() { return computerScore; } public String toString() { String s = name; s += "(学号为" + ID + ")的成绩如下: \n"; s += "英语: " + englishScore + "\n"; s += "数学: " + mathScore + "\n"; s += "计算机: " + computerScore + "\n"; s += "总分: " + sumScore + "\n"; return s; } @Override public boolean equals(Object o) { Student s = (Student) o; //当二者的总成绩相等时,返回true return this.sumScore == s.sumScore; } public int compare(Student s) { //大于时返回1,等于时返回0,小于时返回-1 if (this.sumScore > s.sumScore) return 1; else if(this.sumScore == s.sumScore) return 0; else return -1; } public void sum() { //计算总分 sumScore = englishScore + mathScore + computerScore; } public int testScore() { return sumScore / 3; } }c lass StudentXW extends Student{ int duty; StudentXW(int ID, String name, int englishScore, int mathScore, int computerScore, int duty) { super(ID, name, englishScore, mathScore, computerScore); this.duty = duty; ·30· Java语言程序设计实践教程 } @Override public int testScore() { return sumScore / 3 + 3; //平均分加上3 } }c lass StudentBZ extends Student{ int duty; StudentBZ(int ID, String name, int englishScore, int mathScore, int computerScore, int duty) { super(ID, name, englishScore, mathScore, computerScore); this.duty = duty; } @Override public int testScore() { return sumScore / 3 + 5; //平均分加上5 } } //测试类 public class Exec3_11 { public static void main(String[]args) { Student s = new Student(20100023, "张三", 85, 92, 90); StudentXW xw = new StudentXW(20100015, "李四", 80, 90, 95); StudentBZ bz = new StudentBZ(20100005, "王五", 82, 85, 88); System.out.print(s); System.out.println("评测成绩: " + s.testScore()); System.out.print(xw); System.out.println("评测成绩: " + s.testScore()); System.out.print(bz); System.out.println("评测成绩: " + s.testScore()); } } 程序的输出结果为: 张三(学号为20100023)的成绩如下: 英语: 85 数学: 92 计算机: 90 总分: 267 评测成绩: 89 李四(学号为20100015)的成绩如下: 英语: 80 数学: 90 计算机: 95 第3章 类的重用·31· 总分: 265 评测成绩: 89 王五(学号为20100005)的成绩如下: 英语: 82 数学: 85 计算机: 88 总分: 255 评测成绩: 89 12.包有什么作用? 如何声明包和引用包中的类? 解: 包可以管理命名空间,可以解决同名类的冲突问题;也可以组织相关的类,并控制类的 访问权限。 使用package关键字声明包。使用import关键字引用包中的类。 ·32· Java语言程序设计实践教程