一.单元概述
封装、继承和多态是面向对象的三个基本特征,本章节将对该技术的实现进行探讨。
通过本章的学习:
首先,能够了解Java语言中继承的作用和实现,掌握属性和方法的继承,掌握构造方法的继承,掌握多态的概念和实现。
其次,了解Java语言中常用的修饰符,掌握包的概念,掌握封装的应用。
最后,通过本章的学习掌握如何创建和继承抽象类,掌握如何创建和实现接口,掌握内部类的定义和使用。
二、教学重点与难点 重点:
(1) 继承
(2) 方法的覆盖
(3) 多态
(4) 抽象类
(5) 接口
难点:
(1) 子类实例化的过程
(2) 上溯造型
(3) 下溯造型
(4) 多态
(5) 内部类特性
(6) 静态内部类
7.1类的继承
7.1.1继承的含义
继承是面向对象最显著的一个特性。继承是从已有的类中派生出新的类,新的类能拥有已有类的属性和行为,并能扩展新的属性和行为。Java继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。这种技术使得复用以前的代码非常容易,能够大大缩短开发周期,降低开发费用。 在Java中,一个类只能有一个父类,不支持多继承。现实生活中,父母(Parent)生出小孩(Child),而小孩在很多地方与其父母长的很像,长的像的原因是Child继承了Parent的基因,同时小孩是独立于父母的单独个体(不是包含关系),且有其自己不同于父母的部分。 Java中,类之间的关系也具有与Parent和Child相似的地方,例如在下图中有Worker类与officer类,这两个类有共同的属性和方法,在应用继承时,就可以把共同的属性和方法写在Employee类中,从而类Employee具有员工的共同属性包括姓名、生日和薪水以及方法,而Worker类与officer类继承了Employee类的这些属性和方法,同时又增加了属于自己的属性或者方法。这样大大提高了程序的复用性,类的继承如下图7.1所示。
图7.1类的继承
其实,在继承关系中我们还能发现一个规律:子类是父类的一种,也可以说“子类就是父类”。如:人类就是动物,动物就是生物,生物又可以派生出子类植物。记住这个定律对我们理解继承的概念非常有帮助,生物继承关系如下图7.2所示。
7.1.2继承的语法规则
在Java中定义一个类时,让该类通过关键字extends继承一个已有的类,这就是类的继承(泛化)。被继承的类称为父类(超类,父类),新的类称为子类(派生类)。子类继承父类的所有属性和方法,同时也可以增加自己的属性和方法。Java中只能单继承,也就是说每个类只能有一个直接父类;一个父类可以有多个子类。 Java语言中,类的继承的声明格式是:
在继承关系中:
- n 子类可以得到父类的属性和方法,这是从父类继承来的;
- n 子类可以添加新的属性和方法,添加父类所没有的;
- n 子类可以重新定义父类的属性和方法,修改父类的属性和方法,为自己所用。 先来看一个例子:
```java
class Employee {
String name;
String birth;
double salary;
public void getDetail() {
} double getSalary() {System.out.println("name=" + name + " age=" + birth);
} } class Worker extends Employee { double dressAllowance(){System.out.println(salary);
return salary;
} } public class TestWorker { public static void main(String args[]){double allownance = 200;
return allownance;
} }Worker w = new Worker();
w.getDetail();
运行结果如下:
```java
name=null age=null
<br />从上面的例题中,我们看出,子类继承了父类的getDetail()方法。 继承的好处:
- 使编码更高效
- 易维护
- 代码的重用
7.1.3子类实例化过程
子类实例化时先实例化其父类,然后实例化子类。要先调用父类的构造方法,父类构造方法运行完毕,才调用子类的构造方法。如果实例化类D,构造方法执行的顺序是ACD。继承中的构造方法执行顺序如下图7.4所示。
图7.4继承中的构造方法执行顺序
创建子类对象时,当调用子类的构造方法时,必须先调用父类的构造方法,如果子类的构造方法中没有调用父类的构造方法,则编译器会默认在子类的构造方法中的第一句加上super()来调用父类的默认无参构造方法,如果父类中没有无参的构造方法,则系统编译出错。
在创建子类对象时,它首先调用父类的构造方法,然后运行实例变量和静态变量的初始化器,最后才运行构造方法本身。默认情况下,会调用父类无参的构造方法。
例7.2继承关系下构造方法的调用。 ```java public class Ex_Consructor1{ public static void main(String[] args){
B b = new B();
} } class A{
A(){
} } class B extends A{ B(){System.out.println("A的构造方法");
} }System.out.println("B的构造方法");
运行结果如下图7.5所示。<br /><br />图7.5 Ex_Consructor1的运行结果<br />例7.3继承关系下构造方法的调用。
```java
public class Ex_Consructor2 {
public static void main(String[] args){
BB b = new BB();
}
}
class AA {
int a ;
AA(int a){
this.a = a;
System.out.println("AA构造方法");
}
}
class BB extends AA {
BB(){
System.out.println("BB构造方法");
}
}
如果把例子改为上面的状态,就会出现错误提示如下图7.6所示。
在上面的例子中可以看到如果父类中没有无参的构造方法,则在默认情况下,创建其子类对象时会出错。这时如果希望调用父类的有参构造方法,可以借助 super 完成。
修改上面的子类BB为:
class BB extends AA {
BB() {
super(1);
System.out.println("this is B constructor");
}
}
7.1.4 super和this关键字
super()的作用是调用父类的构造方法,调用构造方法时需注意:
- 只能出现在子类的构造方法中,且必须是第一行
- super()中的参数,决定了调用父类哪个构造方法
如果子类构造方法中没有出现super,那么编译器会默认加上super(),即调用父类的空构造方法,如果父类没有空构造方法,编译器提示错误。
this()的作用是调用本类的构造方法,调用构造方法时需注意:
- 只能写在构造方法的第一行
- 使用这两个关键字时需要注意在同一个构造方法中super()和this()不能同时出现。
关键字super的作用是指向父类的引用。通过关键字super我们可以指定子类在构造时调用父类的哪个构造方法,达到先实例化父类然后实例化子类的目的。子类的构造方法默认的调用父类无参构造方法,即子类构造方法中没有用super指明调用父类哪个构造方法的话,实际上编译器会自动的在子类构造方法第一行加入代码super( );
关键字this的作用是指向本类的引用,子类在实例化时必须调用父类的构造方法,实际上有的子类构造方法也可以先调用本类的其他构造方法,然后再通过那个构造方法调用父类的构造方法。无论是调用父类的构造方法还是子类的构造方法,最终都是找到最顶级的父类自上而下的实例化。只要中间环节有一个构造方法没找到,这个子类就无法完成实例化。
先来看一下第一种功能,调用父类的构造方法的例子如下:
例:调用父类构造方法。
public class Person { //父类Person
String id;
String name;
public Person(){ }
public Person(String id,String name){
this.id = id;
this.name = name;
}
}
public class Teacher extends Person{ //子类Teacher
public Teacher(){
super();
}
public Teacher(String id,String name){
super(id,name);
}
}
在子类的构造方法public Teacher()和public Teacher(String id,String name)中分别调用了父类中的构造方法public Person()和public Person(String id,String name),但不是直接通过父类中的构造方法名Person来调用,而是采用了super来代替父类中的构造方法的名称,否则就会出错;
如果子类的构造方法public Teacher()和public Teacher(String id,String name)中还有其他代码,则super();和super(id,name);必须位于子类构造方法的第一行,否则就会出错
可以做一个练习,将构造方法:
public Teacher(){
super();
}
改成:
public Teacher(){
System.out.println("this is constructor!");
super();
}
看看Java编译器会有什么反映呢,具体信息如下图7.8所示
再来看一下第二种功能,调用父类的属性或者方法的例子如下:
public class Person {
protected String birthday;
public String getBirthday() {
return this.birthday;
}
}
public class Teacher extends Person {
public String getBirthday() {
return super.getBirthday();
//或者return super.birthday;
}
}
7.2包的概念
7.2.1 包的概念以及应用
Java语言提供了把类名空间划分为更多易管理的块的机制,这种机制就是包。包既是命名机制也是可见度控制机制。我们可以在包内定义类,而且在包外的代码不能访问该类。这使得各个类之间有隐私,但不被外界所知。 在Java中,包主要有以下用途:
- 包允许将类组合成较小的单元
- 有助于避免命名冲突
- 包允许在更广的范围内保护类、数据和方法
- 包可以是类、接口和子包的集合 创建一个包是很简单的:只要包含一个package命令作为一个java源文件的第一句就可以了,该文件中定义的任何类将属于指定的包。package语句定义了一个存储类的名字空间。如果省略package语句,类名被输入一个默认的没有名称的包,多数情况,需要为自己的代码定义一个包。 将类放入包中的语法如下:
例如,下面的语句创建一个名为myPackage的包:
package myPackage;
Java用文件系统目录来存储包。
例如,任何声明为myPackage中的类的.cIass文件被存储在一个myPackage目录中。记住这种规则是很重要的,目录名称必须和包名严格匹配。多个文件可以包含相同package声明。package声明仅仅指定了文件中所定义的类属于哪一个包。它不拒绝其他文件的其他方法成为相同包的一部分。多数实际的包会包括很多文件。
我们可以创建包层次。为做到这点,只要将每个包名与它的上层包名用点号“.”分隔开就可以了。一个多级包的声明的通用形式如下:
package pkg1[.pkg2[.pkg3]];
包层次一定要在Java开发系统的文件系统中有所反映。例如,一个由下面语句定义的包:
package java.awt.image;
提示:
1.在java中位于包中的类,在文件系统中的存放位置,必须有与包名层次相对应的
目录结构;
- package语句作为java源文件的第一条语句;
- 每个源文件只能声明一个包;
-
7.2.2 java 类库中常用的包
Java类库中常用的包有多个,列举如下几类:
java.lang:Java编程语言的基本类库
java.awt:提供了创建用户界面以及绘制和管理图形、图像的类
javax.swing: 提供了一系列轻量级的用户界面组件,是目前Java用户界面常用的包
java.awt.event:图形用户界面事件处理包
java.sql:提供了访问和处理来自于Java标准数据源(通常是一个关系型数据库)数据的类
java.io:提供了通过数据流、对象序列以及文件系统实现的系统输入、输出
java.util:包括集合类、时间处理模式、日期时间工具等各类常用工具包
java.net:提供了用于实现网络通讯应用的所有类7.2.3 import导入包中的类
如果程序中需要使用其他包中的类,那么要先引入该包,才能不加前缀地调用这个类。引入其他包中的类的语法如下: import 包名.类名;
使用import关键字引入其它包中的类
位于同一包中的类可以直接访问
例:包的引入例子。package myweb.person;
public class Package_1{
public void intro(){
int age = 12;
String name = "甘罗";
System.out.println(name + ":" + age);
}
}
//在工作目录下建立文件来引用上面的包
import myweb.person.*;
public class Import_1{
public static void main(String args[]) {
Package_1 p = new Package_1();
p.intro();
}
}
提示:
只能引入其他包中的public类;
- 一个类可以使用多个import语句引入其他包;
import语句是在package语句(如果有)后的第一条非注释语句。
7.3可见性修饰符
7.3.1 封装
使用访问权限修饰符对类的成员进行控制,在java中称为“封装”。不过不要把封装理解为private,不要误认为不能访问成员才是封装。实际上对成员访问权限的任何控制(包括public)都可以称为封装机制。 封装就是信息的隐藏,隐藏对象的实现细节,不让用户看到,是将东西包装在一起,然后以新的完整形式呈现出来。例如,两种或多种化学药品组成一个胶囊,将方法和属性一起包装到一个单元中,单元以类的形式实现就是封装。封装的概念如下图7.9所示。
隐藏类的实现细节有如下的好处:让使用者只能通过事先定制好的方法来访问数据,可以方便地加入控制逻辑,限制对属性的不合理操作
- 便于修改,增强代码的可维护性
- 可进行数据检查
7.3.2访问权限修饰符
构造方法和类的权限通常为public;private权限最小,限制类外访问,一般把属性设为private,让其他类不能直接访问属性,达到保护属性的目的;friendly是默认权限,即不使用权限修饰符时,即为friendly,该权限声明的成员在类内以及在同一个包中的其他类可以访问;而protected所修饰的成员在类内、同一个包中、所在类的子类中都可以访问。
访问权限修饰符在类中的应用如下图所示。 public> protected > friendly > private
类的定义中在关键字class之前有修饰符,这里的修饰符就是访问控制修饰符,控制类的可见性,修饰符有两种,如表7.1所示。
名称 | 说明 | 备注 |
---|---|---|
public | 可以被所有类访问(使用) | public类必须定义在和类名相同的同名文件中 |
默认的 | 可以被同一个包中的类访问(使用) | 默认的访问权限,可以省略此关键字,可以定义在和public类的同一个文件中 |
类的成员包括两种:属性和方法,
首先介绍修饰属性的修饰符。
1. 属性的可见性修饰符
属性声明的语法结构为:
[修饰符] 变量类型变量名 [= 变量初始值];
Java中没有全局变量,只有方法变量、实例变量(类中的非静态变量)、类变量(类中的静态变量)。方法中的变量不能够有访问修饰符。所以下面访问修饰符表仅针对于在类中定义的变量,即属性。
声明实例变量时,如果没有赋初值,将被初始化为null(引用类型)或者0、false(原始类型)。类的属性和方法的可见性修饰符如下表7.2所示。
表7.2类的属性和方法的可见性修饰符
例:访问权限修饰符的应用。
public class Access {
private int pri;
int def;
protected int pro;
public int pub;
private void get1() {
System.out.println("The method is private");
}
void get2() {
System.out.println("The method is default");
}
protected void get3() {
System.out.println("The method is protected");
}
public void get4() {
System.out.println("The method is public");
}
}
public class TestAccess {
public static void main(String args[]) {
Access acc = new Access();
//System.out.println(acc.pri);
System.out.println(acc.def);
System.out.println(acc.pro);
System.out.println(acc.pub);
//acc.get1();
acc.get2();
acc.get3();
acc.get4();
}
}
程序的编译情况如下图所示。
程序的运行结果如下图所示。
提示:
- 带有可见性修饰符的变量是类的成员,而不是方法的局部变量。在方法内部使用访问权限修饰符会引起编译错误;
- 大多数情况下,构造方法应该是public的。但是,如果想防止用户创建类的实例,可以使用私有的构造方法。
封装隐藏了属性和方法的细节,那么子类继承父类所有属性和方法,但是子类是否可以直接访问父类所有的属性或方法呢?下面通过一个例子来看一下:
第一种情况:父类和子类在同一个包中。
package Base;
public class Father{
private String id;
String name;
protected int age;
public double salary;
}
class Son extends Father{
public void output(){
System.out.println("id is" + id);
System.out.println("name is" + name);
System.out.println("age is" + age);
System.out.println("salary is" + salary);
}
}
这个程序存在语法错误,如下图所示。
从这个图中可以看出,父子类在一个包中,子类可以继承父类中的非私有属性,这里用private修饰的属性,也能被子类继承,但是不能直接访问。
第二种情况:父类和子类不在同一个包中。
package Base;
public class Father {
private String id;
String name;
protected int age;
public double salary;
}
package Derived;
import Base.Father;
class Son extends Father{
public void output(){
System.out.println("id is" + id);
System.out.println("name is" + name);
System.out.println("age is" + age);
System.out.println("salary is" + salary);
}
}
这个程序存在语法错误,如下图所示。
如果父类不是public的,那么就会出现下面的情况,如下图所示。
这个图表示在导入父类的时候就出错了。因此,可以得出,父子类不在一个包中,子类可以继承 public 类中的,public 属性和 protected 属性。
对于问题“子类是否可以直接访问父类所有访问修饰符控制的属性或方法?”的答案为:
- 父子类在一个包中,子类可以直接访问父类中的非私有属性;
- 父子类不在一个包中,子类可以直接访问 public 类中的, public 属性和 protected 属性。
7.3.3构造器访问方法
由于对象不能直接通过引用访问私有数据域(属性或方法),为了能够访问到私有数据域,可以为私有数据域添加读取方法和设置方法。
1. 读取 - getter方法
为了能够访问私有数据域,可以编写一个getter方法返回该数据值,通常称getter方法为访问器。getter方法的定义形式如下:
public 返回值类型 get属性名(){ return 属性名; }
2 设置- setter方法
为了能够修改私有数据域,可以编写一个setter方法进行设置,通常称setter方法为设置器。setter方法的定义形式如下:
public void set属性名(数据类型参数值){ 属性名 = 参数值; }
例:封装的应用。 ```java
public class Employer {
private String name;
private int age;
private double salary;
public int getAge() {
return age;
}
// 在设置年龄的值时要做判断
public void setAge(int age) {
if (age < 18) {
System.out.println(“输入的年龄不可以小于18”);
} else {
this.age = age;
}
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
}
public class TestEmployer {
public static void main(String args[]){
Employer e = new Employer();
e.setAge(10);
System.out.println(e.getAge());
}
}
运行结果如下图所示。<br />
在上面的例子中,属性都是使用private来修饰的,在其他类中就不可以使用,需要使用访问器方法来访问。<br />例:访问器方法的应用。
```java
public class TestTelephone {
public static void main(String[] args) {
Telephone tel;
tel = new Telephone("TCL", "8309600", 100);
tel.setRate(0.2);
tel.setDialledTime(150);
tel.display();
tel.callCost();
tel.recharge(50);
}
}
class Telephone {
private String brand;
private String number;
private double dialledTime;
private double rate;
private double balance;
public Telephone(String brand, String number, double balance) {
this.brand = brand;
this.number = number;
this.balance = balance;
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
public double getDialledTime() {
return dialledTime;
}
public void setDialledTime(double dialledTime) {
this.dialledTime = dialledTime;
}
public double getRate() {
return rate;
}
public void setRate(double rate) {
this.rate = rate;
}
public double getBalance() {
return balance;
}
public void recharge(double cost) {
balance = balance + cost;
System.out.println("冲值后的余额:" + balance);
}
public void callCost() {
double callcost = dialledTime * rate;
balance = balance - callcost;
System.out.println("话费:" + callcost);
System.out.println("余额:" + balance);
}
public void display() {
System.out.println("电话品牌:" + brand + "电话号码:" + number);
System.out.println("通话时间:" +dialledTime+"费率:"+ rate);
}
}
7.4方法的覆盖
子类将从父类中继承下来的方法重新实现,叫做覆盖(Overriding)或重写(rewrite),方法覆盖的原因是父类中对应方法的行为不适合子类的需要,因此在子类中进行相应的调整。 覆盖(Overriding)是对从父类中继承来的方法进行改造,在子类继承父类时发生,在子类中的覆盖方法与父类中被覆盖的方法应具有:
- 相同的方法名
- 相同的参数列表(参数数量、参数类型、参数顺序都要相同)
- 相同的返回值类型
- 子类覆盖方法的访问权限要不小于父类中被覆盖方法的访问权限 接下来通过下面的例子来学习一下方法覆盖的应用。
例:覆盖的应用。
class Employee {
String name;
String birth;
double salary;
Employee(String name, String birth, double salary) {
this.name = name;
this.birth = birth;
this.salary = salary;
}
public void getDetail() {
System.out.println("name=" + name + " age=" + birth);
}
}
class Worker extends Employee {
double allownance;
void dressAllowance() {
allownance = 200;
}
public Worker(String name, String birth, double salary) {
super(name, birth, salary);
dressAllowance();
}
// 覆盖了父类的getDetail()方法
public void getDetail() {
System.out.println("name=" + name + " age=" + birth + " salary="
+ (salary + allownance));
}
}
public class TestWorker {
public static void main(String args[]){
Worker w = new Worker("zhangsan","1987-09-09",1500);
w.getDetail();
}
}
程序运行结果如下:
name=zhangsan age=1987-09-09 salary=1700.0
从上面的例子可以看出子类Worker从父类Employee继承了方法getDetail(),但是此方法的方法体并不能满足子类的输出总工资的功能,因此在子类Worker中对父类Employee的方法getDetail()进行了覆盖,添加了输出工资的功能。
7.5引用数据类型转换
7.5.1 上溯造型
上溯造型,即向上转型(Upcasting)是指子类转换为父类,这是自动转换;转换的前提是具有继承或实现关系。向上转型损失了子类新扩展的属性和方法,仅可以使用从父类中继承的属性和方法。上溯造型的例子如下图7.19所示。
下面通过一个例子来看一下上溯造型的应用。
例:上溯造型的应用。
public interface Consumer {
public void pay();
}
public class Student implements Consumer {
public String school;
public Student() {
}
public void study() {
System.out.println("I am studying in " + school);
}
public void pay() {
System.out.println("I consume with cash!");
}
}
public class TestStudent {
public static void main(String[] args) {
// 向上转型
Consumer c=new Student();//父类引用指向子类的对象
c.pay();
}
}
程序运行结果如下。
I consume with cash
在上面的例子中,定义了父类Consumer的引用c,但是此引用指向子类的对象,由程序的运行结果可知,当执行c.pay()语句时,调用的是子类的方法。由此可见,在上溯造型中Java的动态运行机制遵循的原则是,当父类引用指向子类对象时,是最终的指向类型而不是声明类型决定了调用谁的成员方法,但是这个被调用的方法必须是在父类中定义过的,也就是说是被子类覆盖的方法。
7.5.2下塑造型
下溯造型,即向下转型(Downcasting)称之为强制转换,是将父类对象显式的转换成子类类型。曾经向上转换过的对象,才能再向下转换。对象不允许不经过上溯造型而直接下溯造型。 如下写法是会出现语法错误:
Person p = new Person();
Student s = (Student)p;
下溯造型的例子如下图所示。
7.5.3 instanceof 运算符
其实经过上溯和下溯造型之后,我们很难知道某个引用到底指向哪种类型的对象了。可以通过instanceof来判断该经过上溯转型后是哪一个子类的。 instanceof 运算符用来判断一个类是否实现了某个接口和一个实例对象是否属于一个类,返回值类型都是布尔类型,具体语法如下: 判断一个类是否实现了某个接口:
判断一个实例对象是否属于一个类:
例: instanceof 运算符的应用。
public interface Consumer {
public void pay();
}
public class Person {
public String name;
public int age;
public String sex;
public Person(String name, int age, String sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
public Person() {
}
public void getInfo() {
System.out.println("The person's name is " + name + ",age is " + age
+ " and the sex is " + sex);
}
public void sayHello() {
System.out.println("Hello everybody");
}
}
public class Student extends Person implements Consumer {
public String school;
public Student(String name, int age, String sex, String school) {
super(name, age, sex);
this.school = school;
}
public Student() {
}
public void study() {
System.out.println("I am studying in " + school);
}
public void pay() {
System.out.println("I consume with cash!");
}
}
public class TestStudent {
public static void main(String args[]) {
Student tom = new Student();
tom.name = "Tom Cruze";
tom.age = 10;
tom.sex = "male";
tom.school = "YuCai School";
tom.getInfo();
tom.study();
tom.pay();
// 向上转型
Person cruze;
cruze = tom;
Student tt = (Student) cruze;
// 向下转型
Person stone = new Student();
Student judy;
judy = (Student) stone;
System.out.println(judy.school);
// instanceof运算符
if (tom instanceof Student) {
System.out.println(tom.name + " is a student!");
}
if (tom instanceof Person) {
System.out.println(tom.name + " is a person!");
}
if (tom instanceof Consumer) {
System.out.println(tom.name + " is a Consumer!");
}
}
}
7.6多态
多态是Java的重要特征之一,方法的覆盖、重载与动态绑定构成了多态性。多态性的实现与静态联编、动态联编有关。静态联编支持的多态性称为编译时的多态性,也称静态多态性,它是通过方法重载实现的;动态联编支持的多态性称为运行时的多态性,也称动态多态性,它是通过继承实现的。因此对于Java中的多态,可以简单理解为包括两种方式,一种是编译时多态,即重载;另外一种是运行时多态,即覆盖。简单来说,多态是具有表现多种形态的能力的特征,是同一个实现接口,使用不同的实例而执行不同操作,不同的对象对同一行为作出的不同响应。例如生活中的打印机的例子如下图7.23所示。
在上图中可以看见打印机有多种类别,例如彩色打印机和黑白打印机,虽然都是完成打印的功能,但是打印的效果是不同的,这就是多态,对于用户而言就是使用不同的打印机执行不同操作,不同的打印机对同一行为产生不同的结果。
多态存在的三个必要条件:
- 要有继承,或实现
- 要有重写
- 父类引用指向子类对象
一旦满足以上3个条件,当调用父类中被重写的方法后,运行时创建的是哪个子类的对象,就调用该子类中重写的那个方法。在执行期间(而非编译期间)判断所引用对象的实际类型,根据其实际类型调用相应的方法。
多态的优点:
- 简化代码
- 改善代码的组织性和可读性
- 易于扩展
接下来我们看一下多态的实际应用场景,主要包括:重载多态,覆盖多态,其中覆盖多态又包括创建对象时多态,传入参数多态等。
首先来看一下覆盖的应用。
练习:覆盖的应用。
在停车场收费系统中,收费者会根据车型的不同收取不同的停车费,其中,
- 客车:15元/小时
- 货车:12元/小时
- 轿车:8元/小时
编写程序,实现停车费用的计算。
public class Player {
String name;
double experience = 1;
double playHour;
public Player() {
}
public Player(String name) {
this.name = name;
}
public double addExperience(double rate) {
experience = experience * (1 + rate) * playHour;
return experience;
}
public static void main(String args[]) {
Player pp[] = new Player[4];
pp[0] = new Player("tangseng");
pp[1] = new Player("sunwukong");
pp[2] = new Player("zhubajie");
pp[3] = new Player("shahesang");
pp[0].playHour = 2.0;
pp[1].playHour = 1.5;
pp[2].playHour = 1.0;
pp[3].playHour = 0.5;
pp[0].addExperience(0.3);
pp[1].addExperience(1.0);
pp[2].addExperience(0.6);
pp[3].addExperience(0.8);
for (int i = 0; i < pp.length; i++) {
System.out.println("name=" + pp[i].name + " experience="
+ pp[i].experience + " playHour=" + pp[i].playHour);
}
}
}
public interface Vehicle {
public double payFees();
}
public class Bus implements Vehicle{
public double payFees(){
System.out.println("I am a bus!");
return 15.0;
}
}
public class Truck implements Vehicle {
public double payFees(){
System.out.println("I am a truck!");
return 12.0;
}
}
class Car implements Vehicle {
public double payFees() {
System.out.println("I am a car!");
return 3.0;
}
}
public class Parker {
public void chargeFees(Vehicle m, int hour) {
double fees = m.payFees();
System.out.println("您的停车时间为" + hour + "小时,请缴费" + fees * hour + "元!");
}
public static void main(String args[]){
Parker p = new Parker();
p.chargeFees(new Car(), 3);
p.chargeFees(new Bus(), 2);
p.chargeFees(new Truck(), 3);
}
}
程序运行结果如下图所示。
在上面的例题中子类Bus、Car和Truck分别继承了父类Vehicle的payFees()方法,并对该方法进行了覆盖。
接下来来看一下重载的应用。
例:重载涨工资的方法。
public class Employee{
String name;
int age;
double salary;
public Employee(){
name = "zhangsan";
age = 32;
salary = 2000;
}
public Employee(String n,int a,double s){
name = n;
age = a;
salary =s ;
}
//方法声明
void raise(double p){
salary = salary + p;
System.out.println("涨工资之后的工资为:" + salary);
}
void raise(){
salary = salary + salary * 0.05; //默认涨5%
System.out.println("涨工资之后的工资为:" + salary);
}
}
上面的例题包含了对构造方法(Employee)的重载和涨工资方法(raise)的重载。
重载和覆盖是多态的两种实现方式。重载方法是提供多于一个方法,这些方法的名字相同,但是参数形式不同;覆盖方法就是在子类定义一个方法,该方法与父类中的方法方法名相同,参数形式也相同,并且返回值类型也相同。
下面使用一个例子来介绍一下重载和覆盖的区别。在图a中,A类中有两个同名的方法method,但是他们的参数形式不同,一个方法带了一个是int类型的参数,另外一个带了一个double类型的参数,这两个方法之间的关系是重载;在图b中,B类中的方法method和它的父类A中的方法method的方法头是完全相同的,这两个方法之间的关系是覆盖。
重载和覆盖的对比情况如下图所示。
提示:
当参数可以通过类型转换进入到任意的方法入口时,程序优先匹配不用发生类型转换即可进入的方法。其次匹配类型转换级别较低的方法。
7.7静态
static可以修饰的元素包括:属性、方法和代码块。需要注意的问题是static只能修饰类成员,不能修饰局部变量。具体情况如下图7.26所示。
使用static修饰的属性称之为静态变量,静态变量是所有对象共享的,也称为类变量,用static修饰的成员变量,它们在类被载入时创建,只要类存在,static变量就存在。
静态变量有两种方式可以对其进行访问:
- 直接访问:类名.属性;
- 实例化后访问:对象名.属性
静态属性的应用如下图7.27所示。
使用static修饰的方法称之为静态方法,静态方法不需要实例化,可以直接访问,也称为类方法。
静态方法有两种方式可以对其进行访问:
- 直接访问:类名.方法名()
- 实例化后访问:对象名.方法名()
使用static修饰方法的作用有两点:
- 简化方法的使用;
- 便于访问静态属性。
使用静态方法须注意:
- 静态方法里只能直接访问静态成员,而不能直接访问类中的非静态成员
- 静态方法中不能使用this、super关键字
- 静态方法不能被非静态方法覆盖,static不能修饰构造方法
静态方法的使用例子如下图所示。
static还能修饰静态代码块,一个类中由static关键字修饰的,不包含在任何方法体中的代码块,当类被载入时,静态代码块被执行,且只被执行一次,静态块经常用来进行类属性的初始化。静态代码块的例子如下图所示。
例:static的应用。
class Chinese {
String name;
static String country;// 静态变量、类变量
String age;
/*
* 代码块:在类体内用花括号括起来的一组语句执行:当创建这个类的对象时,jvm会执行代码块中的内容
* 作用:当对类中的属性进行复杂的初始化时,使用代码块
*/
static {
System.out.println("静态的代码块");
System.out.println("hello");
}
static void sing() {
System.out.println(" 北京欢迎你");
}
void test() {
Chinese.sing();
sing();
}
}
class TestChinese {
public static void main(String args[]){
Chinese zhao = new Chinese();
zhao.country = "China";
System.out.println();
Chinese wang = new Chinese();
System.out.println("wang的国籍是"+wang.country);
Chinese.sing();
}
}
运行结果如下图所示。
例:静态方法的应用。
public class Icebox {
static void putThings(String things){
System.out.println("把冰箱门打开");
System.out.println("把"+things+"放进来");
System.out.println("把冰箱门关上");
}
}
public class Mine {
public static void main(String args[]){
Icebox.putThings("大象");
}
}
运行结果如下所示。
把冰箱门打开
把大象放进来
把冰箱门关上
7.8单例模式
单例模式(singleton)是保证一个类仅有一个实例,并提供一个访问它的全局访问点,单例模式要点包括:
- 某个类只能有一个实例
- 它必须自行创建这个实例
- 必须自行向整个系统提供这个实例 单例模式实现方式为
- 拥有一个私有构造方法
- 提供一个自身静态私有的成员变量
提供一个公有的静态方法 具体的实现模式如下:
public class Singleton {
//在自己内部定义自己的一个实例,只供内部调用
private static Singleton instance = new Singleton();
private Singleton(){
//do something
}
//这里提供了一个供外部访问本class的静态方法,可以直接访问
public static Singleton getInstance(){
return instance;
}
在上面的类Singleton中,仅有一个名为instance的私有属性,定义时就进行了初始化,使用static修饰,说明是类属性,有一个构造方法,但是是私有的,仅在当前类中可以使用,还有一个方法,返回当前类的对象,这就保证了类Singleton仅有一个实例。
7.9final关键字
final是一个关键字,可以修饰程序中的多种元素,包括类、方法、属性和局部变量。使用final修饰的类不能被继承。使用final修饰的变量(属性和局部变量)不能被重新赋值,在声明时赋值,或在构造方法中赋值,系统不会对final属性默认的赋初始值。使用final修饰的方法不能在子类中被覆盖,即不能修改。
在程序中经常使用的一些常量,如圆周率,没必要在程序中频繁的修改它,那么我们可以:首先把它设置为静态static,多个实例共享该常量,没有必要每个对象保存一份;
- 其次,设置为final类型,赋值以后不能再改变;
- 最后注意遵守常量命名规范,所有字母大写、单词之间用下划线。
学习了继承的应用,了解了类的继承关系,但是有时可能不希望某个类被其他的类继承,在这样的情况下需要使用修饰符final来说明一个类是终极的,不能做父类。例如Math类,还有String类都是终极类。
例:终极类。
public final class Final {
}
public class TestFinal extends Final {
public static void main(String args[]) {
}
}
程序编译情况如下图所示。
也可以把一个方法定义为终极的,终极方法不能被它的子类覆盖。
例:终极方法。
public class Final {
// final全局变量
final int Y = 100;
final double PI = 3.145666;
// final方法
public final int add(int x) {
// final局部变量
final int B = 8;
// final修饰的变量不可以被修改
// Y = Y + x;
return B + x;
}
}
public class TestFinal extends Final {
public int add(int x) {
return 0;
}
public static void main(String args[]) {
Final f = new Final();
// f.PI = 3.14;
}
}
程序的编译情况如下图所示。
用final修饰的属性和局部变量都不能被重新赋值。
例:属性和常量。
public final class Final {
// final全局变量
final int Y = 100;
final double PI = 3.145666;
// final方法
public int add(int x) {
// final局部变量
final int B = 8;
// final修饰的变量不可以被修改
Y = Y + x;
return B + x;
}
}
public class TestFinal {
public static void main(String args[]) {
Final f = new Final();
f.PI = 3.14;
}
}
程序编译结果如下图所示。
改变final修饰的属性值的程序编译结果如下图所示
在以后的学习中,还将进一步了解常量的应用。
提示:
final是唯一一个既可修饰属性又可以修饰局部变量的修饰符。
7.10 抽象类
在继承中学习了如何在已有类的基础上扩展出新的类,随着新类的出现,类越来越具体。但是反过来却不是这样,从子类向父类追溯,类就变的更通用。在面向对象的概念中,所有的对象都是通过类来描绘的,但是并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,它只能被继承,派生出子类,这样的类就是抽象类。
抽象类往往用来表征在对问题领域进行分析、设计中得出的抽象概念,是对一系列看上去不同,但是本质上相同的具体概念的抽象。比如:如果进行一个图形编辑软件的开发,就会发现问题领域存在着圆、三角形这样一些具体概念,它们是不同的,但是它们又都属于形状这样一个概念,形状这个概念在问题领域是不存在的,它就是一个抽象概念。正是因为抽象的概念在问题领域没有对应的具体概念,所以用以表征抽象概念的抽象类是不能够实例化的。
7.10.1 创建抽象类
在Java中定义抽象类的结构如下:
[public] abstract class 类名[extends父类][implements接口列表]
{
属性声明及初始化;
抽象方法的声明;
非抽象方法声明及方法体;
}
提示:
1.修饰抽象类的修饰符有public和默认修饰符两种;
2.抽象类中可以有抽象方法,也可以有非抽象的方法;
3.抽象方法是无方法体的方法。
定义抽象类时,要在关键字class的前面添加abstract。
例如:
abstract class Myclass {
int myint;
public abstract void noAction();
public int getMyint() {
return myint;
}
}
下面是定义一个抽象类的例子,定义了一个表示柜子的抽象类Chest,类有两个属性,分别代表宽和高,还有一个方法open,另外还有一个抽象方法storage,表示可以存放东西。
例:定义一个抽象类。
abstract class Chest {
double width;
double high;
public void open() {
System.out.println("柜子能打开");
}
public abstract void storage();
}
class Wardrobe extends Chest {
public void storage() {
System.out.println("衣柜能存放衣服");
}
}
class Cupboard extends Chest {
public void storage() {
System.out.println("橱柜能存放盘子和碗");
}
}
public class TestChest {
public static void main(String[] args) {
Wardrobe w = new Wardrobe();
w.open();
w.storage();
Cupboard c = new Cupboard();
c.open();
c.storage();
}
}
运行结果如下图所示。
注意:
- 抽象类不能被实例化;
- 其包含的抽象方法必须在其子类中被实现,否则该子类只能声明为abstract;
- 抽象方法不能为static;
- 在下列情况下,一个类必须声明为抽象类:
- 当一个类的一个或多个方法是抽象方法时;
- 当类是一个抽象类的子类,并且没有实现父类的所有抽象方法,即只实现部分;
- 当一个类实现一个接口,并且不能为全部抽象方法都提供实现时;
7.11接口
接口是一种用于描述类对外提供功能规范的、能够多继承的、特殊的抽象类。接口中只能定义静态常量和抽象方法。 那么为什么要定义接口呢?主要是某些现实问题需要用多继承描述,但又不适合放在父类中。例如下图7.37中这种情况:
由于类的多继承能够导致方法调用的冲突,所以Java中的类只能单继承。但是很多时候还是需要多继承的,Java中的接口就可以实现多继承,接口中不存在具体方法,不会引起方法调用的冲突。7.11.1创建接口
接口中只包含常量和抽象方法,而没有变量和方法的实现,接口对类来说是一套规范,是一套行为协议;接口不是一个类,不能实例化。接口规定了类的共同行为。在Java中,接口的声明采用interface关键字,接口不是一个类,没有构造方法,不能被实例化,接口定义的语法如下:
提示:[public] interface 接口名 [extends 父接口列表]{
// 属性声明
[public] [static] [final] 属性类型属性名 = 常量值 ;
// 方法声明
[public] [abstract] 返回值类型方法名 ( 参数列表 ) ;
}
1.修饰接口的修饰符只有public和默认修饰符两种;
2.接口可以是多继承,接口只能继承接口,不能继承类;
3.属性必须是常量(有初值),方法必须是抽象的(无方法体)。
例如: ```java public interface IA { public abstract int Action1();
public int Action2(); } //Java接口中不能包含具体实现的方法,如: public interface IB{ public void function(){
} } //上面的用法是错误的,正确的写法是: public interface IB{System.out.println(“Hello!”);
public void function(); } //在接口中除了包含抽象方法外,还可以包含常量的声明,如: public interface IA{
public static final int CODE=1001;
public int Action1();
public int Action2();
}
接口与类之间的关系:类实现了接口,一个类可以同时实现多个接口,一个接口可以被多个类实现。
```java
//定义AudioDevice接口。
public interface AudioDevice {
int MIN_VOLUME = 5;
public abstract void turnOn();
void turnOff();
public void turnVolume(int volume);
}
//定义VideoDevice接口。
public interface VideoDevice {
int MIN_BRIGHTNESS = 10;
void turnOn();
public void turnOff();
}
接口间的继承关系如下图所示。
例:定义VADevice接口。
public interface VADevice extends VideoDevice,AudioDevice {
}
由上面的例子可以看出,接口可以实现多继承,用接口可以实现混合类型(主类型,副类型),Java语言中可以通过接口分出主次类型,主类型使用继承,副类型,使用接口实现。接口可以使方法的定义和实现相分离,降低模块间或系统间的耦合性,针对接口编程可以屏蔽不同实现间的差异,看到的只是实现好的功能。
7.11.2实现接口
为了使用一个接口,就要编写实现接口的类,如果一个类要实现一个接口,那么这个类就必须实现接口中所有抽象方法。否则这个类只能声明为抽象类,多个无关的类可以实现一个接口,一个类可以实现多个无关的接口,一个类可以在继承一个父类的同时,实现一个或多个接口。
用implements子句表示一个类用于实现某个接口。一个类可以同时实现多个接口,接口之间用逗号“,”分隔。在类体中可以使用接口中定义的常量,由于接口中的方法为抽象方法,所以必须在类体中加入要实现接口方法的代码,如果一个接口是从别的一个或多个父接口中继承而来,则在类体中必须加入实现该接口及其父接口中所有方法的代码。
在实现一个接口时,类中对方法的定义要和接口中的相应的方法的定义相匹配,其方法名、方法的返回值类型、方法的访问权限和参数的数目与类型信息要一致,语法格式如下:
class 类名 [extends 父类] [implements 接口列表]
{
覆盖所有接口中定义的方法;
}
提示:
- 一个类可以同时实现多个接口,但只能继承一个类
- 类中必须覆盖接口中的所有方法,而且,都是公开的
例:实现多个接口的例子
public class A implements IA, IB{
public int Action1(){
System.out.println("this is Action1! ");
}‘
public int Action2(){
System.out.println("this is Action2! ");
}
public void function(){
System.out.println("this is function from IB ! ");
}
}
Java中,不允许一个类继承多个类,但允许一个类同时实现多个接口。
接口与抽象类之间的关系:抽象类是类,因此,接口与类之间的关系也适用于抽象类;此外应该注意的是,一个最常用的设计模式就是,抽象类实现接口,多个具体类继承抽象类,则多个具体类也间接的实现了接口。
接口与类的关系如下图所示。
下面来看一个实现接口的例子:
例:定义一个类实现圆柱体接口。
//定义一个圆柱体接口,代表所有圆柱体对象的共同行为
public interface ICylinder{
static final double PI=3.14; //说明圆周率常量
public double area(); // 计算圆柱体表面积的方法
public double bulk(); //计算圆柱体体积的方法
}
public class Cylinder implements ICylinder {
double r;
double h;
public Cylinder(double r, double h) {
this.r = r;
this.h = h;
}
public double area() {
return 2 * PI * r * (h + r);
}
public double bulk() {
return PI * r * r * h;
}
public static void main(String args[]) {
ICylinder c1 = new Cylinder(10, 6);
double arearesult;
arearesult = c1.area();
double bulkresult;
bulkresult = c1.bulk();
System.out.println("面积为" + arearesult);
System.out.println("体积为" + bulkresult);
}
}
程序运行结果如下所示。
面积为:1004.8000000000001
体积为:1884.0
定义Television实现VideoDevice和AudioDevice接口。
public class Television implements VideoDevice, AudioDevice {
int brightness;
int volume;
String signal;
public void turnOff() {
brightness = 0;
volume = 0;
System.out.println("The television is turned off");
}
public void turnOn() {
brightness = MIN_BRIGHTNESS;
volume = MIN_VOLUME;
System.out.println("The television is turned on");
}
public void turnVolume(int volume) {
this.volume = volume;
System.out.println("The television's volume is " + this.volume);
}
public void shiftChannel(String channel) {
System.out.println("change chanel to Channel " + channel);
}
public void changeSingnal(String signal) {
this.signal = signal;
System.out.println("The television's signal is " + this.signal);
}
public static void main(String args[]) {
Television sony = new Television();
sony.turnOn();
sony.changeSingnal("Digital");
sony.shiftChannel("CCTV_1");
sony.turnVolume(16);
sony.turnOff();
}
}
运行结果如下图所示。
练习:继承父类和实现接口综合应用例子。
有一个Student类,该类继承了Person类,并实现了Consumer接口,该类具有String类型的属性school,并有一个study方法,在该方法中,系统可打印出学生在哪所学校学习并创建一个测试方法,测试Student类。
本题中接口与类的继承关系如下图所示。
public interface Consumer {
public void pay();
}
public class Person {
public String name;
public int age;
public String sex;
public Person(String name, int age, String sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
public Person() {
}
public void getInfo() {
System.out.println("The person's name is " + name + ",age is " + age
+ " and the sex is " + sex);
}
public void sayHello() {
System.out.println("Hello everybody");
}
}
public class Student extends Person implements Consumer {
public String school;
public Student(String name, int age, String sex, String school) {
super(name, age, sex);
this.school = school;
}
public Student() {
}
public void study() {
System.out.println("I am studying in " + school);
}
public void pay() {
System.out.println("I consume with cash!");
}
}
接口与抽象类的对比,接口不能含有任何非抽象方法,而抽象类可以。类可以实现多个接口,但只能有一个父类。接口和接口之间可以多继承,如:
public interface A extends B,C
B,C也是接口.
抽象类可以理解为抽象方法和非抽象方法的混合体,而接口中的方法完全是抽象方法,是一套纯粹的规范。一般来说,有关系的类才能继承同一个抽象类,而无关的类不可能有同一个抽象父类,但是无关的类可以实现同一个接口。
7.12继承与多态的应用
练习1:
这个例子使用图形的继承关系为例来介绍继承和多态的应用。首先所有的图形都可以计算面积和体积,因此声明一个抽象类Shape,可利用它来派生出二维的几何形状类Circle和Rectangle,把计算面积的方法声明在抽象类里,pi值是常量,把它声明在抽象类的数据成员里。又利用Rectangle来派生出二维的几何形状类RectangleEx。其中包含了类的继承、方法的覆盖等知识点。
例:图形继承。
public class TestShape {
public static void main(String args[]) {
// 创建三个Shape对象
Shape shape[] = new Shape[3];
shape[0] = new Circle(50, 50, 40);
shape[1] = new Rectangle(0, 0, 40, 30);
shape[2] = new RectangleEx(20, 30, 40, 30, 5);
// 创建三个Shape对象的信息数组
String ShapeName[] = new String[3];
ShapeName[0] = "圆Circle(50, 50, 40)";
ShapeName[1] = "矩形Rectangle(0, 0, 40, 30)";
ShapeName[2] = "圆角矩形RectangleEx(20, 30, 40, 30, 5)";
// 求面积和周长
for (int i = 0; i < shape.length; i++) {
System.out.println(ShapeName[i] + "的面积=" + shape[i].area());
System.out.println(ShapeName[i] + "的周长=" + shape[i].perimeter());
System.out.println();
}
}
}
class Shape {
public final double PI = 3.141592654;
double area() {
return 0;
};
double perimeter() {
return 0;
};
}
// 子类Circle的声明
class Circle extends Shape {
int centerX; // 圆心X坐标
int centerY; // 圆心Y坐标
int radius; // 圆的半径
public Circle(int x, int y, int r) {
super();
centerX = x;
centerY = y;
radius = r;
}
public double area() {
return (int) (PI * radius * radius);
}
public double perimeter() {
return (int) (2 * PI * radius);
}
}
class Rectangle extends Shape {
int left; // 矩形左上角X坐标
int top; // 矩形左上角Y坐标
int width; // 矩形长度
int height; // 矩形宽度
public Rectangle(int l, int t, int w, int h) {
super();
left = l;
top = t;
width = w;
height = h;
}
public double area() {
return (int) (width * height);
}
public double perimeter() {
return (int) ((width + height) * 2);
}
}
class RectangleEx extends Rectangle {
int radius; // 圆角半径
public RectangleEx(int l, int t, int w, int h, int r) {
super(l, t, w, h);
radius = r;
}
public double area() {
return (int) (super.area() - (4 - PI) * radius * radius);
}
public double perimeter() {
return (int) ((width + height) * 2 - 8 * radius + 2 * PI * radius);
}
}
练习2:
定义Mobilephone,它是上一章节中的Telephone 的子类,除了具有 Telephone 类的属性外,它还有自己的属性如网络类型、被叫时间,同时它有自己的计算话费和显示信息的方法。最后程序中应包含一个主类来使用上述两个类并显示它们的信息。
例:电话的子类。
public class TestTelephone{
public static void main(String[] args) {
Telephone tel;
tel = new Telephone("步步高","84259588",80);
tel.setRate(0.3);
tel.setDialledTime(100);
tel.display();
tel.callCost();
tel.recharge(10);
Mobilephone mobile;
mobile=new Mobilephone("Nokia","13007091010",50,"CDMA");
mobile.setRate(0.4);
mobile.setReceivedTime(120);
mobile.display();
mobile.callCost();
mobile.recharge(100);
}
}
class Telephone{
private String brand;
private String number;
private double dialledTime;
private double rate;
double balance;
public Telephone(String brand, String number, double balance){
this.brand = brand;
this.number = number;
this.balance = balance;
}
public String getBrand(){
return brand;
}
public void setBrand(String brand){
this.brand = brand;
}
public String getNumber(){
return number;
}
public void setNumber(String number){
this.number = number;
}
public double getDialledTime(){
return dialledTime;
}
public void setDialledTime(double dialledTime){
this.dialledTime = dialledTime;
}
public double getRate(){
return rate;
}
public void setRate(double rate){
this.rate = rate;
}
public double getBalance(){
return balance;
}
public void recharge(double cost){
balance = balance + cost;
System.out.println("冲值后的余额:" + balance);
}
public void callCost(){
double callcost = dialledTime * rate;
balance = balance - callcost;
System.out.println("话费:" + callcost);
System.out.println("余额:" + balance);
}
public void display(){
System.out.println("电话品牌:" + brand + "电话号码:" + number);
System.out.println("通话时间:" + dialledTime + "费率:" + rate);
}
}
class Mobilephone extends Telephone {
private String network;
private double callTime;
private double receivedTime;
public String getNetwork() {
return network;
}
public void setNetwork(String network) {
this.network = network;
}
public double getCallTime() {
return callTime;
}
public void setCallTime(double callTime) {
this.callTime = callTime;
}
public double getReceivedTime() {
return receivedTime;
}
public void setReceivedTime(double receivedTime) {
this.receivedTime = receivedTime;
}
public Mobilephone(String brand,String num,double balance){
super(brand,num,balance);
}
public Mobilephone(String brand,String num,
double balance,String network) {
super(brand,num,balance);
this.network = network;
}
public void callCost(){
double callcost = (this.callTime + 0.5 * this.receivedTime)
* this.getRate();
balance = balance - callcost;
System.out.println("话费:" + callcost);
System.out.println("余额:" + balance);
}
public void display() {
System.out.println("电话品牌:" + getBrand()
+ "电话号码:" + getNumber() + "网络:"+ network);
System.out.println("主叫时间:" + callTime
+ "被叫时间:" + receivedTime + "费率:"+ getRate());
}
}
运行结果如下图所示。
思考:
如果Telephone类中的属性balance也定义为私有的,那么应该如何修改程序?
练习3:
客观世界中存在如下的实体关系,为了表示这种多的继承关系,可以声明一个抽象类表示二维图形,可利用它来派生出多个不同的二维几何形状类,然后把颜色定义为接口,各个类通过实现接口来实现多继承。
二维图形的继承关系如下图所示。
下面是这个例子的具体实现,定义一个抽象类Shape2D,把计算面积的方法声明在抽象类Shape2D里,pi值是常量,把它声明在抽象类的数据成员里。利用Shape2D派生出两个子类Circle和Rectangle,分别表示圆形和矩形,例:抽象类在图形中的应用。
public class TestCircle {
public static void main(String args[]) {
Rectangle rect = new Rectangle(5, 6);
System.out.println("Area of rect = " + rect.area());
Circle cir = new Circle(2.0);
System.out.println("Area of cir = " + cir.area());
}
}
abstract class Shape2D {
final double pi = 3.14;
public abstract double area();
}
class Circle extends Shape2D {
double radius;
public Circle(double r) {
radius = r;
}
public double area() {
return (pi * radius * radius);
}
}
class Rectangle extends Shape2D {
int width, height;
public Rectangle(int w, int h) {
width = w;
height = h;
}
public double area() {
return (width * height);
}
}
运行结果如下
Area of rect=30.0
Area of cir = 12.56
例:接口在图形的应用。
一个类只能继承一个父类,但是可以实现多个接口,如果需要继承多个类,就可以通过实现接口来实现了。现在对上面的例题进行扩展,定义一个表示颜色的接口Color,声明ColorCircle类继承Shape2D,并且实现接口。
1. Shape2D具有pi与area()方法,用来计算面积;
2. Color则具有setColor方法,可用来赋值颜色;
3. 通过继承抽象类,实现一个接口,ColorCircle类达到了多继承的目的。
public class TestColorCircle {
public static void main(String args[]) {
ColorCircle cir;
cir = new ColorCircle(2.0);
cir.setColor("blue");
System.out.println("Area = " + cir.area());
}
}
abstract class Shape2D {
final double pi = 3.14;
public abstract double area();
}
interface Color {
void setColor(String str);
}
class ColorCircle extends Shape2D implements Color {
double radius;
String color;
public ColorCircle(double r) {
radius = r;
}
public double area() {
return (pi * radius * radius);
}
public void setColor(String str) {
color = str;
System.out.println("color=" + color);
}
}
运行结果如下
color=blue
Area=12.56
【总结与提示】
- Java中使用extends关键字实现继承,子类可以继承父类的属性和方法;
- 在继承关系中,构造方法的调用是先从父类开始;
- super代表父类,this代表当前类,要区分this关键字和super关键字;
- 包(package)是用来保存划分的类名空间;
- 如果写package语句,那么它必须是文件中第一条非注释语句;
- 如果写import语句,那么它必须是继package语句后的第一条非注释语句。
- 可见性修饰符是指定类、方法和属性的访问权限的;
- 类的可见性修饰符有两种,其中public是包内外都可见,默认的是只能包内可见;
- 类的成员的可见性修饰符有四种,其中public是包内+包外可访问,protected是包内+包外子类可访问,默认的为包内可访问,private是类内可访问;
- 向上转型(Upcasting)是指子类转换为父类,这是自动转换;向下转型(Downcasting)称之为强制转换,是将父类对象显式的转换成子类类型;
- 重载和覆盖是多态的两种实现方式。重载方法是提供多于一个方法,这些方法的名字相同,但是参数形式不同;覆盖方法就是在子类定义一个方法,该方法与父类中的方法方法名相同,参数形式也相同,并且返回值类型也相同;
- static可以修饰的元素包括:属性、方法和代码块。需要注意的问题是static只能修饰类成员,不能修饰局部变量;
- 单例模式(singleton)是保证一个类仅有一个实例,并提供一个访问它的全局访问点;
- final是一个关键字,可以修饰程序中的多种元素,包括类、方法、属性和局部变量;
- 使用abstract修饰的类为抽象类,抽象类中可以有抽象方法,也可以有非抽象的方法,抽象方法是无方法体的方法;
- 在Java中,接口的声明采用interface关键字,接口不是一个类,没有构造方法,不能被实例化;
- 接口中只能存在常量和抽象方法;
内部类就是定义在另一个类内部的类。内部类对于同一包中的其它类来说,内部类能够隐藏起来。
7.13课后作业
一)选择题
1. 以下哪个接口的定义是正确的?
A.interface B{ void print() { } ; }
B.abstract interface B { void print() ; }
C.abstract interface B extends A1,A2 { abstract void print(){ }; }
D.interface B { void print();}定义一个接口时,下列哪个关键字用不到?
A.public
B.extends
C.interface
D.class定义一个接口时,要使用如下哪个关键字?
A.abstract
B.final
C.interface
D.class在使用interface声明一个接口时,只可以使用哪个修饰符修饰该接口。
A.private
B.protected
C.private或者protected
D.public下列类头定义中,错误的是 ?
A.public x extends y
B.public class x extends y
C.class x extends y implements y1
D.class x下列类定义中,不正确的是?
A.class x
B.class x extends y
C.class x implements y1,y2
D.public class x extends X1,X2Java中能实现多继承的方式是?
A.接口
B.同步
C.抽象类
D.父类下列叙述正确的是?
A.Java中允许多继承
B.Java一个类只能实现一个接口
C.Java中只能单重继承
D.Java中一个类可以继承多个抽象类若在某一个类定义中定义有如下的方法:static void testMethod( ) 该方法属于 ?
A.本地方法
B.最终方法
C.静态方法
D.抽象方法面向对象的特点是?
A.继承 封装 多态
B.继承 接口 对象
C.消息 继承 类
D.接口 继承 类
(二)编程题
1) 创建一个球员类,并且该类最多只允许创建十一个对象。提示利用 static 和封装性来完成。
类图如下:
效果如下:
2) (1)定义一个汽车类Vehicle,要求如下:
(a)属性包括:汽车品牌brand(String类型)、颜色color(String类型)和速度speed(double类型)。
(b)至少提供一个有参的构造方法(要求品牌和颜色可以初始化为任意值,但速度的初始值必须为0)。
(c)为属性提供访问器方法。注意:汽车品牌一旦初始化之后不能修改。
(d)定义一个一般方法run(),用打印语句描述汽车奔跑的功能
定义测试类VehicleTest,在其main方法中创建一个品牌为“benz”、颜色为“black”的汽车。
(2)定义一个Vehicle类的子类轿车类Car,要求如下:
(a)轿车有自己的属性载人数loader(int 类型)。
(b)提供该类初始化属性的构造方法。
(c)重新定义run(),用打印语句描述轿车奔跑的功能。
(d)定义测试类Test,在其main方法中创建一个品牌为“Honda”、颜色为“red”,载人数为2人的轿车。
3) 设计四个类,分别是:
(1)Shape表示图形类,有面积属性area、周长属性per,颜色属性color,有两个构造方法(一个是默认的、一个是为颜色赋值的),还有3个抽象方法,分别是:getArea计算面积、getPer计算周长、showAll输出所有信息,还有一个求颜色的方法getColor。
(2)2个子类:
1)Rectangle表示矩形类,增加两个属性,Width表示长度、height表示宽度,重写getPer、getArea和showAll三个方法,另外又增加一个构造方法(一个是默认的、一个是为高度、宽度、颜色赋值的)。
2)Circle表示圆类,增加1个属性,radius表示半径,重写getPer、getArea和showAll三个方法,另外又增加两个构造方法(为半径、颜色赋值的)。
(3)一个测试类PolyDemo,在main方法中,声明创建每个子类的对象,并调用2个子类的showAll方法。
4) Cola公司的雇员分为以下若干类:
(1) ColaEmployee :这是所有员工总的父类,属性:员工的姓名,员工的生日月份。
方法:getSalary(int month) 根据参数月份来确定工资,如果该月员工过生日,则公司会额外奖励100 元。
(2) SalariedEmployee : ColaEmployee 的子类,拿固定工资的员工。
属性:月薪
(3) HourlyEmployee :ColaEmployee 的子类,按小时拿工资的员工,每月工作超出160 小时的部分按照1.5 倍工资发放。
属性:每小时的工资、每月工作的小时数
(4) SalesEmployee :ColaEmployee 的子类,销售人员,工资由月销售额和提成率决定。
属性:月销售额、提成率
(5) 定义一个类Company,在该类中写一个方法,调用该方法可以打印出某月某个员工的工资数额,写一个测试类TestCompany,在main方法,把若干各种类型的员工放在一个ColaEmployee 数组里,并单元出数组中每个员工当月的工资。
5) 利用接口实现动态的创建对象:
(1)创建4个类
苹果
香蕉
葡萄
园丁
(2)在三种水果的构造方法中打印一句话.
以苹果类为例
class apple{
public apple(){
System.out.println(“创建了一个苹果类的对象”);
}
}
(3)类图如下:
(4)要求从控制台输入一个字符串,根据字符串的值来判断创建三种水果中哪个类的对象。
运行结果如图:
6) 编写三个系别的学生类:英语系,计算机系,文学系(要求通过继承学生类),各系有以下成绩:
英语系:演讲,期末考试,期中考试;
计算机系:操作能力,英语写作,期中考试,期末考试;
文学系:演讲,作品,期末考试,期中考试;
各系总分评测标准:
英语系:
演讲 50%
期末考试 25%
期中考试 25%
计算机系:
操作能力 40%
英语写作 20%
期末考试 20%
期中考试 20%
文学系:
演讲 35%
作品 35%
期末考试 15%
期中考试 15%
定义一个可容纳5个学生的学生类数组,使用随机数给该数组装入各系学生的对象,然后按如下格式输出数组中的信息:
学号:XXXXXXXX 姓名:XXX 性别:X 年龄:XX 综合成绩:XX
(三)扩充题
1) 下列程序的输出结果是什么?
public class Foo{
static int i = 0;
static int j = 0;
public static void main(String[] args) {
int i = 2;
int k = 3;
int j = 3;
System.out.println("i + j is " + i + j);
k = i + j;
System.out.println("k is " + k);
System.out.println("j is " + j);
}
}
2) 请写出下列输出结果。
class FatherClass {
public FatherClass() {
System.out.println("FatherClass Create");
}
}
class ChildClass extends FatherClass {
public ChildClass() {
System.out.println("ChildClass Create");
}
public static void main(String[] args) {
FatherClass fc = new FatherClass();
ChildClass cc = new ChildClass();
}
}
3) 下列程序的输出结果是什么?
class Base {
int i = 99;
public void amethod(){
System.out.println("Base.amethod()");
}
Base(){
amethod();
}
}
class Derived extends Base{
int i = -1;
public static void main(String argv[]){
Base b = new Derived();
System.out.println(b.i);
b.amethod();
}
public void amethod(){
System.out.println("Derived.amethod()");
}
}
4) 现有类说明如下,请回答问题:
class A{
String x=”gain”;
String str=" no pain ";
public String toString(){
return str+x;
}
}
public class B extends A{
String x=" no ";
public String toString(){
return str+x+" and "+super.x;
}
}
问题:类A和类B是什么关系?若a是类A的对象,则a.toString( )的返回值是什么?若b是类B的对象,则b.toString( )的返回值是什么?
5) 下面是一个类的定义,填写程序空白处。
class B {
private int x;
private char y;
public B(_______ , char j) {
x = i;
y = j;
}
public getX(){
return x;
}
public void setX(int x){
}
public char getY(){
return y;
}
public void setY(______){
this.y=y;
}
}
6) 定义类A和类B如下.:
class A{
int m=1;
double n=2.0;
void print( ) {
System.out.println("Class A: m="+m +",n="+n);
}
}
class B extends A{
float m=3.0f;
String n="Good .";
void print( ) {
super.print( );
System.out.println("Class B: m="+m +",n="+n);
}
}
问题:
(1) 若在应用程序的main方法中有以下语句:
A a=new A();
a.print();
则输出的结果如何?
(2) 若在应用程序的main方法中定义类B的对象b:
B b=new B();
b.print();
则输出的结果如何?
7) 写出程序的运行结果
class Parent {
void printMe() {
System.out.println("parent"); }
}
class Child extends Parent {
void printMe() {
System.out.println("child");
}
void printAll() {
super.printMe();
this.printMe();
printMe();
}
}
public class T {
public static void main(String args[]) {
Child myC = new Child();
myC.printAll();
}
}
8) 请写出下列输出结果。
public class Class1 {
public static void main(String[] args) {
A a1 = new A();
a1.printa();
B b1 = new B();
b1.printb();
b1.printa();
}
}
class A {
int x = 1;
void printa() {
System.out.println(x);
}
}
class B extends A {
int x = 100;
void printb() {
super.x = super.x + 10;
System.out.println("super.x=" + super.x + "x=" + x);
}
}
9) 请写出下列输出结果。
interface A {
int x = 1;
void showX();
}
interface B {
int y = 2;
void showY();
}
class InterfaceTest implements A, B {
int z = 3;
public void showX() {
System.out.println("x=" + x);
}
public void showY() {
System.out.println("y=" + y);
}
public void showMe() {
System.out.println("z=" + (z + x + y));
}
}
public class Class1 {
public static void main(String[] args) {
InterfaceTest myObject = new InterfaceTest();
myObject.showX();
myObject.showY();
myObject.showMe();
}
}
10) 定义一个抽象类,包括能求面积的抽象方法
public class Test6 {
public abstract double area();
}
11) 下面是定义一个接口ITF的程序,完成程序填空。
public ___________ ITF{
public static final double PI=Math.PI;
public abstract double area(double a, double b);
}
12) 定义一个接口Test,类T实现接口Test,完成程序填空。
public Test {
void test();
}
class _________________{
public void test(){
}
}
[