• 沒有找到結果。

第三章 面向对象基本特性

N/A
N/A
Protected

Academic year: 2021

Share "第三章 面向对象基本特性"

Copied!
29
0
0

加載中.... (立即查看全文)

全文

(1)

第三章 面向对象基本特性

面向对象程序设计是一种先进的编程思想,更加容易解决复杂的问题。面向对象系统最 突出的特性是封装性、继承性和多态性。

学习完本章节,您能够:

l 理解封装的概念。

l 实现对象封装特性。

l 实现继承特性。

l 使用接口。

l 掌握对象的多态性。

任务 3.1 保护个人数据

3.1.1  情境描述 

Tom 设计的 A 类员工类,外部可以直接访问它的成员变量,在现实社会中,一个对象的 某些属性外界是不可以得知的,因此需要针对个人的数据成员进行隐藏保护。为了保护  A  类 员工的个人数据,他需要完成以下任务:

(1)设置访问区分符。

(2)设置 getter 和 setter。 

3.1.2  情景分析

针对数据成员进行封装是面向对象的基本特性,java 提供 public、protected,private 及包 封装四种,封装可以针对数据成员,也可以针对类。同时 java 也提供 getter 和 setter 方法对成 员变量进行读写封装,进一步提高数据成员封装性。 

3.1.3  解决方案

(1)打开 Eclipse,选择 Task2_2,复制之后粘贴为 Task3_1 项目。

(2)选择 Task3_1 项目,打开 EmployeeA 类,针对当前 EmployeeA 的成员变量进行访问 区分符限定,一般情况下,针对成员变量的封装采取 private 封装,针对成员方法的封装,采 取 public 封装。封装后的代码如下: 

