第5章〓面向对象(下) 时间不等人!历史不等人!时间属于奋进者!历史属于奋进者!为了实现中华民族伟大复兴的中国梦,我们必须同时间赛跑、同历史并进。 ——习近平(习近平总书记在2020年春节团拜会上的讲话) 编程的艺术就是处理复杂性的艺术。 ——艾兹格·迪科斯彻(Edsger Dijkstra,计算机科学家,1972年图灵奖得主) 代码胜于雄辩。(Talk is cheap. Show me the code) ——林纳斯·托瓦兹(Linus Torvalds,Linux之父) 名家观点 学会如何实现类的重用(继承和组合)。 掌握关键字this和super。 理解方法的覆盖。 掌握类的载入、静态语句块的调用、非静态语句块的调用、构造方法的调用。 理解对象的上溯造型和下溯造型。 掌握最终类、抽象类和接口的定义和应用。 掌握和灵活应用Java异常处理机制。 掌握常见英文单词。 本章学习目标 张双南先生在《科学的目的、精神和方法是什么》中写到: 科学的目的是发现各种规律; 科学的精神包括质疑、独立、唯一; 科学的方法包括逻辑化、定量化和实证化。 思维是人脑对客观事物间接和概括的反映,包括分析、综合、比较、概括、归纳、演绎和推理等能力。 在科学认识活动中,科学思维必须遵循以下三个基本原则。 (1) 在逻辑上要求严密的逻辑性,达到归纳和演绎的统一。归纳方法是从个别或特殊的事物概括出共同本质或一般原理的逻辑思维方法,它是从个别到一般的推理。其目的 课程思政——科学思维 在于透过现象认识本质,通过特殊揭示一般。演绎思维是从一般到个别的推理,是根据一类事物都有的属性、关系、本质来推断该事物中个别事物也具有此属性、关系和本质的思维方法和推理形式。其基本形式是三段论,由大前提、小前提和结论三部分组成。只要前提是真的,在推理形式合乎逻辑的条件下,运用演绎推理得到的结论必然是真实的。 (2) 在方法上要求使用辩证地分析和综合这两种思维方法。分析与综合是抽象思维的基本方法。分析是把事物的整体或过程分解为各个要素,分别加以研究的思维方法和思维过程。只有先对各要素作出周密的分析,才可能从整体上进行正确的综合,从而真正地认识事物。综合就是把分解开来的各个要素结合起来,组成一个整体的思维方法和思维过程。只有对事物各种要素从内在联系上加以综合,才能正确地认识整个客观对象。 (3) 在体系上,要求实现逻辑与历史的一致,达到理论与实践的具体历史的统一。历史是指事物发展的历史和认识发展的历史,逻辑是指人的思维对客观事物发展规律的概括反映,即历史的事物在理性思维中的再现。历史是第一性的,是逻辑的客观基础; 逻辑是第二性的,是对历史的抽象概括。 5.1类的重用 5.1.1类的继承和组合 51类的重用 面向对象中的继承思想接近于人类的思维方式,可以大大提高代码的可重用性和健壮性,提高编程效率,降低软件维护的工作量。 类的重用有两种实现方式: 继承和组合。 1. 继承 类的继承表达的是一般与特殊(IS A)的关系。那么,何时选择继承呢? 一个很好的判断方法为: “B是一个A吗?”,如果是,则让B做A的子类。 Java采用继承机制来组织、设计软件系统中的类和接口。在Java的单继承树状结构中,java.lang.Object是Java中所有类的直接父类或间接父类。除根父类外,每个子类只能有一个直接父类,除最终类之外的类都可以有多个子类。 直观地讲,父类更加抽象,子类更加具体。子类是对父类的扩展,子类是一种特殊的父类。父类(ParentClass)又被称为基类(BaseClass)、超类(SuperClass)。子类(Subclass/ChildClass)又被称为衍生类(Derived Class)。 Java采用“extends 父类名”的方式来实现单继承,采用“implements <接口列表>”的方式来实现多继承。 子类可以继承父类中未被private修饰的成员变量和方法,但不包括构造方法。在子类的类体中可以重写父类中的方法,增加新的属性和方法。所以,类除了自己的属性和方法外,还有从父类继承过来的属性和方法。 子类继承父类的语法格式如下: <类修饰符> class <子类名> [extends <父类名>][implements <接口列表>]{ … } 说明: 如果省略extends子句,编译器会自动加上extends java.lang.Object。 2. 组合和聚合 组合和聚合表达的是整体与部分(Have a)的关系。组合强调整体和部分之间具有很强的“属于”关系,且它们的生存期是一致的。聚合则强调一种松散的家族和成员之间的关系。 何时选择组合或聚合呢?一个很好的判断方法为“A有一个B吗?”,如果有,则让B做A的成员变量。 【示例程序51】类的组合示例(CombinationTest.java)。 功能描述: 本程序描述了汽车类Car,由1个引擎(Engine类)、4个车门(Door类数组)组合而成。 01class Engine { 02public void start() { 03System.out.println("------启动------"); 04} 05public void stop() { 06System.out.println("------停止------"); 07} 08} 09class Door { 10public void open() { 11System.out.println("------开门------"); 12} 13public void close() { 14System.out.println("------关门------"); 15} 16} 17class Car { 18Engine engine = new Engine(); 19Door[] doors = new Door[4]; 20} 21public class CombinationTest { 22public static void main(String[] args) { 23Car car = new Car(); 24car.engine.start(); 25for(int i=0; i<4; i++) { 26car.doors[i]=new Door(); 27} 28car.doors[0].close(); 29} 30} 5.1.2关键字this和super 1. 关键字this this指向当前对象,主要用来调用构造方法或访问成员变量(方法),语法格式如下。 (1) 在本类构造方法中调用其他构造方法,只能在第一行: this([实参数列表]); (2) 用于区分构造方法和对象方法中重名的对象变量和局部变量: this.成员变量; (3) 在构造方法和对象方法中调用其他对象方法(this可能省略): this.成员方法([实参数列表]); 说明: this()只能出现构造方法的第一行,在同一个构造方法中super()和this()不能同时出现。 2. 关键字super super 指向当前类的父类,主要用来调用父类的构造方法或访问父类的成员变量(方法),语法格式如下。 (1) 在本类构造方法中调用父类的构造方法,只能在第一行: super([实参数列表]); (2) 在构造方法或对象方法中访问父类中被覆盖的成员变量: super.成员变量; (3) 在构造方法或对象方法中访问父类中被覆盖的成员方法: super.成员方法([实参数列表]); 说明: (1) 调用父类的构造方法,只能出现在子类的构造方法中,且必须是第一行。 (2) super([实参数列表])中的参数,决定了调用父类哪个构造方法。如果子类构造方法中没有出现super([实参数列表]),那么编译器将自动加入super(),即调用父类的空构造方法。 5.1.3方法的覆盖 覆盖是指子类的成员方法的名称和形式参数与父类的成员方法的名称和形式参数相同的现象。 方法覆盖的规则如下。 (1) 一般在子类继承父类时发生。 (2) 3个相同: 方法名、形式参数、返回类型相同。 (3) 方法抛出的异常: 子类方法抛出的异常应比父类方法抛出的异常更小或相等,即子类方法只能抛出父类方法异常或其异常的子类。 (4) 方法的权限修饰符: 子类方法的访问权限要大于或等于父类方法的访问权限。 (5) static()方法只能被static()方法覆盖,非static()方法只能被非static()方法覆盖,不能交叉覆盖。 (6) final()方法不能被覆盖。 【示例程序52】方法覆盖应用示例(Ostrich.java)。 功能描述: 本程序中Ostrich类的fly()方法覆盖Bird类中的fly()方法。 01class Bird{ 02public void fly(){ 03System.out.println("--Bird: fly()--"); 04} 05} 06public class Ostrich extends Bird{ 07public void fly(){ 08System.out.println("--Ostrich: fly()--"); 09} 10public static void main(String[] args) { 11Ostrich o=new Ostrich(); 12o.fly(); 13Bird b=new Ostrich(); //上溯造型后 14b.fly(); 15} 16} 程序运行结果如下: --Ostrich: fly()-- --Ostrich: fly()-- 5.2语句块和对象的造型 5.2.1语句块 52语句块 和对象 的造型 关键字static用来区分成员变量、成员方法、内部类、初始化块是属于类的还是属于对象的。有static修饰的成员属于类,否则属于对象。 【拓展知识51】语句块的隐含调用。Java语句一般写在方法中,供其他程序调用。语句块写在类体中方法外,由JVM自动调用。 (1) 静态语句块指在类体中、方法外定义的有static修饰的语句块。静态语句块所在类被JVM载入内存后自动执行一次,负责类的初始化。 JVM要实例化一个类或访问某一个类中的方法时,需要先将该类载入内存。在将该类载入内存之前,JVM自动先将这个类的父类载入内存。正如要上三楼,需要先上一楼、二楼,再上三楼。类载入内存后,JVM自动执行该类的静态语句块。 (2) 非静态语句块指在类体中、方法外定义的未被static修饰的语句块。非静态语句在调用该类的构造方法前自动执行一次,用于初始化对象。调用一个类的构造方法,必须先调用其父类的构造方法。 JVM使用“new 构造方法()”的方式实例化一个类时,必须先实例化其父类。因为编译器为每个类的构造方法都自动加上了一句“super(); ”,实例化一个类之前,JVM自动执行该类的非静态语句块。 【示例程序53】子类构造方法自动调用父类构造方法测试(ConStructor CallTest.java)。 功能描述: 本程序中Wolf类继承了Animal类,Animal类继承了Creature类。主类的main()方法中只有一句new Wolf(),请写出程序运行结果,与标准答案对比。 01package chap05; 02class Creature extends java.lang.Object { 03public Creature() { 04System.out.println("------生物------"); 05} 06} 07class Animal extends Creature { 08public Animal() { 09System.out.println("------动物------"); 10} 11} 12class Wolf extends Animal { 13public Wolf() { 14System.out.println("-------狼-------"); 15} 16} 17public class ConStructorCallTest { 18public static void main(String[] args) { 19new Wolf(); 20} 21} 程序运行结果如下: ------生物------ ------动物------ -------狼------- 【示例程序54】静态和非静态语句块、父类构造方法的自动隐含调用测试(D.java)。 功能描述: 本程序中D类继承了B类,B类继承了A类。A类、B类、D类每个类中均包含了静态语句块、非静态语句块、构造方法,请写出调用D类的构造方法时自动执行的语句顺序(程序运行结果),与标准答案对比。 01class A extends java.lang.Object{ 02{ 03System.out.println("------调用A的非static语句块-----"); 04} 05static{ 06System.out.println("------调用A的static语句块-----"); 07} 08A(){ 09System.out.println("------调用A()-----"); 10} 11} 12class B extends A { 13static{ 14System.out.println("------调用B的static语句块-----"); 15} 16{ 17System.out.println("------调用B的非static语句块-----"); 18} 19B(){ 20System.out.println("------调用B()-----"); 21} 22} 23public class D extends B{ 24static{ 25System.out.println("------调用D的static语句块-----"); 26} 27{ 28System.out.println("------调用D的非static语句块-----"); 29} 30D(){ 31System.out.println("------调用D()-----"); 32} 33public static void main(String args[]){ 34D d=new D(); 35} 36} 程序运行结果如下: ------调用A的static语句块----- ------调用B的static语句块----- ------调用D的static语句块----- ------调用A的非static语句块----- ------调用A()----- ------调用B的非static语句块----- ------调用B()----- ------调用D的非static语句块----- ------调用D()----- 如果读者认为自己掌握了本节的内容,请通过5.7节自测题中的“二、程序运行题”验证自己掌握的程度。 5.2.2对象的上溯造型和下溯造型 子类其实是一种特殊的父类。把一个子类对象直接赋值给一个父类对象变量的语法现象称为上溯造型。把父类对象强制转换赋值给子类对象变量的语法现象称为下溯造型。 某个类的对象可以完成以下操作之一。 (1) 上溯造型为任何一个父类类型或父接口(自动完成): 即任何一个子类的变量(或对象)都可以被当成一个父类或父接口变量(或对象)来对待。例如: Shape s=new Circle(); List i = new ArrayList(); (2) 通过下溯造型回到它自己所在的类(强制转换): 一个对象被溯型为父类或父接口后,还可以再被下溯造型,回到它自己所在的类。只有曾经上溯造型过的对象才能进行下溯造型。对象不允许不经过上溯造型而直接下溯造型。否则运行时会抛出异常信息: java.lang.ClassCastException。 上溯造型的优点是: 可以把不同类型的子类上溯为同一个父类类型,便于统一处理它们。缺点是: 损失了子类新扩展增加的属性和方法(覆盖父类的方法不会损失),除非再进行下溯造型,否则这部分属性和方法不能被访问。 Java的引用变量有两种类型,一种是编译时类型,另一种是运行时类型。编译时类型由声明该变量时使用的类型决定,运行时类型由实际赋给该变量的对象决定。如果编译时类型和运行时类型不一致,就会出现多态。 【示例程序55】引用类型上溯下溯造型示例(CastUpDownTest.java)。 功能描述: Fish类继承了Animal类。Fish对象可以自动上溯造型为Animal对象,然后下溯造型为Fish对象。本程序演示了在这个过程中方法的损失情况。 01class Animals { 02void breathe() { 03System.out.println("…动物呼吸…"); 04} 05//final方法不能被子类覆盖 06final static void live(Animals an){ 07an.breathe(); 08} 09} 10class Fish extends Animals { 11void swim() { //增加新的方法 12System.out.println("…鱼会游泳…."); 13} 14@Override//覆盖父类的方法 15void breathe() { 16System.out.println("…鱼用鳃呼吸…"); 17} 18} 19public class CastUpDownTest { 20public static void main(String args[]) { 21// Fish对象上溯为Animal对象,将丢失增加的新方法swim() 22Animals an = new Fish(); 23//24行找不到 swim(),编译出错 24// an.swim(); 25// 上溯造型时,覆盖父类的方法不会损失 26an.breathe(); 27// 下溯造型后,swim()又能被访问了 28Fish f = (Fish)an; 29f.swim(); 30} 31} 程序运行结果如下: …鱼用鳃呼吸… …鱼会游泳… 5.3最终类、抽象类和接口 5.3.1最终类 53最终类、 抽象类 和接口 final关键字可以作为变量修饰符、方法修饰符和类修饰符。 (1) final用在变量前面,该变量成为常量,只能被赋值一次。 (2) final用在方法前面,该方法成为最终方法,不能被子类的方法覆盖。 (3) final用在类前面,该类成为最终类,只能实例化,不能被继承。 5.3.2抽象类 abstract关键字有以下两种用法。 (1) abstract用作方法修饰符,表示该方法为抽象方法。抽象方法只有方法的定义(方法首部),没有方法的实现(方法体)。例如: public abstract double getArea(); (2) abstract用作类修饰符,则该类为抽象类。包含抽象方法的类必须声明为抽象类。 注意: (1) 抽象类不能被实例化为对象,只能被其他子类继承。 (2) 抽象方法不能为static。 在下列情况下,一个类必须声明为抽象类。 (1) 当一个类包含抽象方法时。 (2) 当一个类继承了一个抽象类,并且没有实现父类的所有抽象方法,即只实现了部分抽象方法时。 (3) 当一个类实现了一个接口,并且没有实现接口中所有的抽象方法时。 【示例程序56】类继承抽象类应用示例(AbstractTest.java)。 功能描述: 本程序定义了抽象类AShape,Square类继承了抽象类AShape,类AbstractTest进行了测试。 01package chap05; 02abstract class AShape{ 03//抽象方法只有方法的声明,没有方法的实现 04public abstract double getArea(); 05public abstract double getPerimeter(); 06} 07//类Circle继承了抽象类AShape就必须实现AShape中的所有抽象方法 08//否则Circle只能声明为抽象类 09class Circle extends AShape{ 10private double r; 11// 无参构造方法 12public Circle(){ 13super(); 14} 15//有参数的构造方法 16public Circle(double r){ 17super(); 18this.r=r; 19} 20// 实现继承抽象类AShape中的抽象方法 21@Override 22public double getArea(){ 23return r*r; 24} 25@Override 26public double getPerimeter(){ 27return 2*Math.PI*r; 28} 29// 通过覆盖toString()方法,定制自己的输出信息 30@Override 31public String toString(){ 32//父类中的toString()默认输出信息: chap05.Circle@7637f22 33return "Circle["+r+"]"; 34} 35// private变量的Getters、Setters方法 36public double getR(){ 37return r; 38} 39public void setR(double r){ 40this.r = r; 41} 42} 43public class AbstractTest{ 44public static void main(String[] args){ 45Circle s1 = new Circle(); 46s1.setR(9); 47Circle s2 = new Circle(9); 48System.out.println(s1.getArea()); //调用对象方法 49System.out.println(s1.getPerimeter()); 50System.out.println(s2); 51System.out.println(s2.toString()); 52} 53} 5.3.3接口 接口本质上是一个比抽象类更加抽象的类,接口中只能定义常量和抽象方法。抽象类只能对其子类的行为进行约束。但接口可以对实现本接口的所有类进行约束,范围更加广泛。 【拓展知识52】类、抽象类和接口。类是具体的,可以实例化。抽象类是包含抽象方法的类,只能被继承,不能被实例化。接口更加抽象,只能包含常量和抽象方法。接口是常量和抽象方法的集合。接口不能被实例化,只能被继承(extends)或实现(implements)。 1. 接口的定义 语法格式如下: [接口修饰符] interface 接口名称 [extends 父接口列表]{ //常量定义 //抽象方法的定义 } 说明: (1) 接口可以通过extends关键字继承其他接口,一个接口可以继承多个接口,这点与类的继承有所不同。 (2) 常量前如果没有public、static、final修饰符,编译器会自动加上。 (3) 接口中的抽象方法前会被编译器自动加上abstract关键字。 2. 类实现接口 定义好接口后,其他类就可以用implements关键字实现该接口,接受该接口的约束。类实现接口的语法如下: [类修饰符] class 类名 [extends 父类名] [implements <接口列表>]{ … … //实现接口中的抽象方法 } 说明: (1) 实现接口的类必须实现该接口中的所有抽象方法,只要有一个抽象方法没有实现,该类就只能被声明为抽象类。 (2) 在实现接口的时候,来自接口的方法必须声明成public。 【示例程序57】类实现接口和继承抽象类示例(Person.java)。 功能描述: 本程序中Person 类继承了司机抽象类(Driver),实现了教师接口(ITeacher)和父亲接口(IFather)。一个人拥有多个角色,既是司机、又是教师和父亲。Person 类必须接受其父类和父接口的约束,即实现其父类和父接口中的抽象方法。 01interface ITeacher { 02public void teach(); 03} 04interface IFather { 05public void earnMoney(); 06} 07abstract class Driver { 08public abstract void drive(); 09} 10public class Person extends Driver implements IFather,ITeacher{ 11@Override 12public void teach() { 13System.out.println("---------教书育人----------"); 14} 15@Override 16public void earnMoney() { 17System.out.println("---------挣钱养家----------"); 18} 19@Override 20public void drive() { 21System.out.println("---------开车上路----------"); 22} 23public static void main(String[] args) { 24Person p=new Person(); 25p.teach(); 26p.earnMoney(); 27p.drive(); 28} 29} 5.4异常处理机制 54异常处 理机制 异常(Exception)是指程序运行过程中出现的可能会打断程序正常执行的事件或现象。例如,用户输入错误、除数为零、文件找不到、数组下标越界、内存不足等。为了加强程序的健壮性,编写程序时必须考虑到可能发生的异常事件并做出相应的处理。 面向过程语言中用if语句实现程序的错误处理,业务逻辑代码和异常处理代码交叉在一起,程序阅读和维护都不太方便。Java采用面向对象的异常处理机制,用try…catch…finally语句将程序中的业务逻辑代码和异常处理代码有效分离,便于程序的阅读、修改和维护。 【拓展知识53】异常处理机制。异常处理机制的完善性已经成为判断一门编程语言是否成熟的标准之一。它可以使程序中异常处理代码和正常业务代码分离,使得程序代码更加优雅,并提高了程序的健壮性。 5.4.1方法调用堆栈 JVM在执行字节码文件时,方法调用堆栈中详细记录了每一层方法调用的信息。程序运行过程中一旦发生异常,就会中止运行,并在控制台上输出方法调用堆栈中的信息。方法调用堆栈信息包括异常类型、每一层方法调用时异常发生所在类、方法、代码行等信息。通过查看方法调用堆栈信息,可以迅速定位异常发生位置,该信息是调试和修改程序的重要依据。 【示例程序58】程序发生异常时,控制台输出的方法调用堆栈信息(MethodCallTest.java)。 功能描述: 本程序演示了当程序运行过程中发生异常时,自动在控制台上输出方法调用堆栈的信息。 01public class MethodCallTest{ 02int[] arr = new int[3]; 03public void methodOne(){ 04methodTwo(); 05System.out.println("One"); 06} 07public void methodTwo(){ 08methodThree(); 09System.out.println("Two"); 10} 11public void methodThree(){ 12System.out.println(arr[3]); 13System.out.println("Three"); 14} 15public static void main(String[] args){ 16new MethodCallTest().methodOne(); 17System.out.println("main"); 18} 19} 控制台输出信息如下: Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3 at chap05.MethodCallTest.methodThree(MethodCallTest.java: 12) at chap05.MethodCallTest.methodTwo(MethodCallTest.java: 8) at chap05.MethodCallTest.methodOne(MethodCallTest.java: 4) at chap05.MethodCallTest.main(MethodCallTest.java: 17) 5.4.2Exception的概念、子类及其继承关系 在Java中,异常情况分为Error 和Exception两大类。 (1) Error类: 指较少发生的内部系统错误,由JVM生成并抛出,包括动态链接失败、JVM内部错误、资源耗尽等严重情况,程序员无能为力,只能让程序终止。 (2) Exception类: 由程序本身及环境所产生的异常,有补救或控制的可能,程序员可预先防范,增加程序的健壮性。 Java中的Exception可以分为以下两大类。 (1) 编译时异常: 也称为检查性异常,Java编译器要求Java程序必须通过捕获或声明所有的检查性异常的方式进行异常处理,否则不能通过编译。在Java中,Exception类中除了RunTimeException类及其子类外都是编译时异常。IOException是典型的编译时异常,IOException及其子类的继承结构如图51所示。 (2) 运行时异常(RuntimeException): 指可以通过编译,只有在JVM运行时才能发现的异常,如被0除、数组下标超范围等。运行时异常的产生比较频繁,处理较为麻烦,对程序可读性和运行效率影响不太大。因此用户可以检测也可以不检测。RuntimeException及其子类的继承结构如图52所示。 图51IOException及其子类的继承结构 图52RuntimeException及其子类的继承结构 5.4.3Java异常处理机制 Java异常处理机制主要通过以下两种方式来处理异常。 (1) 使用try…catch…finally语句对异常进行捕获和处理。 (2) 在可能产生异常的方法定义首部用throws声明抛出异常。JVM将类载入内存后调用main()方法,main()方法再调用其他方法。在Java中,采用“谁调用,谁负责处理”的异常处理机制。 1. try…catch…finally语句 语法格式如下: try{ …//可能产生异常的代码 }catch(异常类型1 异常对象1){ …//异常处理代码 }catch(异常类型2 异常对象2){ …//异常处理代码 }[finally{ …//不管异常发生与否都执行的代码 }] 说明: (1) try语句块中包围的是可能产生异常的语句。 (2) finally子句是try…catch的统一出口,一般用来处理“善后工作”。可以把不管异常发生与否都要执行的代码放到这里,例如,关闭数据库连接、关闭Socket连接、关闭文件流等代码。 (3) 一个try语句块可以对应多个catch块,用于对多个异常类进行捕获。如果要捕获的异常类之间有父子继承关系时,应该将子类的catch块放置在父类的catch块之前。 (4) try…catch…finally语句可以嵌套。 2. 在方法首部throws声明抛出异常 如果一个方法没有捕获可能引发的异常,就必须在方法首部声明可能发生异常,交由调用者来处理异常。throws的语法格式如下: throws <异常类型列表> 【示例程序59】try…catch…finally语句测试(TryCatchFinally.java)。 功能描述: 本程序演示了try…catch…finally语句的使用,阅读程序时要注意: 程序发生异常时直接跳转到相应的catch子句,后面的代码将不会被执行; 当有多个catch子句时,捕获的异常子类应在前,父类在后; 无论发生异常与否,finally子句中的代码都会被执行。 01public class TryCatchFinally{ 02public static void proc(int mode){ 03try{ 04if(mode==0){ 05System.out.println("没有异常发生!"); 06}else{ 07int j=4/0; //出现ArithmeticException,直接跳转到12行 08System.out.println("因08行出现异常,本行不被执行!"); 09} 10}catch(ArithmeticException e ) { 11System.out.println("捕获到ArithmeticException异常!"); 12}catch(Exception e ) { 13System.out.println("捕获到Exception异常!"); 14}finally{ 15System.out.println("无论发生异常与否都要执行!"); 16} 17} 18public static void main( String args[] ){ 19proc(0); //没有异常发生 20proc(1); //有异常发生 21} 22} 程序运行结果如下: 没有异常发生! 无论发生异常与否都要执行! 捕获到ArithmeticException异常! 无论发生异常与否都要执行! 5.5综合实例: 编写平面图形程序,理解抽象类和接口 5.5.1案例背景 面向对象思想更加贴近人类的行为模式。例如平面几何图形,如三角形、矩形、圆等,当不确定具体是什么平面图形时,只能要求计算周长和面积; 当确定是某种平面图形后,就可以给出具体求周长和面积的算法。 在面向对象中,接口和抽象类不能被实例化为对象,只能被其他子类继承。 抽象类是包含抽象方法的类。抽象类可以约束所有继承它的类,即: 继承抽象类的类必须实现抽象类中所有的抽象方法,否则它也只能是抽象类。一个类只能继承一个抽象类。 接口是抽象方法和常量的集合,可以约束所有实现的类。一个类可以实现多个接口。实现接口的类必须实现接口中的所有抽象方法,否则它只能是抽象类。 5.5.2编程实践 可以把平面图形类设计成抽象类AShape,甚至更抽象的接口IShape,把计算周长和面积的方法设计成抽象方法。平面图形(Triangle类、Rectangle类、Circle类等)继承AShape抽象类或实现IShape接口。Triangle类、Rectangle类、Circle类必须实现所有抽象方法,即编写计算周长和面积的算法。 【示例程序510】平面图形抽象类(CShape.java)。 功能描述: 抽象类CShape包含两个抽象方法: getArea()和getPerimeter()。 01public abstract class CShape { 02//抽象方法 03public abstract double getArea(); 04public abstract double getPerimeter(); 05} 【示例程序511】平面图形接口(IShape.java)。 功能描述: 接口IShape包含两个抽象方法: getArea()和getPerimeter()。 01public interface IShape { 02//接口的方法只能是抽象方法,自动添加public abstract 03double getArea(); 04double getPerimeter(); 05} 【示例程序512】三角形类(Triangle.java)。 功能描述: 包含3个属性、构造方法、Getter()和Setter()方法、覆盖toString()方法、实现了抽象类CShape包含的两个抽象方法: getArea()和getPerimeter()。 01public class Triangle extends CShape{ 02private double a; 03private double b; 04private double c; 05public Triangle() { 06} 07public Triangle(double a, double b, double c) { 08this.a = a; 09this.b = b; 10this.c = c; 11} 12@Override//实现父类CShape中的抽象方法 13public double getArea() { 14double l=(a+b+c)/2; 15double s=Math.sqrt(l*(l-a)*(l-b)*(l-c)); 16return s; 17} 18@Override//实现父类CShape中的抽象方法 19public double getPerimeter() { 20return a+b+c; 21} 22@Override//覆盖父类Object中的抽象方法 23public String toString() { 24return "Triangle["+a+","+b+","+c+"]"; 25} 26public double getA() { 27return a; 28} 29public void setA(double a) { 30this.a = a; 31} 32public double getB() { 33return b; 34} 35public void setB(double b) { 36this.b = b; 37} 38public double getC() { 39return c; 40} 41public void setC(double c) { 42this.c = c; 43} 44} 【示例程序513】矩形类(Rectangle.java)。 功能描述: 包含了两个属性、构造方法、Getter()和Setter()方法、覆盖了toString()方法、实现了抽象类CShape包含的两个抽象方法: getArea()和getPerimeter()。 01public class Rectangle extends CShape{ 02private double w; 03private double h; 04public Rectangle() { 05} 06public Rectangle(double w, double h) { 07this.w = w; 08this.h = h; 09} 10@Override//实现父类CShape中的抽象方法 11public double getArea() { 12return w*h; 13} 14@Override//实现父类CShape中的抽象方法 15public double getPerimeter() { 16return 2*(w+h); 17} 18@Override//覆盖父类Object中的抽象方法 19public String toString() { 20return "Rectangle["+w+","+h+"]"; 21} 22public double getW() { 23return w; 24} 25public void setW(double w) { 26this.w = w; 27} 28public double getH() { 29return h; 30} 31public void setH(double h) { 32this.h = h; 33} 34} 【示例程序514】圆形类(Circle.java)。 功能描述: Circle类实现了接口IShape。包含一个属性、构造方法、Getter()和Setter()方法、覆盖了toString()方法、实现了接口IShape包含的两个抽象方法: getArea()和getPerimeter()。 01public class Circle implements IShape{ 02private double r; 03public Circle() { 04} 05public Circle(double r) { 06this.r = r; 07} 08@Override 09public double getArea() { 10return Math.PI*r*r; 11} 12@Override 13public double getPerimeter() { 14return 2*Math.PI*r; 15} 16 17@Override 18public String toString() { 19return "Circle["+r+"]"; 20} 21public double getR() { 22return r; 23} 24public void setR(double r) { 25this.r = r; 26} 27} 5.6本章小结 本章介绍了面向对象的高级部分,包括类的继承和组合,方法的覆盖,静态和非静态语句块,对象的上溯造型和下溯造型,抽象类,接口,异常处理机制等内容。 经过本章的学习,读者已经基本完成面向对象编程的学习,能够应用Java语言解决一定的实际问题。 5.7自测题 一、 选择题 1. Java采用 “<父类>”的方式来实现单继承,采用“<接口列表>”的方式来实现多继承。 2. 关键字指向当前类的对象,关键字指向当前类的父类。 3. 关键字用于标识成员变量、成员方法、内部类、初始化块等成员是属于类的还是属于对象的。 4. 指在类体中、方法外定义的有修饰的语句块,当其所在类被JVM载入内存时,自动执行一次,负责 的初始化。要将一个类载入内存,必须先载入其。 5. 块指在类体中、方法外定义的语句块,当调用实例化对象之前,JVM会自动执行一次,用于的初始化。要调用一个类的构造方法,JVM会自动先调用的构造方法。 6. final用在变量前面,该变量成为,只能被赋值一次。final用在方法前面,该方法成为,不能被子类的方法覆盖。final用在类前面,该类成为,只能实例化,不能被继承。 7. 关键字修饰的方法为抽象方法(只有方法的定义,没有方法的实现)。含有抽象方法的类必须声明为类。 8. 本质上是一个比 更抽象的类。在接口中只能定义 和。 9. 经过多次的上溯造型和下溯造型后,当不能确定某个对象是不是某个类的对象时,可以使用运算符来判断。 10. 关键字private 修饰的成员的可见范围是: ,没有权限修饰符成员的可见范围是: ,关键字protected 修饰的成员的可见范围是: ,关键字public 修饰的成员的可见范围是所有包中所有类。 11. 在Java中,可以用……结构对异常进行捕获和处理。也可以在可能产生异常的方法定义首部用声明抛出异常。 12. 程序可能发生异常时,应该把不管异常发生与否都执行的代码放到子句中。 二、 程序运行题 写出下列程序的运行结果。 01class A { 02int i = 9, j; 03public A() { 04prt("i=" + i + ",j=" + j); 05j = 10; 06} 07static { 08int x1 = prt("A is superclass."); 09} 10static int prt(String s) { 11System.out.println(s); 12return 11; 13} 14} 15public class B extends A { 16int k = prt("B is key."); 17public B() { 18prt("k=" + k + ",j=" + j); 19} 20static int x2 = prt("B is childclass."); 21public static void main(String args[]) { 22prt("A is key."); 23B is = new B(); 24} 25} 三、 编程实践 1. 类的继承应用示例程序(ExtendsTest.java)。 编程要求: (1) Vehicle车辆类,受保护的属性有wheels(车轮个数)和weight(车重)。 (2) Car汽车类是Vehicle类的子类,属性loader(可载人数)。 (3) Truck卡车类是Vehicle类的子类,属性payload(载重)。 (4) 要求生成无参构造方法和包含所有属性的构造方法,要求自定义对象输出信息。 (5) 生成每一个类的对象,并测试相关方法。 2. 接口应用示例程序(ImplementsTest.java)。 编程要求: (1) Biology生物接口中定义了breathe()抽象方法(用输出语句模拟即可)。 (2) Animal动物接口继承了Biology接口,增加eat()和sleep()两个抽象方法。 (3) Human人类接口继承了Animal接口,增加think()和learn()两个抽象方法。 (4) 定一个普通人类Person实现Human接口,并进行测试。 55游戏团 队战斗 力统计 3. 游戏团队战斗力统计程序(GameRoleTest.java)。 编程要求: (1) Role角色类是所有职业的父类,包含受保护的属性: roleName(角色名字),public int getAttack()(返回角色的攻击力,因角色不具体,只能定义成抽象方法)。 (2) Magicer法师类继承了Role类,包含私有属性: name(姓名),grade(魔法等级1~ 10)。方法: public int getAttack()(返回法师的攻击力,法师攻击力=魔法等级*5)。提供私有属性的Getters/Setter()方法。 (3) Soldier战士类继承了Role类,包含私有属性: name(姓名),attack(攻击力); 提供私有属性的Getters/Setter()方法。 (4) Team 团队类,一个团队包括一个法师、若干战士(最多6个,用Soldier数组实现)。私有属性: num(战士实际个数)。定义了两个方法: public boolean addMember(Solider s)方法(当战士的实际个数不超过6个时,将s赋值给第num个数组元素),提供私有属性的public int attackSum()方法(返回该团队的总攻击力)。 (5) 根据以上描述创建相应的类,并编写相应的测试代码。