面向对象编程 Java是一门纯面向对象的语言。本章将正式步入面向对象程序开发中,说明面向对 象程序设计的基本理论、类、对象、继承、重载、覆盖和多态等特征,实现使用Java语言进 行面向对象的程序设计。 3.1 面向对象程序设计思想 面向对象是一种将对象(Object)、类(Class)、封装(Encapsulation)、继承 (Inheritance)、抽象(Abstract)、聚合(Aggregation)、消息传递(Communication with Messages)和多态(Polymorphism)等概念运用到软件开发中的方法。它实现了软件工程 中重用性、灵活性和扩展性的3个目标,所以被广泛应用到软件开发的各个阶段,包括面 向对象分析(OOA,ObjectOrientedAnalysis),面向对象设计(OOD,ObjectOriented Design)和面向对象程序设计(OOP,ObjectOrientedProgramming)。 1. 面向对象程序设计 面向对象程序设计是一种以“对象”为中心的程序语言模型,主要包括对象、类、数据 抽象、数据封装、继承、动态绑定、多态性、消息传递等概念,使面向对象的思想得到了具体 体现。面向对象开发领域流行的一句话是“万物皆对象”,即软件是由各种对象构成的一 个动态运行的系统。每个对象都属于某一类特定类型,不同类型决定了属于它的对象可 以接收不同的消息。 (1)类(Class)。 用于定义某类事物的抽象特征和行为。通俗地说,类定义了某类事物的属性和它所 具有的行为(或方法)。例如,在教学管理系统中,常常会设计“学生”类,类中包含“学号” “姓名”“入学时间”“年级”和“专业”等属性,同时也会设计“入学注册”“选课”等基本行为。 在设计阶段,类一般采用统一建模语言(Unified ModelingLanguage,UML)图来描述。 类由3部分组成:类名、属性和方法,如图3-1所示。其中左边为类的一般描述,右边为 Student类的UML描述。 类是面向对象程序设计的基础,通过提供类机制,供开发人员构建适合应用所需的类 型。面向对象程序设计中的核心问题是类从哪里来,类的属性和行为如何决定。实际上, 类来源于对应业务领域的分析,从具体用例(UseCase)中提取出来。类的属性和方法则 3 63 图3-1 类图 根据业务领域的应用范围来决定。例如,“学生”类中一般会设计“性别”属性,但是作为教 学管理系统,一般与“性别”没有紧密的关系或应用,所以教学管理系统中“学生”类可以不 设计“性别”属性。 (2)对象(Object)。 对象是类的实例(Instance),是一个动态的概念,用于构成系统的具体执行逻辑。例 如,“学生”类定义了教学管理系统中学生的抽象特征,是一个用来声明具体对象的类型。 “李凯”是一个具体的学生,他是2020年入学的软件工程专业的一年级学生。他具有“学 生”类中各个属性对应的值。所以,“李凯”是“学生”类的一个实例,他的属性值被称作“状 态”。实际上,当用户编写的程序运行时,JVM会为对象动态地分配内存空间。因此,类 是抽象的,即用户自定义的数据类型,对象则是具体的,是类型的变量。 在面向对象软件开发中,软件开发人员首先从问题领域中寻找对象,并将同类对象的 共有属性抽取出来,设计成类(即所说的抽象成类),然后将类实例化为对象,由各种不同 类型对象的相互作用构成软件。 (3)消息传递(MessagePassing)。 对象通过接受消息、处理消息、传出消息或使用其他类的方法来实现系统功能。对象 的相互作用实现了系统的业务功能。例如,“李凯”发出“选课”消息。 2.面向对象程序设计的特征 面向对象的软件开发过程更适于设计大型和复杂的系统,主要原因是实现了数据和 功能的封装,易于代码维护和复用。面向对象程序设计包括3个主要特征:封装 (Encapsulation)、继承(Inheritance)和多态(Polymorphism )。 (1)封装。 将数据和方法封装在一个类中,对数据实现了隐藏,对公开方法通过消息传递实现调 用(自身的具体实现,实现了隐藏),并使程序的调试实现了局部化的目标。例如,“李凯” 具有“选课”的方法,程序设计中实现“选课”功能时,只需给“李凯”这个对象传递“选课”消 息,而不需要知道具体的“选课”流程。在程序调试过程中,一旦选课的业务不符合客户的 逻辑需求,只需要修改“选课”方法的内部逻辑,实现调试的局部化。 (2)继承。 是一种数据和方法共享的机制,实现了代码重用、易扩展的目标;在软件开发中,业务 64 系统中的类会出现一种概念分层的情况,例如“教学管理系统”中出现了“本科生”“研究 生”和“预科生”等学生类别,可以将他们抽象出一张类图,如图3-2所示。其中“学生”称 为父类,具有更加普遍意义上的属性和方法;“本科生”“研究生”和“预科生”称为子类,比 父类具有要更加具体化的属性和方法。 图3-2 继承类图 (3)多态性。 在程序执行时,对象会根据当前具体对应的消息执行的一种机制。实现了不同对象 的消息执行不同动作的过程,具有动态绑定的灵活性,将面向对象开发技术具有的这一特 征称为多态。多态性的实现可以通过“类”或“接口”实现。例如,在“学生”示例中,对于学 生(Student类)对象,向其发送“毕业检查”(checkgraduate)的消息,当该对象的具体实例 是“研究生”时,将执行研究生的“毕业检查”;是“预科生”时,将执行预科生的“毕业检查”。 这就是使用类实现的多态过程。 3.2 类 3.2.1 类的定义 根据前面的知识,写程序时首先要写类。为什么面向对象不首先写对象,而是写类 呢? 实际上,面向对象程序设计方法写程序前,首先是要对研究问题的领域进行对象寻 找,然后再把具有相同或相似属性和行为的对象抽象成一个类。 Java语言对类的一般定义格式如下。 []class [extends superclass] [implements interfacea,...]{ [] //定义属性 [] //定义构造方法 [] //定义方法 } 类定义包括如下内容。 (1)modifiers:修饰符。修饰符包含访问修饰符和非访问修饰符。访问修饰符可以 是public、private和默认(default)等值,用于说明类的访问权限。非访问修饰符可以是 65 abstract、final或static,用于说明定义的类的特性。 (2)class:类定义。class为类关键字,class_name为类名,是一个由 用户定义的合法Java标识符。按照命名规范,类名的首字符一般大写。 (3)extendssuperclass:类继承。extends为关键字,superclass为父类标识符。 (4)implementsinterfacea,...:接口实现列表。implements为关键字,interfacea为 接口标识符。 (5){}:为类结构体。 3.2.2 属性 完成类结构的定义后,要进行类属性的定义。类的属性定义包括访问修饰符、类型和 标识符。格式如下。 []; (1)accessmodifiers。 指属性访问修饰符。public、protected、private和default。为了实现类的封装特性, 一般情况下属性的访问修饰符设为private,即只有类自身可以访问它,其他类都不能访 问。为了能够让这些属性被访问,需要设置public的方法。这将在方法定义中说明。 (2)type。 类型名称。既可以是基本类型名,也可以是复合类型名。 (3)attribute_name。 属性名称。是合法的Java标识符。 【例3-1】 根据图3-3所示的Student(学生)类,使用Java语言描述属性部分。 图3-3 学生类结构 class Student{ private String sid; //学号 private String sname; //姓名 66 private String sclass; //班级 private String specialty; //专业 private String college; //学院 private Grade mark; //Grade 用户自定义成绩类,成绩 } 【例3-2】 学生的成绩类包括学号、课名、学分和成绩,使用Java语言描述。 class Grade{ private String sid; //学号 private String cname; //课名 private float credit; //学分 private float score; //成绩 } 上面介绍的属性是必须实例化后才能访问的属性,称为成员变量(fields),还有一种 属性是不需要实例化就可以使用的属性,而且是一种共享变量,称为类变量(class variable)。定义类变量的办法就是使用static修饰符对变量进行修饰。 【例3-3】 定义一个共享变量。 public static int total =0; 还可以使用final修饰符定义类间共享的符号常量。 【例3-4】 假设学生毕业的最低学分是210分,这个数据被所有学生对象共享,而且 不能修改,所以,可以定义为类的符号常量。 class Student{ public final static int CREDIT_MIN =210; private String sid; //学号 private String sname; //姓名 private String sclass; //班级 private String specialty; //专业 private String college; //学院 private Grade mark; //Grade 用户自定义成绩类,成绩 } 3.2.3 方法 1. 方法的定义 方法是类向外提供的接口,也可以为类内部使用提供服务。方法的定义如下。 [](parameter lists) [throws exception lists]{ //method body (方法体); } 67 每个方法包括以下6部分中的几部分。 (1)modifiers:访问修饰符,包括public、protected、private和default。 (2)return_type:返回类型,方法运行后返回值的数据类型,可以是任何有效的类 型,包括全部基本类型和复合类型,如果方法没有返回值,则返回类型为void。 (3)method_name:方法名,符合规范的标识符。 (4)parameterlists:参数列表,表示传输的数据,包括数据类型和方法变量,使用“,” 对多个参数进行分割,方法定义时也可以没有参数。 (5)exceptionlists:异常列表,在第7章中介绍。 (6)方法体:方法的功能定义。 方法的使用根据定义也分为两种:一种是必须被实例化后才能使用的方法,称为成 员方法;另一种是通过类名进行访问的方法,称为类方法。使用static进行定义。下面演 示成员方法的定义,类方法将在3.7.1节中详细介绍。 【例3-5】 实现一个将成绩类对象中的属性逐一输出显示的方法。 程序Grade.java如下。 public class Grade { private String sid; private String cname; private float credit; private float score; public void showInfo() { System.out.println("sid=" +sid +", cname=" +cname +", credit=" + credit +", score=" +score ); } 【例3-5】中定义的showInfo方法没有返回值,因此方法类型为void,方法无参数,小 括号中的内容为空,方法体为一条输出语句,输出对象的属性及对应的属性值。 由于类将属性进行了封装,特别是将属性设置为私有类型(private),其他对象无法通 过“对象名.属性名”的方式访问它们,所以需要设置方法提供对它们的访问。对它们的访 问包括两类方法:读取和设置属性的值。为了方便用户定义与使用,Java定义了统一的 方法名。读取属性值为getXxx(),设置属性值为setXxx()方法。 【例3-6】 对成绩类属性进行读取和设置方法的定义。 程序Grade.java如下。 public class Grade { private String sid; private String cname; private float credit; private float score; public void showInfo() { System.out.println("sid=" +sid +", cname=" +cname +", credit=" + credit +", score=" +score ); 68 } public String getSid() { return sid; } public void setSid(String sid) { this.sid =sid; } public String getCname() { return cname; } public void setCname(String cname) { this.cname =cname; } public float getCredit() { return credit; } public void setCredit(float credit) { this.credit =credit; } public float getScore() { return score; } public void setScore(float score) { this.score =score; } } 2. 不定参数个数的方法定义 使用方法时,常有一些不定参数个数的广义方法的使用。如C 语言中printf("% d,%s",a,b)的使用,根据格式参数来决定对应参数的个数使用。Java也提供了这种方 法的定义。参数的定义使用3个点(…),格式为(类型…参数变量)。 【例3-7】 不定参数个数方法的定义。 程序TestMethod.java如下。 class TestMethod{ static void myprint(String format,Object …objects){ //不定参数个数的方法定义 String temp =format; String result =""; int index =0; int num =0 ; while((index =temp.indexOf('%'))>=0 && index ]class extends superclass { [] [] [] } 其中,extends就是类继承的关键字。创建一个新类时,系统中已经存在一个具有新 80 类的部分属性和方法的类,可以使用继承创建这个新类。这样就可以复用已经定义的属 性和方法。 【例3-18】 在实际成绩管理中,成绩不仅包括百分制,还有二分制和五分制。其中 二分制的成绩为“不及格”和“及格”;五分制的成绩为“不及格”“及格”“中”“良”和“优秀”。 那么成绩类如何设计? (1)解决方案1:修改成绩类。 根据前面定义的成绩类,成绩类包括的属性为学号、课程名、学分和成绩。为了实现 二分制、五分制和百分制,修改成绩类,增加成绩类型属性。1表示百分制;2表示二分制; 3表示五分制。其中成绩在百分制下使用0~100的实数;二分制下使用0~1的实数;五 分制使用0~4的实数。 class Grade{ public final static int CENTI_SCALE =1; public final static int BINARY_SCALE =2; public final static int FIVE_SCALE =3; private String sid; //课程编号 private String cname; //课程名 private float credit; //学分 private int gType; //成绩类型 private float score; //成绩 //构造方法 . //读取成绩 public String getScore() { String result =null; switch(gType){ case CENTI_SCALE: result =Float.toString(score); break; case BINARY_SCALE: if(score ==0){ result ="不及格"; }else{ result ="及格"; } break; case FIVE_SCALE: if(score ==0){ result ="不及格"; }else if(score ==1){ result ="及格"; }else if(score ==2){ result ="中"; 81 }else if(score ==3){ result ="良"; }else if(score ==4){ result ="优秀"; } break; } return result; } } 成绩类的使用,如姚明参加了“高数”“IT日语”和“JavaEE框架技术”课程的学习(其 中“高数”是百分制,公共选修课“IT 日语”是二分制,选修课“JavaEE框架技术”是五分 制),下面实例化这些课程的成绩对象,并使用它们。 class TestGrade{ public static void main(String[]args){ Grade grade; //编号“01”,高数,6 学分,百分制,89 分 grade =new Grade("01","高数",6, Grade.CENTI_SCALE,89); System.out.println("成绩"+grade.getScore()); //编号“02”,IT 日语,3 学分,二分制,1-表示“及格” grade =new Grade("02","IT 日语",3, Grade.BINARY_SCALE,1); System.out.println("成绩"+grade.getScore()); //编号“03”,Java EE 框架技术,4 学分,五分制,3-表示“良” grade =new MyGrade("03","Java EE 框架技术",4, Grade.FIVE_SCALE,3); System.out.println("成绩"+grade.getScore()); } } (2)解决方案2:使用继承。 在原有类型的基础上,继承实现二分制和五分制的成绩表示,百分制仍然由原来的成 绩类来表示。类的继承模型如图3-6所示。 class BinaryGrade extends Grade { //二分制成绩类 private String biscore; public String getBiscore() { return biscore; } public void setBiscore(String biscore) { this.biscore =biscore; } }c lass FiveGrade extends Grade { //五分制成绩类 private String fscore; public String getFscore() {