public class EmployeeA { 

private String employeeNo;  //工号  private String employeeName;  //姓名  private String employeeGender;  //性别  private String employeeDepartment;  //所属部门  private String employeePos;  //职务

(2)

private String employeeTitlePos;  //职称  private Date employeeEntryDate;  //入职日期  } 

(3)对 EmployeeA 类的数据成员进行 getter 和 setter 方法封装。在 Eclipse 中,可以执行

“Source”菜单下的“Generate Getters and Setters…”选项,进行访问 getter 和 setter 封装,如 图 3­1 所示。

图 3­1  创建 getter 和 setter 方法 设置了 getter 和 setter 的类 EmployeeA 的成员如下: 

public 

class EmployeeA { 

private 

String employeeNo;  //工号 

private 

String employeeName;  //姓名 

private 

String employeeGender;  //性别 

private 

String employeeDepartment;  //所属部门 

private 

String employeePos;  //职务 

private 

String employeeTitlePos;  //职称 

private 

Date employeeEntryDate; 

public 

String getEmployeeNo(); 

public void 

setEmployeeNo(String employeeNo); 

public 

String getEmployeeName(); 

public void 

setEmployeeName(String employeeName); 

public 

String getEmployeeGender(); 

public void 

setEmployeeGender(String employeeGender); 

public 

String getEmployeeDepartment(); 

public void 

setEmployeeDepartment(String employeeDepartment);

(3)

public 

String getEmployeePos(); 

public void 

setEmployeePos(String employeePos); 

public 

String getEmployeeTitlePos(); 

public void 

setEmployeeTitlePos(String employeeTitlePos); 

public Date

getEmployeeEntryDate(); 

public void 

setEmployeeEntryDate(Date employeeEntryDate); 

public int getWorkYears(); 

public void display(); 

(4)修改员工入职日期的 setter 方法: 

/** 

*  设置入职日期 

* @param employeeEntryDate 

*/ 

public void setEmployeeEntryDate(String employeeEntryDate) {  try { 

SimpleDateFormat fmt = new SimpleDateFormat("yyyy­MM­dd"); 

this.employeeEntryDate = fmt.parse(employeeEntryDate); 

} catch (ParseException e) {  e.printStackTrace(); 

}  } 

(5)修改 Menus 类中的 employeeMenu 方法,通过 setter 方法设置成员变量的值。具体 代码如下: 

/** 

*  员工信息操作菜单 

*/ 

public static void employeeMenu() {  //略 

//以下是方法中 case 1 的部分修改结果  EmployeeA objTom = new EmployeeA(); 

System.out.print("请输入员工号:"); 

objTom.setEmployeeNo(in.next()); 

System.out.print("请输入员工姓名:"); 

objTom.setEmployeeName(in.next()); 

System.out.print("请输入员工性别:"); 

objTom.setEmployeeGender(in.next()); 

System.out.print("请输入所属部门:"); 

objTom.setEmployeeDepartment(in.next()); 

System.out.print("请输入员工职务:"); 

objTom.setEmployeePos(in.next()); 

System.out.print("请输入员工职称:"); 

objTom.setEmployeeTitlePos(in.next()); 

System.out.print("请输入员工入职日期,格式 yyyy­MM­dd:"); 

objTom.setEmployeeEntryDate(in.next()); 

}

(4)

(6)完成封装及其访问。 

3.1.4  知识总结  1.封装

一个对象的变量构成这个对象的核心,一般不将其对外公开,而是将其变量处理的方法 对外公开,这样变量就被隐藏起来,这种将对象的变量置于方法的保护之下的方法,被称为封 装。封装是将对象的状态和行为捆绑在一起的机制,通过对对象的封装,数据和基于数据的操 作封装在一起,使其构成一个不可分割的独立实体,数据被保护在对象的内部,尽可能地隐藏 内部的细节, 只保留一些对外接口使之与外部发生联系。 封装实际上就是对于访问权限的控制 操作。

2.访问权限控制

在 Java 中,针对类的每个成员变量和方法都有访问权限的控制。Java 支持四种用于成员 变量和方法的访问级别:public、protected、private  和包访问控制。这种访问权限控制实现了 一定范围内的隐藏。表 3­1 列出了不同范围的访问权限。

表 3­1  类成员访问权限作用范围 作用范围

访问权限 同一个类中 同一个包中 不同包中的子类 不同包中的非子类 

private  √ × × ×

包访问控制 √ √ × × 

protected  √ √ √ × 

public  √ √ √ √

注:表中“√”表示可以访问, “×”表示不可以访问

(1)private 

类中限定为 private 的成员变量和成员方法只能被这个类本身的方法访问,它不能在类外 通过名字来访问。private  的访问权限有助于对客户隐藏类的实现细节,减少错误,提高程序 的可修改性。

建议把一个类中所有的实例变量设为  private,必要时,用  public  方法设定或读取实例变 量的值。类中的一些辅助方法也可以设为  private 访问权限,因为这些方法没有必要让外界知 道, 对他们的修改也不会影响程序的其他部分。 这样类的编程人员就控制了如何操纵类的数据。

另外,对于构造方法,也可以限定它为  private。如果一个类的构造方法声明为  private,

则其他类不能通过构造方法生成该类的一个对象, 但可以通过该类中一个可以访问的方法间接 地生成一个对象实例。

(2)包访问控制

如果在成员变量和成员方法前不加任何访问权限修饰符,则默认为包访问控制。这样同 一包内的其他所有类都能访问该成员, 但对包外的所有类就不能访问。 包访问控制允许将相关 的类都组合到一个包里,使它们相互间方便进行沟通。

(3)protected

(5)

类中限定为  protected  的成员可以被这个类本身、它的子类(包括同一包中的和不同包中 的子类)以及同一包中所有其他的类访问。如果一个类有子类,而不管子类是否与自己在同一 包中,都想让子类能够访问自己的某些成员,就可以将这些成员声明为 protected 访问类型。

(4)public 

类中声明为 public 的成员可以被所有的类访问。 public 的主要用途是让类的客户了解类提 供的服务,即类的公共接口,而不必关心类是如何完成其任务的。将类的实例变量声明为  private,并将类的方法声明为 public 就可以方便程序的调试,因为这样可以使数据操作方面的 问题局限在类的方法中。 

3.setter 和 getter 方法

前面提到,一个类的成员变量一旦被定义为  prviate,就不能被其他类访问,那么外部如 何实现对这些成员变量的操纵呢?一个好的办法就是为  private 成员变量提供一个公有的访问 方法,外界通过公有的方法来访问它。Java 提供访问器方法,即 getter 和 setter 方法,通过访 问器方法,其他对象可以读取和设置 private 成员变量的值。这样做的好处是 private 成员变量 可以得到保护,防止错误的操作,因为在访问器方法中可以判断操作是否合法。

(1)getter 方法:读取对象的属性值,只是简单的返回。语法格式为: 

public attributeType getAttributeName(); 

其中,AttributeName  是读取成员变量的名字,首字母要大写,方法没有参数,返回类型 和被读取成员变量的类型一致。

(2)setter 方法:设置对象的属性值,可以增加一些检查的措施。语法格式为: 

public void setAttributeName(attributeType parameterName); 

其中,AttributeName  是成员变量的名字,首字母要大写,方法参数类型与要设置的成员 变量的类型一致,方法没有返回值。

例 3­1  getter 和 setter 方法的使用。 

class Student { //类的声明 

private String name;  //成员变量  private int age; 

public void setName(String name) {  //设置 name 属性值  this.name = name; 

public void setAge(int age) { 

if (age >= 0 && age <= 200) {  //设置正确的年龄范围  this.age = age; 

} else 

System.out.println("error,age between 0 and 200"); 

public String getName() {  //读取 name 属性值  return name; 

public int getAge() {  //读取 age 属性值 

return age; 

}  }

(6)

public class StudentObjectDemo { 

public static void main(String[] args) { 

Student s = new Student();  //声明和创建对象  s.setName("张三");  //调用 setter 方法  s.setAge(30); 

System.out.println("姓名:" + s.getName() + ",年龄:" + s.getAge());  //调用 getter 方法  } 

从本例中可以看到,每一个 private 成员变量都有一对 getter 和 setter 方法,在 setAge 方 法中对输入的变量值进行了验证,如果不符合要求则拒绝修改,从而有效地保护了数据。 

3.1.5  应用实践

在 Java 中,封装的概念比较多,这里只是列出了通过 private 等访问权限的控制来实现数 据的封装,更多的应用在以后会陆续地接触到。通过本实践,更进一步加深对封装的理解。 

1.利用 setter 和 getter 方法,修改应用实践 2.2.5,声明一个矩形类,定义成员变量和方 法,并创建一个矩形对象,通过设置长和宽,输出其周长和面积。对于其中长和宽设置值验证 机制,只能为正数。 

2.扩展:针对 B 类员工及 C 类员工进行封装及其封装访问。

任务 3.2 类的继承性

3.2.1  情境描述

代 码复 用是面 向对 象的重 要特 性之一 , Tom  所 定 义的  EmployeeA, EmployeeB  及  EmployeeC 三个类均存在相同的成员变量及成员方法,当前的类定义方式违背了复用的原则,

为了提高代码的复用,Tom 需要完成以下任务:

(1)理解继承性。

(2)定义基类。

(3)从基类派生子类。

(4)定义抽象方法。 

3.2.2  问题分析 

Java 语句是纯面向对象语言,因此实现代码复用的方法就是继承,将当前的 A、B、C 三 类员工的功能属性和共同行为抽象定义为基类  Employee  类,由  Employee  类派生出子类  EmployeeA、EmployeeB、EmployeeC。

就 A、B、C 三类员工同样存在相同的行为,例如 display 方法,他们的方法名称相同,但 是实现过程不一样,在解决 display 行为统一时,可以在基类 Employee 中声明 display 方法,

在子类中重新定义。

(7)

3.2.3  解决方案

(1)打开 Eclipse,选择 Task3_1 项目,进行复制,粘贴形成新项目 Task3_2。

(2) 选中 Task3_2 项目, 打开 EmployeeA 类, 选择菜单 “Refactor” à “Extract SuperClass..,”

进入图 3­2 所示界面,在“Superclass name”后输入 Employee,在“Types to extract a superclass  from”部分,单击“Add”按钮,添加 EmployeeB 和 EmployeeC,最后勾选需要提取到基类的 成员变量及成员方法。

图 3­2  重构父类图

单击“Next”进行重构类勾选,选中 EmployeeA、EmployeeB 及 EmployeeC 需要重构的 字段,如图 3­3 所示。

(3)单击  Finish  按钮,重构后形成  Employee  类及其字段  EmployeeA、EmployeeB、 

EmployeeC。 

1)Employee 类  //Employee.java  public class Employee { 

protected String employeeNo; 

protected String employeeName; 

protected String employeeGender; 

protected String employeeDepartment; 

protected String employeePos; 

public String getEmployeeNo(); 

public void setEmployeeNo(String employeeNo);

(8)

public String getEmployeeName() ; 

public void setEmployeeName(String employeeName) ;  public String getEmployeeGender() ; 

public void setEmployeeGender(String employeeGender) ;  public String getEmployeeDepartment(); 

public void setEmployeeDepartment(String employeeDepartment); 

public String getEmployeePos() ; 

public void setEmployeePos(String employeePos); 

图 3­3  重构字段  2)EmployeeA 子类 

//EmployeeA.java 

public class EmployeeA extends Employee {  private String employeeTitlePos; 

private Date employeeEntryDate; 

public String getEmployeeTitlePos(); 

public void setEmployeeTitlePos(String employeeTitlePos) ;  public Date getEmployeeEntryDate() ; 

public void setEmployeeEntryDate(String employeeEntryDate) ;  public int getWorkYears(); 

public void display(); 

3)EmployeeB 子类

(9)

//EmployeeB.java 

public class EmployeeB extends Employee {  private String employeeTitlePos;//职称  private int employeeWorkTimes;//月工作时间  public String getEmployeeTitlePos(); 

public void setEmployeeTitlePos(String employeeTitlePos) ;  public int getEmployeeWorkTimes(); 

public void setEmployeeWorkTimes(int employeeWorkTimes); 

public void display() ; 

4)EmployeeC 子类  //EmployeeC.java 

public class EmployeeC extends Employee {  private int employeeWorkTimes;//月工作时间  public int getEmployeeWorkTimes(); 

public void setEmployeeWorkTimes(int employeeWorkTimes); 

public void display() ; 

(4)修改 Employee 类为抽象类,再添加抽象方法 display。具体声明如下: 

public abstract class Employee { 

public abstract void display();//声明统一的抽象方法  } 

3.2.4  知识总结  1.继承的概念

继承性是面向对象程序设计的一个重要特征。通过继承,子类可以继承基类的属性和方 法,还可以加以扩展,或者修改原有的方法,从而提高程序的扩展性和灵活性。通过继承,子 类可以复用基类的代码,从而提高编程的效率。

其中,被继承的类称为基类、超类或父类(super class);继承的类称为继承类或子类,它 在基类的基础上增加新的属性、方法;一个父类可以同时拥有多个子类,但 Java  中不支持多 重继承,所以一个类只能有一个直接父类。 

2.类的继承

(1)子类的声明

在 Java 语言中,所有的类都是直接或间接地继承自 java.lang.Object 类。通常在类的声明 中加入 extends 关键字来创建一个类的子类,其形式如下: 

[修饰符] class  子类类名  extends  父类类名  { 

//语句体  } 

如果没有  extends  关键字指明基类,则默认基类是  java.lang.Object。如前面章节中创建 的类。

创建成功后,子类实际拥有的成员变量和方法有:

①子类本身拥有的成员变量和方法。

(10)

②基类及其祖先的 public 和 protected 成员变量和方法。

③如果子类与基类在同一个包中,则包括基类的(default)成员变量和方法;如果不在同 一个包中,则不包括基类的(default)成员变量和方法。

④不包括基类的 private 成员变量和方法。 子类也不能访问基类的 private 成员变量和方法。

(2)成员变量的隐藏和成员方法的覆盖

如果子类中声明了与基类同名的成员变量,则在子类中基类的成员变量被隐藏。子类仍 然继承了基类的成员变量,只是将成员变量的名字隐藏,使其不可直接被访问。

如果子类定义的方法与基类中的方法具有相同的名字、相同的参数表和相同的返回类型,

则基类的方法被重写,在子类中基类的成员方法被覆盖。

子类通过成员变量的隐藏和成员方法的覆盖可以把基类的属性和方法改变为自身的属性 和方法。

例 3­2  隐藏成员变量和覆盖成员方法  class Stu { 

String name; 

int age; 

int number; 

public void display() { 

System.out.println("name:" + name + "\n" + "age:" + age); 

class GraduateStu extends Stu {  //声明子类 GraduateStu 

int number;  //隐藏了父类的 number 属性 

String mentorName;  //增添了新的 mentorName 属性 

public void display() {  //覆盖了父类的方法  System.out.println("name:" + name + "\n" + "age:" + age); 

System.out.println("his mentor is:" + mentorName); 

}  } 

注意:子类中不能覆盖基类中的 final 方法;子类中必须覆盖基类中的 abstract 方法,否则 子类也为抽象类。

(3)继承的特点

l 重用性:在继承关系中,基类中已经存在的代码,包括属性和方法,可以继承到子类 中。

l 可靠性:通过继承的方式利用 java API 类库及第三方类库,可以提高代码的可靠性。

l 扩展性:子类可以增添新的属性和方法,加强子类的功能。通过继承来扩展现有程序 的功能,比重新设计程序更容易一些。

(4)super 关键字

如果子类隐藏了基类的成员变量或者覆盖了基类的成员方法,而有时还需要使用父类中 的那些成员变量和方法,这时就需要借助 Java 中的 super 关键字来实现对父类成员的访问。

(11)

l 访问被隐藏的直接基类的同名成员变量: 

super.成员变量

l 访问直接基类中被覆盖的同名方法: 

super.成员方法([参数列表]) 

l 访问直接基类的构造方法: 

super([参数列表])  注意:

①如果存在多层继承,可以通过直接基类的方法来间接引用;

②引用基类的构造方法时,子类构造方法中  super()语句必须是第一条语句。先初始化基 类,再初始化子类。

③不能从非构造方法中引用基类的构造方法。

④如果不存在同名的成员变量或方法,一般不需要用 super 关键字。

(5)final 关键字 

final 关键字可以修饰类、成员变量和方法中的参数。

被 final 关键字修饰的类称为 final 类,不能被继承,即不能有子类。有时候出于安全性考 虑,将一些类修饰为 final 类。如 String 类,它对于编译器和解释器的正常运行有很重要的作 用,对它不能轻易地改变,因此它被修饰为 final 类。

如果一个方法被修饰为 final 方法,则这个方法不能被覆盖。

如果一个成员变量被修饰为  final,则它将成为常量,且必须初始化,不能再发生变化。

如:final double PI=3.1415926;。 

3.抽象类

包含一个抽象方法的类称为抽象类,抽象方法是只声明而未实现的方法,所有的抽象方 法必须使用 abstract 关键字声明。声明抽象类的格式与声明类的格式相同,但要用 abstract 修 饰符指明它是一个抽象类。

定义抽象方法的语法格式与普通的方法有些不同: 

abstract  返回类型 方法名([参数列表]); 

可以看出,抽象方法没有方法体,直接用分号结束。具体的抽象方法必须在子类中被实现。

对于抽象类,不能直接进行  new  关键字实例化的操作。但可以声明,在声明抽象类时,

将规范其子类所应该有的方法。如果想使用抽象类,则必须依靠子类,抽象类是必须被子类继 承的,而且子类需要实现抽象类中的全部抽象方法。因此,抽象类不允许使用 final 关键字声 明,因为被 final 声明的类不能有子类。

抽象类的用途:通过对抽象类的继承可以实现代码的复用;可以规范子类的行为。 

3.2.5  应用实践

通过本实践,加深对继承的理解,并综合运用抽象类等知识。

利用抽象类,完成继承的实现:求圆和矩形的面积。 

abstract class Shape {      //抽象类  public String color = "white"; 

abstract double area();      //抽象方法  public String getColor() {

(12)

return color; 

class CircleShape extends Shape {  //继承基类,同时继承其属性和方法 

public double radius;  //声明自身独有的属性 

public double area() {  //必须实现 area()方法  return Math.PI * radius * radius; 

class RectangleShape extends Shape {  public double length; 

public double width; 

public double area() {  //必须实现 area()方法  return length * width; 

public class Practise3_2_5 { 

public static void main(String args[]) { 

RectangleShape r = new RectangleShape(); 

r.length = 10; 

r.width = 8; 

System.out.println("矩形的面积是:" + r.area()); 

CircleShape c = new CircleShape(); 

c.radius = 5; 

System.out.println("圆的面积是:" + c.area()); 

}  } 

任务 3.3 接口

3.3.1  情境描述

在工资管理系统中,所涉及到的员工信息、工资信息等均需要进行数据的显示及存储操 作,数据的显示和存储从计算机的结构上讲均是数据的输出,数据显示是输出到显示器,数据 存储是输出到硬盘。针对不同对象的显示和输出行为,从本质上讲,属于同一行为,只是实现 手段不同而已。Tom 正在考虑如何利用 Java 语言来统一不同行为,为了达到这个目的,他需 要完成以下任务:

(1)定义接口及接口中的方法。

(2)实现接口及重写方法。 

3.3.2  问题分析

在现实社会中,很多不同的对象存在相同的行为,例如飞的动作,飞机可以飞,鸟也可 以飞。将这种相同行为进行统一,就形成标准,形成接口,类似 USB 接口,只要满足他的规

(13)

范,均可以连接到 USB 接口上。Java 语言在模拟现实社会对象的行为过程中,同样能够将对 象的相同行为统一定义,称为  Interface(接口)。员工、工资的信息显示问题,可以抽象定义 接口 Output,同时需要抽象定义方法 print,print 可以输出到文件、可以输出到打印机等。 

3.3.3  解决方案

(1)打开 Eclipse,选择 Task3_2 项目,执行复制,之后粘贴为新项目,命名为 Task3_3。

(2)选择 FileàNewàinterface,新建接口,命名为“Output” ,如图 3­4 所示。

图 3­4  创建接口图 代码如下: 

/** 

*  实现显示、打印行为的公共行为定义 

*/ 

package com.esms; 

/** 

* @author  李法平 

*/ 

public interface Output { 

(3)在 Output 接口中添加 print 方法用于显示输出。 

public interface Output {  void print();//输出方法声明  }

(14)

(4)在 A 类员工中实现输出行为,实现接口的行为。 

public class EmployeeA extends Employee implements Output    {  //略 

@Override 

public void print() {//显示输出  display(); 

(5)创建 Salary 工资类,实现接口 Output,并实现 print 方法。在 Eclipse 中,可以在新 建类的新建接口对话框中单击“Add”按钮,弹出“Implemented  Interfaces  Selection”窗体,

在弹出窗体中输入“Output”并且单击“Add”按钮,之后关闭弹出窗体。并单击新建类窗口 的“Finish”按钮,完成新建操作。如图 3­5 所示。

图 3­5  新建类界面 代码如下: 

public class Salary implements Output { 

@Override 

public void print() { 

}  } 

(6)在 print 方法中输出到显示器,用于测试方法。 

public class Salary implements Output { 

@Override

(15)

public void print() { 

System.out.println("工资管理类"); 

}  } 

(7)在 Salary 类中,添加 main 函数测试 A 类员工输出及工资输出。具体代码如下: 

public static void main(String[] args) { 

EmployeeA obj=new EmployeeA();  //定义 A 类员工类  obj.setEmployeeNo("001");  //设置 A 类员工对象值  obj.setEmployeeName("约翰"); 

obj.setEmployeeGender("男"); 

obj.setEmployeeDepartment("技术一部"); 

obj.setEmployeePos("技术员"); 

obj.setEmployeeTitlePos("中级"); 

obj.setEmployeeEntryDate("2005­07­01"); 

System.out.println("以下是 A 类员工输出"); 

Output p=obj;  //将 A 类员工赋值给 Output 接口对象 

p.print();  //调用输出方法 

System.out.println("以下是工资输出"); 

p=new Salary();  //通过 Salary 类创建 Output 对象 

p.print();  //调用输出方法 

(8)运行 Salary 类下的 main,结果如图 3­6 所示。

图 3­6  运行结果 

3.3.4  知识总结  1.接口概念

接口是方法定义和常量值的集合,接口中定义的方法都是抽象方法,实现接口的类要实 现接口中定义的所有方法。接口的用处主要体现在以下几个方面:

l 通过接口实现不相关类的相同方法,而不需要考虑这些类之间的层次关系。

l 通过接口可以指明多个类需要实现的方法。

l 通过接口可以了解对象的交互界面,而不需要了解对象所对应的类。

总之,接口的引入实现了某种意义上的多继承,并且一个类可以实现多个接口。

(16)

2.接口定义

接口的定义包括接口声明和接口体,其语法格式如下: 

[public] interface  接口名  [extends  父接口列表]{ 

//接口体  //常量域声明 

[public] [static] [final]  域类型 域名=常量值; 

//抽象方法声明 

[public] [abstract]  返回值类型 方法名([参数列表]); 

}  其中:

(1)使用 extends 来继承父接口,与类中的 extends 不同的是,它可以有多个父接口,各 接口间用逗号隔开。

(2)接口可以有静态的公开常量,用 public static final 加以修饰。

(3)接口中所有的方法都是抽象的和公开的,即用 public abstract 修饰的。

(4)与抽象类一样,接口不能被实例化。 

3.接口实现

接口中声明了一组方法,而具体地实现接口的方法,则需要某个类来实现。在类的声明 中使用 implements 关键字来实现接口,一个类可以同时实现多个接口。 

Class 类名  implements  接口 1 [接口 2,接口 3,…,接口 n]{ 

类体  }  其中:

l 若实现接口的类不是抽象类,则必须实现所有接口的所有方法。

l 一个类在实现接口的抽象方法时, 必须使用完全相同的方法名、 参数列表和相同的返 回值类型。

l 接口中抽象方法的访问修饰符默认为 public, 所以在实现中必须显式地使用 public 修 饰符。

接口作为对类的抽象,主要作用是规范类的方法,接口不能实现代码的复用。接口的主 要应用有两种方式:

(1)实现接口:通过类对接口的实现,实现接口中规范的方法,不同的实现类对方法的 实现可能不同,从而实现了多态性。

(2)接口作为参数:接口可以作为方法定义时的参数,在实际调用方法时传入接口的实 现类。传入不同的实现类,实现不同的行为,从而体现了多态性。 

3.3.5  应用实践 

1.通过接口实现对圆、矩形求周长和面积。 

interface Shape1 {  //声明接口 

double PI = 3.1415926;  //定义常量 

double area();  //接口是高度抽象的,因此省略了 abstract 关键字 

interface Shape2 { 

double perimeter();  //默认为 public 修饰符

(17)

class Rectangle2 implements Shape1, Shape2 {  //实现两个接口  public double length, width; 

public double area() {  //实现抽象方法 

return length * width; 

public double perimeter() {  return 2 * (length + width); 

class Circle2 implements Shape1, Shape2 {  //实现两个接口  public double radius; 

public double area() { 

return PI * radius * radius; 

public double perimeter() {  return 2 * PI * radius; 

public class Practise3_3_5 { 

public static void main(String args[]) {  Rectangle2 r = new Rectangle2(); 

Circle2 c = new Circle2(); 

r.length = 10; 

r.width = 8; 

System.out.println("矩形的面积是:" + r.area()+",周长是:"+r.perimeter()); 

c.radius = 5; 

System.out.println("圆的面积是:" + c.area()+",周长是:"+c.perimeter()); 

}  } 

2.扩展练习:利用接口及继承性实现 A、B、C 类员工的工资类的定义。

任务 3.4 静态多态性

3.4.1  情境描述 

Tom 开发的工资管理系统中,相同类型的员工的工资计算方式往往存在多种,比如 A 类 员工,员工的加班与否往往影响工资的计算,为了适应不同方式的工资计算,Tom  需要完成 以下任务:

(1)理解方法重载。

(2)定义重载方法。

(3)调用重载方法。 

3.4.2  问题分析

工资计算是一个复杂问题,相同员工由于工种、职务、劳动强度不同等情况,可能造成

(18)

工资计算的方式不同, 这就是所谓的行为相同, 但是行为的执行过程多样, 这就是多态性, Java  语言提供方法重载来实现多态性。 

3.4.3  解决方案

(1)打开 Eclipse,选中 Task3_3,执行复制,粘贴为新项目 Task3_4。

(2)打开 SalaryB.java 文件,添加构造方法,用于初始化 SalaryB 中的所有的成员变量。 

/** 

*  构造方法 

* @param obj 

* @param wage 

*/ 

public SalaryB(EmployeeB obj) {  super(obj); 

this.baseWage = 0; 

this.posWage = 0; 

this.timeWge = 0; 

}  /** 

*  构造方法 

* @param obj 

* @param wage 

* @param baseWage 

* @param posWage 

* @param ageWage 

* @param timeWage 

*/ 

public SalaryB(EmployeeB obj, double baseWage, double posWage,  double timeWage) { 

super(obj); 

this.baseWage = baseWage; 

this.posWage = posWage; 

this.timeWge = timeWage; 

注:当前 B 类员工的工资构造方法由以前带 2 个参数的方法的基础上,增加了一个新的 方法。2 个构造方法的名称相同,但是参数不同。

(3)针对某些员工可能存在的加班情况,扩展 calWages 方法,便于实现加班员工的工资 计算问题。 

/* 

*工资计算  * 

* @see com.esms.Salary#calWages() 

*/ 

@Override

(19)

public void calWages() { 

EmployeeB obj=(EmployeeB)this.empObj; 

this.wage = this.baseWage + this.posWage + 

this.timeWge*obj.getEmployeeWorkTimes() ; 

/** 

*  具备加班工资的工资计算 

* @param addWage 

*/ 

public void calWages(double addWage) {  calWages(); 

this.wage = this.wage + addWage; 

(4)在 SalaryB 类中添加 main 方法,测试未加班 B 类员工工资及加了班的员工工资。 

EmployeeB objTom = new EmployeeB();  //创建员工对象  objTom.setEmployeeNo("001");  //设置工号  objTom.setEmployeeName("汤姆");  //设置姓名  objTom.setEmployeeWorkTimes(50);  //工资小时数量  SalaryB salaryTom = new SalaryB(objTom); 

salaryTom.setBaseWage(1500);  //基本工资  salaryTom.setPosWage(450);  //岗位工资 

salaryTom.setTimeWge(30);  //记时工资单位数量  //工资计算 

salaryTom.calWages(); 

System.out.println("以下是未加班的 B 类员工 Tom 的工资:"); 

//输出当前员工的工资  salaryTom.print(); 

EmployeeB objJack=new EmployeeB(); 

objJack.setEmployeeNo("002"); 

objJack.setEmployeeName("杰克"); 

objJack.setEmployeeWorkTimes(56); 

//第二个构造方法构造对象 

SalaryB salaryJack = new SalaryB(objJack,1000,300,60); 

salaryJack.calWages(400);  //带加班工资的计算  System.out.println("以下是加班的 B 类员工 Jack 的工资:"); 

salaryJack.print(); 

(5)运行 SalaryB 类下的 main 方法,运行结果如图 3­7 所示。

图 3­7  程序运行结果

(20)

3.4.4  知识总结  1.多态性

多态性是面向对象程序设计的另一个重要特征。多态是指一个方法只有一个名称,但可 以有多种行为,即一个类中可以存在多个同名的方法,可以使对象具有不同的行为,实现了对 象行为的多态性。 多态可以理解为属于两个或多个不同类的对象以各自的类相关的不同方式响 应同一方法调用的能力。通过继承和接口可以实现多态。

多态有两种,即重载和覆盖。其中覆盖是一种动态的多态,在面向对象的程序设计中具 有特别的意义。将在后面的任务中提及。 

2.方法的重载

方法重载体现了面向对象系统的多态性。方法重载是指一个类中可以有多个方法具有相 同的方法名,但这些方法的参数个数不同,或者参数类型不同。重载中需注意以下几点:

(1)返回类型不同不足以构成方法重载,重载可以有不同的返回类型。

(2)同名的方法分别位于基类和子类中,只要参数不同,也将构成重载。

(3)同一个类中的多个构造方法必然构成重载。

重载是一种静态的多态。当重载方法被调用时,编译器将根据参数的数量和类型来确定 实际调用的重载方法是哪个版本。

下述代码为各种类型数据相加方法的重载: 

public class OverloadingDemo {  int add(int x, int y) { 

return x + y; 

int add(int x, int y, int z) {  //参数个数不同  return x + y + z; 

float add(float x, float y) {  //参数类型不同  return x + y; 

double add(double x, double y) {  //参数类型不同  return x + y; 

}  } 

3.4.5  应用实践

通过本实践巩固对方法重载实现多态性的理解。 

1.要求将知识总结中的上述代码补充为一个完整的应用程序:实现各类数据相加的方法 重载。

2.利用构造方法实现方法重载。编写一个学生类:student,类的组成如下:

l 成员变量:学号、姓名、年龄、专业

(21)

l 成员方法:

(1)利用构造方法完成设置信息:

①无参:均为空值

②单参:只传递学号,则姓名:无名氏,年龄:0,专业:未定

③双参:只传递学号和姓名,则年龄:19,专业:未定

④四参:传递学号、姓名、年龄、专业

(2)显示信息。

扩展练习:

完善 A 类员工及 C 类员工的工资计算扩展。由于物价消费的过快增长,A 类员工在工资 发放的时候,每人按照职称不等增加了 400~600 元的补贴;C 类员工临时加班了几个月增加 了交通补贴及加班工资项目。

任务 3.5 动态多态性

3.5.1  情境描述 

Tom 在该公司进一步调查发现,同样是 B 类员工,有的员工工资计算的时候,计算了岗 位工资,没有时间工资,而另外一部分员工有时间工资,没有岗位工资,为了解决这个问题, 

Tom 需要完成以下任务:

(1)利用基类派生出子类。

(2)重写基类中的工资计算方法。 

3.5.2  问题分析 

B  类员工的工资计算不是简单的拆分问题,故针对  B  类员工而言,其工资的计算应该派 生出两个子类, 分别处理两种不同业务逻辑的工资计算, 在子类中对不带参数的方法 calWages  进行重写可以达到目的。 

3.5.3  解决方案

(1)打开 Eclipse,选择项目 Task3_4,复制粘贴形成新的项目 Task3_5。

(2)修改 SalaryB 的类,去掉非公共成员。 

/** 

*/ 

package com.esms; 

/** 

* @author  李法平 

*/ 

public class SalaryB extends Salary {

(22)

private double baseWage; 

private double timeWage; 

/** 

*  构造方法 

* @param obj 

* @param wage 

*/ 

public SalaryB(EmployeeB obj) {  super(obj); 

this.baseWage = 0; 

/** 

*  构造方法 

* @param obj 

* @param wage 

* @param baseWage 

* @param posWage 

* @param ageWage 

* @param timeWage 

*/ 

public SalaryB(EmployeeB obj, double baseWage) {  super(obj); 

this.baseWage = baseWage; 

/** 

* @return the baseWage 

*/ 

public double getBaseWage() {  return baseWage; 

/** 

* @param baseWage 

*      the baseWage to set 

*/ 

public void setBaseWage(double baseWage) {  this.baseWage = baseWage; 

}  /* 

*  工资计算 

* @see com.esms.Salary#calWages()

(23)

*/ 

@Override 

public void calWages() { 

this.wage = this.baseWage    ;  } 

/** 

*  具备加班工资的工资计算 

* @param addWage 

*/ 

public void calWages(double addWage) {  calWages(); 

this.wage = this.wage + addWage; 

}  } 

(3)新建 SalaryB 的派生类 SalaryPostB 及 SalaryTimeB 两个子类,具体如图 3­8 所示。

图 3­8  新建 SalaryPostB 

新建成功之后,分别修改两个类的构造方法,适应新的构造。修改 SalaryPostB 类的构造 方法。 

/** 

*/ 

package com.esms;

(24)

/** 

* @author  李法平 

*/ 

public class SalaryPostB extends SalaryB {  private double posWage; 

/** 

* @return the posWage 

*/ 

public double getPosWage() {  return posWage; 

/** 

* @param posWage the posWage to set 

*/ 

public void setPosWage(double posWage) {  this.posWage = posWage; 

/** 

* @param obj 

*/ 

public SalaryPostB(EmployeeB obj) {  super(obj); 

//TODO Auto­generated constructor stub  } 

/** 

* @param obj 

* @param baseWage 

* @param posWage 

* @param timeWage 

*/ 

public SalaryPostB(EmployeeB obj, double baseWage, double posWage  ) {

super(obj, baseWage); 

this.posWage=posWage; 

//TODO Auto­generated constructor stub  } 

修改 SalaryTimeB 类的构造方法。 

/** 

*/

(25)

package com.esms; 

/** 

* @author  李法平 

*/ 

public class SalaryTimeB extends SalaryB {  double timeWage; 

/** 

* @param obj 

*/ 

public SalaryTimeB(EmployeeB obj) {  super(obj); 

//TODO Auto­generated constructor stub  } 

/** 

* @param obj 

* @param baseWage 

* @param posWage 

* @param timeWage 

*/ 

public SalaryTimeB(EmployeeB obj, double baseWage,  double timeWage) { 

super(obj, baseWage ); 

this.timeWage=timeWage; 

//TODO Auto­generated constructor stub  } 

/** 

* @return the timeWage 

*/ 

public double getTimeWage() {  return timeWage; 

/** 

* @param timeWage the timeWage to set 

*/ 

public void setTimeWage(double timeWage) {  this.timeWage = timeWage; 

/* (non­Javadoc) 

* @see com.esms.SalaryB#calWages() 

*/

(26)

@Override 

public void calWages() { 

EmployeeB obj = (EmployeeB) this.empObj; 

this.wage=this.getBaseWage()+this.getTimeWage()*obj.getEmployeeWorkTimes(); 

}  } 

(4)针对 SalaryPostB 类,重写 calWages 方法。 

/* (non­Javadoc) 

* @see com.esms.SalaryB#calWages() 

*/ 

@Override 

public void calWages() { 

this.wage=this.getBaseWage()+this.getPosWage(); 

(5)根据 SalaryTimeB 类的特性,在 SalaryTimeB 中重写 calWages 方法。 

/* (non­Javadoc) 

* @see com.esms.SalaryB#calWages() 

*/ 

@Override 

public void calWages() { 

EmployeeB obj = (EmployeeB) this.empObj; 

this.wage=this.getBaseWage()+ 

this.getTimeWage()*obj.getEmployeeWorkTimes(); 

(6)在 SalaryB 基类中编写主函数验证。 

/** 

*  主方法 

* @param args 

*/ 

public static void main(String[] args) { 

EmployeeB objTom = new EmployeeB();  //创建员工对象  objTom.setEmployeeNo("001");  //设置工号  objTom.setEmployeeName("汤姆");  //设置姓名  SalaryB salaryTom = new SalaryPostB(objTom,1500,2000); 

//工资计算 

salaryTom.calWages(); 

System.out.println("以下是 B 类员工按岗位计算工资情况:"); 

//输出当前员工的工资  salaryTom.print(); 

EmployeeB objJack=new EmployeeB(); 

objJack.setEmployeeNo("002"); 

objJack.setEmployeeName("杰克"); 

objJack.setEmployeeWorkTimes(64); 

//第二个构造方法构造对象

(27)

SalaryB salaryJack = new SalaryTimeB(objJack,1000,30); 

salaryJack.calWages(400);//带加班工资的计算 

System.out.println("以下是 B 类员工按小时计算工资情况:"); 

salaryJack.print(); 

(7)运行结果如图 3­9 所示。

图 3­9  程序运行结果 

3.5.4  知识总结  1.方法的覆盖

方法覆盖是 Java 实现多态性机制的另一种方式。在类的继承的部分已经作了介绍,由于 它在面向对象的程序设计中的重要性,这里再次对它进行详细的讨论。

在继承关系中,基类(也包括接口)和子类存在同名的方法,如果同时满足以下条件:

l 相同的参数(包括参数个数、类型、顺序)。

l 相同的返回值类型。

那么,子类的方法覆盖基类的方法。使用覆盖时需注意以下几点:

l 不允许出现参数相同,但返回值类型不同的同名方法。

l 子类方法不能比基类同名方法有更严格的访问范围。

l 子类方法不能比基类同名方法抛出更多的异常。

覆盖是一种动态的多态,它是通过基类引用来体现的。JVM  根据当前被引用对象的类型 来动态地决定执行的是覆盖方法的哪个版本。 

2.多态的优点

覆盖这种形式的多态具有以下优点:

(1)可替换性:多态对已存在的代码具有可替换性。

(2)可扩充性:多态对代码具有可扩充性。增加新的子类不影响已存在类的多态性、继 承性,以及其他特性的运行和操作。

(3)接口性:多态是接口通过方法签名,向子类提供一个共同接口,由子类来覆盖它而 实现的。 

3.5.5  应用实践

为了更充分地理解多态,体现面向对象程序设计的核心思想,完成本实践内容。

修改实践 3.3.5,通过接口实现对圆、矩形求面积。

(28)

interface ShapeOver {  double area(); 

class RectangleOver implements ShapeOver {  public double length; 

public double width; 

public double area() {  //覆盖接口的方法  return length * width; 

class CircleOver implements ShapeOver {  public double radius; 

public double area() {  //覆盖接口的方法  return Math.PI * radius * radius; 

public class Practise3_5_5 { 

public static void main(String[] args) { 

RectangleOver r = new RectangleOver();  //声明一个矩形  CircleOver c = new CircleOver();  //声明一个圆  r.length = 10; 

r.width = 8; 

c.radius = 5; 

ShapeOver shape; 

shape = r; 

printArea(shape);    //实参是 shape,但它指向的是 RectangleOver()的实例  shape = c; 

printArea(shape);    //实参是 shape,但它指向的是 CircleOver()的实例 

static void printArea(ShapeOver shape) { 

System.out.println("面积是:" + shape.area()); 

//动态多态,根据传入对象的实际类型,调用正确的覆盖方法的版本  //是矩形时,调用矩形的面积计算方法;是圆时,调用圆的面积计算方法  } 

在此基础上,读者可根据 Shape 的接口要求,增加实现三角形类等功能。

任务小结

本章节中,主要总结了面向对象程序设计的基本特性:封装性、继承性和多态性。

面向对象程序设计体系中的思想精髓之一是“面向接口编程” 。接口中只有抽象的方法,

可以将这些方法理解成是一组规则的集合, 它规定了实现本接口的类或继承本接口的接口必须 拥有的一组规则。多态可以体现在类对类的继承上,也可以体现在类对接口的实现上。

(29)

接口的使用会导致代码结构变得复杂,但对于大型系统却有着不可估量的作用,因为它 能使大型系统变得结构清晰、容易维护。

抽象类和接口都是建立在抽象的基础上的,它们的区别在于想要达到的目的。使用抽象 类是为了代码的复用,使用接口是为了实现多态性。

面向对象的思想就是模拟真实世界,把真实世界中的事物抽象成类,使整个程序靠各个 类的实例通过互相通信和互相协作来完成系统功能。

练习作业 

1.创建一个 Animal 类,让 Horse、Dog、Cat 等动物继承 Animal 类。在 Animal 类中定 义一些方法,让其子类重载这些方法,编写一个运行时多态的程序。 

2.修改练习 1,使 Animal 成为一个接口。 

3.利用接口编写计算三角形、梯形、锥形面积及周长的程序。 

4.编写一个类,该类有一个方法 public int f(int a,int b),该方法返回 a 和 b 的最大公约数。

然后再编写一个该类的子类,要求子类覆盖方法 f,而且覆盖的方法将返回 a 和 b 的最小公倍 数。要求在覆盖的方法体中首先调用被隐藏的方法返回 a  和 b  的最大公约数  m,然后将乘积  (a*b)/m 返回。要求在应用程序的主类中分别使用父类和子类创建对象,并分别调用方法  f 计 算两个正整数的最大公约数和最小公倍数。 

5.Eclipse 提供了一些自动生成代码的工具,例如自动生成 setters()和 getters()方法。请在  Eclipse 中找出更多的自动生成代码的功能。探索它们的用途以及与本章内容有什么关系? 

6.Eclipse  为程序员提供了许多工具,其中一个是“格式” (主菜单中的“Source”à

“Format” ),能够将不规则的代码重排,称为符合规范的代码。探索一下它的功能,写一段代 码,然后用 Format 功能,了解规范的代码的写法。

參考文獻

相關文件

The 2007 Health Care Survey collected information from 713 health care establishments, comprising the 3 hospitals providing hospital care services, 477 private clinics and

[r]

The Task Force fully recognises students’ diverse learning and development needs across different key stages and domains, and hence the recommendations need to be considered in

public static double calculate(int i, int j) throws ArithmeticException,

private void Page_Load(object sender, System.EventArgs e) {{. string dataSource

private void Page_Load(object sender, System.EventArgs e) {. string dataSource

If land resource for private housing increases, the trading price in private housing market will decrease but there may not be any effects on public housing market 54 ; if we

If land resource for private housing increases, the trading price in private housing market will decrease but there may not be any effects on public housing market 54 ; if