JAVA教程全集-电子版(中)(java教程电子书下载)
第4章 面向对象的程序设计基础
如前所述,Java语言是一种纯面向对象的编程语言,面向对象的程序设计是以类为基础的。从本章开始,我们将从类入手,详细介绍面向对象程序设计的基本思想和方法。
本章将简要介绍面向对象的基本概念、定义类、构造对象等。
4.1 面向对象的基本概述
随着计算机应用的深入,软件的需求量越来越大,另一方面计算机硬件飞速发展也使得软件的规模越来越大,导致软件的生产、调试、维护越来越困难,因而发生了软件危机。人们期待着一种效率高、简单、易理解且更加符合人们思维习惯的程序设计语言,以加快软件的开发步伐、缩短软件开发生命周期,面向对象就是在这种情况下应运而生的。
4.1.1 类和对象
我们可以把客观世界中的每一个实体都看作是一个对象,如一个人、一辆汽车、一个按钮、一只鸟等等。因此对象可以简单定义为:“展示一些定义好的行为的、有形的实体”。当然在我们的程序开发中,对象的定义并不局限于看得见摸得着的实体,诸如一个贸易公司,它作为一个机构,并没有物理上的形状,但却具有概念上的形状,它有明确的的经营目的和业务活动。根据面向对象的倡导者Grady Booch的理论,对象具有如下特性:
1) 它具有一种状态;
2) 它可以展示一种行为;
3) 它具有唯一的标识。
对象的状态通过一系列属性及其属性值来表示;对象的行为是指在一定的期间内属性的改变;标识是用来识别对象的,每一个对象都有唯一的标识,诸如每个人都有唯一的特征,在社会活动中,使用身份证号码来识别。
我们生活在一个充满对象的世界中,放眼望去,不同形状、不同大小和颜色各异的对象;静止的和移动的对象。面对这些用途各异、五花八门的对象,我们该如何下手处理它们呢?借鉴于动物学家将动物分成纲、属、科、种的方法。我们也可以把这些对象按照它们所拥有的共同属性进行分类。例如,麻雀、鸽子、燕子等都叫作鸟。它们具有一些共同的特性:有羽毛、有飞翔能力、下蛋孵化下一代等。因此我们把它们归属为鸟类。
综上所述我们可以简单地把类定义为:“具有共同属性和行为的一系列对象”。
4.1.2 面向对象的特点
1. 什么是面向对象
面向对象的方法将系统看作是现实世界对象的集合,在现实世界中包含被归类的对象。如前所述,面向对象系统是以类为基础的,我们把一系列具有共同属性和行为的对象划归为一类。属性代表类的特性,行为代表有类完成的操作。
例如:例如汽车类定义了汽车必须有属性:车轮个数、颜色、型号、发动机的能量等;类的行为有:启动、行驶、加速、停止等。
对象是类的一个实例,它展示了类的属性和行为。例如,李经理的那辆红旗牌轿车就是汽车类的一个对象。
2. 面向对象的特性
1)抽象
所谓抽象就是不同的角色站在不同的角度观察世界。比如,当你购买电视机时,站在使用的角度,你所关注的是电视机的品牌、外观及功能等等。然而,对于电视机的维修人员来说,站在维修的角度,他们所关注的是电视机的内部,各部分元器件的组成及工作原理等。
其实,所有编程语言的最终目的都是提供一种“抽象”方法。在早期的程序设语言中,一般把所有问题都归纳为列表或算法,其中一部分是面向基于“强制”的编程,而另一部分是专为处理图形符号设计的。每种方法都有自己特殊的用途,只适合解决某一类的问题。面向对象的程序设计可以根据问题来描述问题,不必受限于特定类型的问题。我们将问题空间中的元素称之为“对象”,在处理一个问题时,如果需要一些在问题空间没有的其他对象,则可通过添加新的对象类型与处理的问题相配合,这无疑是一种更加灵活、更加强大的语言抽象方法。
2)继承
继承提供了一种有助于我们概括出不同类中共同属性和行为的机制,并可由此派生出各个子类。
例如,麻雀类是鸟类的一个子类,该类仅包含它所具有特定的属性和行为,其他的属性和行为可以从鸟类继承。我们把鸟类称之为父类(或基类),把由鸟类派生出的麻雀类称之为子类(或派生类)。
在Java中,不允许类的多重继承(即子类从多个父类继承属性和行为),也就是说子类只允许有一个父类。父类派生多个子类,子类又可以派生多个子子类,… 这样就构成了类的层次结构。
3)封装
封装提供了一种有助于我们向用户隐藏他们所不需要的属性和行为的机制,而只将用户可直接使用的那些属性和行为展示出来。
例如,使用电视机的用户不需要了解电视机内部复杂工作的具体细节,他们只需要知道诸如:开、关、选台、调台等这些设置与操作就可以了。
4)多态
多态指的是对象在不同情况下具有不同表现的一种能力。
例如,一台长虹牌电视机是电视机类的一个对象,根据模式设置的不同,它有不同的表现。若我们把它设置为静音模式,则它只播放画面不播放声音。
在Java中通过方法的重载和覆盖来实现多态性。
3. 面向对象的好处
今天我们选择面向对象的程序设计方法,其主要原因是:
1)现实的模型
我们生活在一个充满对象的现实世界中,从逻辑理念上讲,用面向对象的方法来描述现实世界的模型比传统的过程方法更符合人的思维习惯。
2)重用性
在面向对象的程序设计过程中,我们创建了类,这些类可以被其他的应用程序所重用,这就节省程序的开发时间和开发费用,也有利于程序的维护。
3)可扩展性
面向对象的程序设计方法有利于应用系统的更新换代。当对一个应用系统进行某项修改或增加某项功能时,不需要完全丢弃旧的系统,只需对要修改的部分进行调整或增加功能即可。可扩展性是面向对象程序设计的主要优点之一。
4.2 类
面向对象的程序设计是以类为基础的,Java程序是由类构成的。一个Java程序至少包含一个或一个以上的类。
4.2.1 定义类
如前所述,类是对现实世界中实体的抽象,类是一组具有共同特征和行为的对象的抽象描述。因此,一个类的定义包括如下两个方面:
定义属于该类对象共有的属性(属性的类型和名称);
定义属于该类对象共有的行为(所能执行的操作即方法)。
类包含类的声明和类体两部分,其定义类的一般格式如下:
[访问限定符] [修饰符] class 类名 [extends 父类名] [implements 接口名列表>]//类声明
{ //类体开始标志
[类的成员变量说明] //属性说明
[类的构造方法定义]
[类的成员方法定义] //行为定义
} //类体结束标志
对类声明的格式说明如下:
1) 方括号“[]”中的内容为可选项,在下边的格式说明中意义相同,不再重述。
2) 访问限定符的作用是:确定该定义类可以被哪些类使用。可用的访问限定符如下:
a) public 表明是公有的。可以在任何Java程序中的任何对象里使用公有的类。该限定符也用于限定成员变量和方法。如果定义类时使用public进行限定,则类所在的文件名必须与此类名相同(包括大小写)
b) private表明是私有的。该限定符可用于定义内部类,也可用于限定成员变量和方法。
c) protected 表明是保护的。只能为其子类所访问。
d) 默认访问 若没有访问限定符,则系统默认是友元的 (friendly)。友元的类可以被本类包中的所有类访问。
3) 修饰符的作用是:确定该定义类如何被其他类使用。可用的类修饰符如下:
a) abstract 说明该类是抽象类。抽象类不能直接生成对象。
b) final 说明该类是最终类,最终类是不能被继承的。
4) class是关键字,定义类的标志(注意全是小写)。
5) 类名是该类的名字,是一个Java标识符,含义应该明确。一般情况下单词首字大写。
6) 父类名跟在关键字 “extends”后,说明所定义的类是该父类的子类,它将继承该父类的属性和行为。父类可以是Java类库中的类,也可以是本程序或其他程序中定义的类。
7) 接口名表是接口名的一个列表,跟在关键字“implements”后,说明所定义的类要实现列表中的所有接口。一个类可以实现多个接口,接口名之间以逗号分隔。如前所述,Java不支持多重继承,类似多重继承的功能是靠接口实现的。
以上简要介绍了类声明中各项的作用,我们将在后边的章节进行详细讨论。
类体中包含类成员变量和类方法的声明及定义,类体以定界符左大括号“{”开始,右大括号“}”结束。类成员变量和类方法的声明及定义将下边各节中进行详细讨论。
我们先看一个公民类的定义示例。
public class Citizen
{
[ 声明成员变量 ] //成员变量(属性)说明
[ 定义构造方法 ] //构造方法(行为)定义
[ 定义成员方法 ] //成员方法(行为)定义
}
我们把它定义为公有类,在任何其他的Java程序中都可以使用它。
4.2.2 成员变量
成员变量用来表明类的特征(属性)。声明或定义成员变量的一般格式如下:
[访问限定符] [修饰符] 数据类型 成员变量名[=初始值];
其中:
1)访问限定符用于限定成员变量被其它类中的对象访问的权限,和如上所述的类访问限定符类似。
2)修饰符用来确定成员变量如何在其他类中使用。可用的修饰符如下:
a) static 表明声明的成员变量为静态的。静态成员变量的值可以由该类所有的对象共享,它属于类,而不属于该类的某个对象。即使不创建对象,使用“类名.静态成员变量”也可访问静态成员变量。
b) final 表明声明的成员变量是一个最终变量,即常量。
c) transient 表明声明的成员变量是一个暂时性成员变量。一般来说成员变量是类对象的一部分,与对象一起被存档(保存),但暂时性成员变量不被保存。
d) volatile 表明声明的成员变量在多线程环境下的并发线程中将保持变量的一致性。
3)数据类型可以是简单的数据类型,也可以是类、字符串等类型,它表明成员变量的数据类型。
类的成员变量在类体内方法的外边声明,一般常放在类体的开始部分。
下边我们声明公民类的成员变量,公民对象所共有的属性有:姓名、别名、性别、出生年月、出生地、身份标识等。
import java.util.*;
public class Citizen
{
//以下声明成员变量(属性)
String name;
String alias;
String sex;
Date brithday; //这是一个日期类的成员变量
String homeland;
String ID;
//以下定义成员方法(行为)
……
}
在上边的成员变量声明中,除出生年月被声明为日期型(Date)外,其他均为字符串型。由于Date类被放在java.util类包中,所以在类定义的前边加上import语句。
4.2.3 成员方法
方法用来描述对象的行为,在类的方法中可分为构造器方法和成员方法,本小节先介绍成员方法。
成员方法用来实现类的行为。方法也包含两部分,方法声明和方法体(操作代码)。
方法定义的一般格式如下:
[访问限定符] [修饰符] 返回值类型 方法名([形式参数表]) [throws 异常表]
{
[ 变量声明 ] //方法内用的变量,局部变量
[ 程序代码 ] //方法的主体代码
[ return [ 表达式 ] ] //返回语句
}
在方法声明中:
1)访问限定符如前所述。
2)修饰符用于表明方法的使用方式。可用于方法的修饰符如下:
a) abstract说明该方法是抽象方法,即没有方法体(只有“{}”引起的空体方法)。
b) final说明该方法是最终方法,即不能被重写。
c) static说明该方法是静态方法,可通过类名直接调用。
d) native说明该方法是本地化方法,它集成了其他语言的代码。
e) synchronized说明该方法用于多线程中的同步处理。
3)返回值类型应是合法的java数据类型。方法可以返回值,也可不返回值,可视具体需要而定。当不需要返回值时,可用void(空值)指定,但不能省略。
4)方法名是合法Java标识符,声明了方法的名字。
5)形式参数表说明方法所需要的参数,有两个以上参数时,用“,”号分隔各参数,说明参数时,应声明它的数据类型。
6)throws 异常表定义在执行方法的过程中可能抛出的异常对象的列表(放在后边的异常的章节中讨论)。
以上简要介绍了方法声明中各项的作用,在后边章节的具体应用示例中再加深理解。
方法体内是完成类行为的操作代码。根据具体需要,有时会修改或获取对象的某个属性值,也会访问列出对象的相关属性值。下边还以公民类为例介绍成员方法的应用,在类中加入设置名字、获取名字和列出所有属性值3个方法。
例4.1 完善公民类Citizen。程序如下:
/* 这是一个公民类的定义程序
* 程序的名字是:Citizen.java
*/
import java.util.*;
public class Citizen
{
//以下声明成员变量(属性)
String name;
String alias;
String sex;
Date brithday; //这是一个日期类的成员变量
String homeland;
String ID;
//以下定义成员方法(行为)
public String getName() //获取名字方法
{ //getName()方法体开始
return name; //返回名字
} //getName()方法体结束
/***下边是设置名字方法***/
public void setName(String name)
{ //setName()方法体开始
this.name=name;
} //setName()方法体结束
/***下边是列出所有属性方法***/
public void displayAll()
{ //displayAll()方法体开始
System.out.println(“姓名:”+name);
System.out.println(“别名:”+alias);
System.out.println(“性别:”+sex);
System.out.println(“出生:”+brithday.toLocaleString());
System.out.println(“出生地:”+homeland);
System.out.println(“身份标识:”+ID);
} //displayAll()方法体结束
}
在上边的示例中,两个方法无返回值(void),一个方法返回名字(String);两个方法不带参数,一个方法带有一个参数,有关参数的使用将在后边介绍。在显示属性方法中,出生年月的输出使用了将日期转换为字符串的转换方法toLocaleString()。
需要说明的是,在设置名字方法setName()中使用了关键字this,this代表当前对象,其实在方法体中引用成员变量或其他的成员方法时,引用前都隐含着“this.”,一般情况下都会缺省它,但当成员变量与方法中的局部变量同名时,为了区分且正确引用,成员变量前必须加“this.”不能缺省。
4.2.4 构造方法
构造方法用来构造类的对象。如果在类中没有构造方法,在创建对象时,系统使用默认的构造方法。定义构造方法的一般格式如下:
[public] 类名([形式参数列表])
{
[方法体]
}
我们可以把构造方法的格式和成员方法的格式作一个比较,可以看出构造方法是一个特殊的方法。应该严格按照构造方法的格式来编写构造方法,否则构造方法将不起作用。有关构造方法的格式强调如下:
1)构造方法的名字就是类名。
2)访问限定只能使用public或缺省。一般声明为public,如果缺省,则只能在同一个包中创建该类的对象。
3)在方法体中不能使用return语句返回一个值。
下边我们在例4.1定义的公民类Citizen中添加如下的构造方法:
public Citizen(String name,String alias,String sex,Date brithday,String homeland,String ID)
{
this.name=name;
this.alias=alias;
this.sex=sex;
this.brithday=brithday;
this.homeland=homeland;
this.ID=ID;
}
到此为止,我们简要介绍了类的结构并完成了一个简单的公民类的定义。
4.3 对象
我们已经定义了公民(Citizen)类,但它只是从“人”类中抽象出来的模板,要处理一个公民的具体信息,必须按这个模板构造出一个具体的人来,他就是Citizen类的一个实例,也称作对象。
4.3.1 对象的创建
创建对象需要以下三个步骤:
1. 声明对象
声明对象的一般格式如下:
类名 对象名;
例如:
Citizen p1,p2; //声明了两个公民对象
Float f1,f2; //声明了两个浮点数对象
声明对象后,系统还没有为对象分配存储空间,只是建立了空的引用,通常称之为空对象(null)。因此对象还不能使用。
2. 创建对象
对象只有在创建后才能使用,创建对象的一般格式如下:
对象名 = new 类构造方法名([实参表]);
其中:类构造方法名就是类名。new运算符用于为对象分配存储空间,它调用构造方法,获得对象的引用(对象在内存中的地址)。
例如:
p1=new Citizen("丽柔","温一刀","女",new Date(),"中国上海","410105651230274x");
f1=new Float(30f);
f2=new Float(45f);
注意:声明对象和创建对象也可以合并为一条语名,其一般格式是:
类名 对象名 = new 类构造方法名([实参表]);
例如:
Citizen p1=new Citizen("丽柔","温一刀","女",new Date(),"中国上海","410105651230274x");
Float f1=new Float(30f);
Float f2=new Float(45f);
3. 引用对象
在创建对象之后,就可以引用对象了。引用对象的成员变量或成员方法需要对象运算符“.”。
引用成员变量的一般格式是: 对象名.成员变量名
引用成员方法的一般格式是: 对象名.成员方法名([实参列表])
在创建对象时,某些属性没有给于确定的值,随后可以修改这些属性值。例如:
Citizen p2=new Citizen("李明","","男",null,"南京","50110119850624273x");
对象p2的别名和出生年月都给了空值,我们可以下边的语句修正它们:
p2.alias="飞翔鸟";
p2.brithday=new Date("6/24/85");
名字中出现别字,我们也可以调用方法更正名字:
p2.setName("李鸣");
4.3.2 对象的简单应用示例
本小节介绍两个简单的示例,以加深理解前边介绍的一些基本概念,对Java程序有一个较为全面的基本认识。
例4.2 编写一个测试Citizen类功能的程序,创建Citizen对象并显示对象的属性值。
/* Citizen测试程序
* 程序的名字是:TestCitizenExam4_2.java
*/
import java.util.*;
public class TestCitizenExam4_2
{
public static void main(String [] args)
{
Citizen p1,p2; //声明对象
//创建对象p1,p2
p1=new Citizen("丽柔","温一刀","女",new Date("12/30/88"),"上海","421010198812302740");
p2=new Citizen("李明"," ","男",null,"南京","50110119850624273x");
p2.setName("李鸣"); //调用方法更正对象的名字
p2.alias="飞翔鸟"; //修改对象的别名
p2.brithday=new Date("6/24/85"); //修改对象的出生日期
p1.displayAll(); //显示对象p1的属性值
System.out.println("----------------------------");
p2.displayAll(); //显示对象p2的属性值
}
}
如前所述,一个应用程序的执行入口是main()方法,上边的测试类程序中只有主方法,没有其他的成员变量和成员方法,所有的操作都在main()方法中完成。
编译、运行程序,程序运行结果如图4-1所示。
需要说明的是,程序中使用了JDK1.1的一个过时的构造方法Date(日期字符串),所以在编译的时候,系统会输出提示信息提醒你注意。一般不提倡使用过时的方法,类似的功能已由相关类的其他方法所替代。在这里使用它,主要是为了程序简单阅读容易。
请读者认真阅读程序,结合前边介绍的内容,逐步认识面向对象程序设计的基本方法。
在程序中,从声明对象、创建对象、修改对象属性到执行对象方法等等,我们一切度是围绕对象在操作。
例4.3 定义一个几何图形圆类,计算圆的周长和面积。
/* 这是一个定义圆类的程序
* 程序的名字是CircleExam4_3.prg
* 该类定义了计算面积和周长的方法。
*/
public class CircleExam4_3
{
final double PI=3.1415926; //常量定义
double radius=0.0 ; //变量定义
//构造方法定义
public CircleExam4_3(double radius)
{
this.radius=radius;
}
//成员方法计算周长
public double circleGirth()
{
return radius*PI*2.0;
}
//成员方法计算面积
public double circleSurface()
{
return radius*radius*PI;
}
//主方法
public static void main(String [] args)
{
CircleExam4_3 c1,c2;
c1=new CircleExam4_3(5.5);
c2=new CircleExam4_3(17.2);
System.out.println("半径为5.5圆的周长="+c1.circleGirth()+" 面积="+c1.circleSurface());
System.out.println("半径为17.2圆的周长="+c2.circleGirth()+" 面积="+c2.circleSurface());
}
}
编译、运行程序,执行结果如图4-2所示。
4.3.3 对象的清除
在Java中,程序员不需要考虑跟踪每个生成的对象,系统采用了自动垃圾收集的内存管理方式。运行时系统通过垃圾收集器周期性地清除无用对象并释放它们所占的内存空间。
垃圾收集器作为系统的一个线程运行,当内存不够用时或当程序中调用了System.gc()方法要求垃圾收集时,垃圾收集器便于系统同步运行开始工作。在系统空闲时,垃圾收集器和系统异步工作。
事实上,在类中都提供了一个撤销对象的方法finalize(),但并不提倡使用该方法。若在程序中确实希望清除某对象并释放它所占的存储空间时,只须将空引用(null)赋给它即可。
4.4 方法的进一步讨论
我们已经介绍了方法的声明及方法的引用。本节主要讨论方法引用中的参数传递、方法的重载以及static(静态)方法等概念。
4.4.1方法引用及参数传递
在Java中,方法引用有两种方式:系统自动引用和程序引用。系统自动引用一般用在一些特定的处理中,我们将在后边的章节遇到它。本小节主要介绍程序引用方法及参数传递问题。
1. 方法声明中的形式参数
在方法声明中的“()”中说明的变量被称之为形式参数(形参),形参也相当于本方法中的局部变量,和一般局部变量所不同的是,它自动接受方法引用传递过来的值(相当于赋值)。然后在方法的执行中起作用。例如,在Citizen类中的方法:
public void setName(String name)
{
this.name=name;
}
当对象引用该方法时,该方法的形参name接受对象引用传递过来的名字,然后它被赋给对象的属性name。
2. 方法引用中的实际参数
一般我们把方法引用中的参数称为实际参数(实参),实参可以是常量,变量、对象或表达式。例如:
Citizen p2=new Citizen("李明"," ","男",null,"南京","50110119850624273x");
p2.setName("李鸣");
方法引用的过程其实就是将实参的数据传递给方法的形参,以这些数据为基础,执行方法体完成其功能。
由于实参与形参按对应关系一一传递数据,因此在实参和形参的结合上必须保持“三一致”的原则,即:
1) 实参与形参的个数一致;
2) 实参与形参对应的数据类型一致;
3) 实参与形参对应顺序一致。
3. 参数传递方式
参数传递的方式有两种:按值传递和按引用传递。
1)按值传递方式
一般情况下,如果引用语句中的实参是常量、简单数据类型的变量或可计值的基本数据类型的表达式,那么被引用的方法声明的形参一定是基本数据类型的。反之亦然。这种方式就是按值传递的方式。
2)按引用传递方式
当引用语句中的实参是对象或数组时,那么被引用的方法声明的形参也一定是对象或数组。反之亦然。这种方式称之为是按引用传递的。
下边举例说明参数的传递。
例4.4 传递方法参数示例。
/* 这是一个简单的说明方法参数使用的示例程序
* 程序的名字:ParametersExam4_4.java
*/
public class ParametersExam4_4
{
/**下边定义方法swap(int n1,int n2) 该方法从调用者传递的实际参数值获得n1,n2
* 的值,这是一种传值方式。方法的功能是交换 n1,n2的值。
*/
public void swap(int n1,int n2) //定义成员方法带两个整型参数
{
int n0; //定义方法变量n0,
n0=n1; //先将n1的值赋给n0
n1=n2; //再将n2的值赋给n1
n2=n0; //最后将n0 (原n1) 的值赋给n2
System.out.println("在swap()方法中:n1="+n1+" n2="+n2+"\n-------------");
}
public static void main(String [] arg) //以下定义main()方法
{
int n1=1,n2=10; //定义方法变量
ParametersExam4_4 par=new ParametersExam4_4();//创建本类对象
par.swap(n1,n2); //以方法变量n1,n2的值为实参调用方法swap
System.out.println("在main()方法中:n1="+n1+" n2="+n2);
}
}
编译、运行程序,执行结果如图4-3所示。
看到程序的执行结果,读者可能会有疑问,不是在swap方法中交换了n1,n2的值么,为什么在main()中仍然是原来的值呢?
程序的执行过程是这样的:当在main()方法中执行对象的swap()方法时,系统将方法调用中的实参n1,n2的值传递给swap()方法的形式参数n1,n2,在swap()方法体的执行中形式参数n1,n2的值被交换,这就是我们看到的swap()方法中的输出结果,由于形参n1,n2是swap()方法的局部变量,它们只在该方法中有效,它们随方法的结束而消失,因此在swap()方法中并没有涉及对实参的改变,所以在main()方法中,n1,n2还是原来的值。
例4.5 方法参数传递引用方式示例。
/* 这是一个简单的说明方法参数使用的示例程序
* 程序的名字:ParametersExam4_5.java
*/
import java.util.*;
public class ParametersExam4_5
{
/**下边定义方法swap()
* 对象n从调用者传递的实际参数获得引用。
* 该方法的功能是交换对象中成员变量n1,n2的值。
*/
public void swap(Citizen p1,Citizen p2)
{
Citizen p; //定义方法变量p
p=p1; p1=p2; p2=p; // 交换p1,p2对象的引用
p2.alias="发烧游二"; //修改p2的别名
p1.alias="网中逑大"; //修改p1的别名
System.out.println(p1.name+" "+p1.alias+" "+p1.sex);//显示相关属性
System.out.println(p2.name+" "+p2.alias+" "+p2.sex);//显示相关属性
System.out.println("-----------------------");
}
//以下定义main()方法
public static void main(String [] arg)
{
ParametersExam4_5 par=new ParametersExam4_5();//创建本类对象
//下边创建两个Citizen对象
Citizen p1=new Citizen("钱二","","男",new Date("12/23/88"),"杭州"," ");
Citizen p2=new Citizen("赵大","","男",new Date("8/31/85"),"北京"," ");
par.swap(p1,p2); //以对象p1,p2实参调用方法swap
p1.displayAll(); //输出p1对象的属性值
System.out.println("--------------------------");
p2.displayAll(); //输出p2对象的属性值
}
}
编译、运行程序,程序的执行结果如图4-4所示。
程序的执行过程是这样的:当在main()方法中执行对象的swap()方法时,系统将方法调用中的实参p1,p2对象的引用传递给swap()方法的形式参数p1,p2对象,在swap()方法体的执行中形式参数p1,p2对象引用值(即地址)被交换,随后修改了对象的属性值,这就是我们看到的swap()方法中的输出结果,同样由于形参p1,p2是swap()方法的局部变量,它们只在该方法中有效,它们随方法的结束而消失。但需要注意的是,swap()方法中修改的对象属性值并没有消失,这些修改是在原对象的地址上修改的,方法结束,只是传递过来的原对象引用的副本消失,原对象依然存在。因此我们就看到了main()中的显示结果。
4.4.2方法的重载
所谓重载(Overloading)就是指在一个类中定义了多个相同名字的方法,每个方法具有一组唯一的形式参数和不同的代码,实现不同的功能。
方法的名字一样,在对象引用时,系统如何确定引用的是哪一个方法呢?
在Java中,方法的名称、类型和形式参数等构成了方法的签名,系统根据方法的签名确定引用的是那个方法,因此方法的签名必须唯一。所以我们在编写重载方法时,应该注意以下两点:
1) 方法的返回值类型对方法的签名没有影响,即返回值类型不能用于区分方法,因为方法可以没有返回值。
2) 重载方法之间是以所带参数的个数及相应参数的数据类型来区分的。
下边我们举例说明方法重载的应用。例如,在Citizen类中添加如下的构造方法:
public Citizen()
{
name="无名";
alias="匿名";
sex=" ";
brithday=new Date();
homeland=" ";
ID=" ";
}
然后再添加如下3个显示对象属性的成员方法:
/*****显示3个字符串属性值的方法****/
public void display(String str1,String str2,String str3)
{
System.out.println(str1+" "+str2+" "+str3);
}
/*****显示2个字符串属性值和一个日期型属性的方法****/
public void display(String str1,String str2,Date d1)
{
System.out.println(str1+" "+str2+" "+d1.toString());
}
/*****显示3个字符串属性值和一个日期型属性的方法****/
public void display(String str1,String str2,Date d1,String str3)
{
System.out.println(str1+" "+str2+" "+d1.toString()+" "+str3);
}
在Citizen类中添加上述的方法之后,我们给出一个测试重载方法的例子:
例4.6 使用Citizen类创建对象,显示对象的不同属性值。
/*这是一个简单的测试程序
* 程序的名字是:TestCitizenExam4_6.java
* 程序的目的是演示重载方法的使用。
*/
public class TestCitizenExam4_6
{
public static void main(String [] args)
{
Citizen p1,p2; //声明对象
p1=new Citizen(); //创建对象
p2=new Citizen("钱二","发烧游二","男",new Date("12/23/88"),"杭州","暂无");
System.out.println("-----对象p1-----");
p1.display(p1.name,p1.alias,p1.sex); //显示姓名、别名、性别
p1.display(p1.name,p1.alias,p1.brithday); //显示姓名,别名,生日
p1.display(p1.name,p1.sex,p1.brithday,p1.ID);//显示姓名,性别,生日及出生地
System.out.println("-----对象p2-----");
p2.display(p2.name,p2.alias,p2.sex); //显示姓名、别名、性别
p2.display(p2.name,p2.sex,p2.brithday,p2.homeland);//显示姓名,性别,生日及出生地
p2.display(p2.name,p2.ID,p2.brithday,p2.sex);//显示姓名,身份标识,生日,性别
}
}
编译、运行程序。程序执行结果如图4-5所示。
请读者认真阅读程序,理解重载方法的应用。
由方法的重载实现了静态多态性(编译时多态)的功能。在很多情况下使用重载非常方便,比如在数学方法中求绝对值问题,按照Java中的基本数据类型byte、short、int、long、float、double,在定义方法时,如果使用六个不同名称来区分它们,在使用中不但难以记忆还比较麻烦。若使用一个名字abs()且以不同类型的参数区分它们,这样做既简洁又方便。
4.4.3 静态(static)方法
所谓静态方法,就是以“static”修饰符说明的方法。在不创建对象的前提下,可以直接引用静态方法,其引用的一般格式为:
类名.静态方法名( [ 实参表 ] )
一般我们把静态方法称之为类方法,而把非静态方法称之为类的实例方法(即只能被对象引用)。
1. 使用方法注意事项
在使用类方法和实例方法时,应该注意以下几点:
1)当类被加载到内存之后,类方法就获得了相应的入口地址;该地址在类中是共享的,不仅可以直接通过类名引用它,也可以通过创建类的对象引用它。只有在创建类的对象之后,实例方法才会获得入口地址,它只能被对象所引用。
2)无论是类方法或实例方法,当被引用时,方法中的局部变量才被分配内存空间,方法执行完毕,局部变量立该释放所占内存。
3)在类方法里只能引用类中其他静态的成员(静态变量和静态方法),而不能直接访问类中的非静态成员。这是因为,对于非静态的变量和方法,需要创建类的对象后才能使用;而类方法在使用前不需要创建任何对象。在非静态的实例方法中,所有的成员均可以使用。
4)不能使用this和super关键字(super关键字在后面章节中介绍)的任何形式引用类方法。这是因为this是针对对象而言的,类方法在使用前不需创建任何对象,当类方法被调用时,this所引用的对象根本没有产生。
下边我们先看一个示例.
例4.7 在类中有两个整型变量成员,分别在静态和非静态方法display()中显示它们。若如下的程序代码由错,错在哪里,应如何改正。
public class Example4_7
{
int var1, var2;
public Example4_7()
{
var1=30;
ver2=85;
}
public static void display()
{
System.out.println(“var1=”+var1);
System.out.println(“var2=”+var2);
}
public void display(int var1,int var2)
{
System.out.println(“var1=”+var1);
System.out.println(“var2=”+var2);
}
public static void main(String [] args)
{
Example4_7 v1=new Example4_7();
v1.display(v1.var1,v1.ver2);
display();
}
}
编译程序,编译系统将显示如下的错误信息:
Example4_7.java:11: non-static variable var1 cannot be referenced from a static context
System.out.println("var1="+var1);
^
Example4_7.java:12: non-static variable var2 cannot be referenced from a static context
System.out.println("var2="+var2);
^
2 errors
我们可以看出两个错误均来自静态方法display(),错误的原因是在静态方法体内引用了外部非静态变量,这是不允许的。此外,也应该看到在非静态方法display()中设置了两个参数用于接收对象的两个属性值,这显然是多余的,因为非静态方法由对象引用,可以在方法体内直接引用对象的属性。
解决上述问题的方法是:
1) 将带参数的display()方法修改为静态方法,即添加修饰符static;
2) 将不带参数的display()方法修改为非静态方法,即去掉修饰符static;
3) 修改main()中对display()方法的引用方式,即静态的方法直接引用,非静态的方法由对象引用。
2. main() 方法
我们在前边的程序中已经看到了main()方法的应用。main()方法就是一个静态的方法,main()方法也是一个特殊的方法,在Java应用程序中可以有许多类,每个类也可以有许多方法。但解释器在装入程序后首先运行的是main()方法。
main()方法和其他的成员方法在定义上没有区别,其格式如下:
public static void main(String [] args)
{
//方法体定义
………
}
其中:
1) 声明为public以便解释器能够访问它。
2) 修饰为static是为了不必创建类的实例而直接调用它。
3) void 表明它无返回值。
4) 它带有一个字符串数组参数(有关数组的内容,将在后边的章节介绍),它可以接收引用者向应用程序传递相关的信息,应用程序可以通过这些参数信息来确定相应的操作,就象其他方法中的参数那样。
main()方法不能象其他的类方法那样被明确地引用用,那么如何向main()方法传递参数呢?我们只能从装入运行程序的命令行传递参数给它。其一般格式如下:
java 程序名 实参列表
其中:
1)java是解释器,它将装入程序并运行之。
2)程序名是经javac 编译生成的类文件名,也就是应用程序的文件名。
3)当实参列表包含多个参数时,参数之间以空格分隔,每个参数都是一个字符串。需要注意的是,如果某个实参字符串中间包含空格时,应以定界符双引号(“ ”)将它们引起来。
尽管数组部分的内容还没有介绍,但还是要举一个简单的例子说明main()方法参数的传递及操作。
例4.8 从命令行传递”This is a simple Java program. ”,”ok!”两个字符串并显示。程序参考代码如下:
/**这是一个简单的演示程序,程序名为 CommandExam4_8.java
*其目的是演示接收从命令行传递的参数并显示。
*/
class CommandExam4_8
{
public static void main(String [ ] args)
{
System.out.println(args[0]); //显示第一个字符串
System.out.println(args[1]); //显示第二个字符串
}
}
在命令提示符下编译、运行程序。操作步骤及执行结果如图4-6所示。
4.4.4 最终(Final)方法
在Java中,子类可以从从父类继承成员方法和成员变量,并且可以把继承来的某个方法重新改写并定义新功能。但如果父类的某些方法不希望再被子类重写,必须把它们说明为最终方法,final修饰即可。
所谓最终方法,是指不能被子类重写(覆盖)的方法。定义final方法的目的主要是用来防止子类对父类方法的改写以确保程序的安全性。
一般来说,对类中一些完成特殊功能的方法,只希望子类继承使用而不希望修改,可定义为最终方法。定义最终方法的一般格式如下:
[访问限定符] final 数据类型 最终方法名([参数列表])
{
//方法体代码
………
}
我们将会在后边的章节中看到最终方法的例子。
4.5 变量的进一步讨论
在前边的例子中,我们已经看到了变量的应用。和方法类似,我们可以把变量分为静态(static)变量、最终变量(final)和一般变量。一般把静态变量称为类变量,而把非静态变量称为实例变量。
1.实例变量和类变量
下边我们通过例子来讨论类变量和实例变量之间的区别。
例4.9 编写一个学生入学成绩的登记程序,设定录取分数的下限及上限(满分),如果超过上限或低于下限,就需要对成绩进行审核。程序参考代码如下:
/* 这是一个学生入学成绩登记的简单程序
* 程序的名字是:ResultRegister.java
*/
import javax.swing.*;
public class ResultRegister
{
final int MAX=700; //分数上限
final int MIN=596; //分数下限
String student_No; //学生编号
int result; //入学成绩
public ResultRegister(String no, int res) //构造方法
{
String str;
student_No=no;
if(res>MAX || res { str= result=Integer.parseInt(str); } else result=res; } //构造方法结束 public void display() //显示对象属性方法 { System.out.println(this.student_No+" "+this.result); } //显示对象属性方法结束 public static void main(String [] args) { ResultRegister s1,s2; //声明对象s1,s2 s1=new ResultRegister("201",724); //创建对象s1 s2=new ResultRegister("202",657); //创建对象s2 s1.display(); //显示对象s1的属性 s2.display(); //显示对象s2的属性 System.exit(0); //结束程序运行,返回到开发环境 } } 在程序中,我们使用了字符串的函数String.valueOf(res),将整数值转换为字符串,有关字符串的细节将在后边的章节介绍。 在程序执行时,由于定义的都是实例变量,所以对创建的每个对象,它们都有各自独立的存储空间。现在看一下它们的存放情况,如图4-7所示。 从图中可以看出,每个对象都存储了MAX和MIN两个量,由于这两个量是常量,每个对象都重复存储它们,这就浪费了存储空间。尽管两个整数占据8个字节,但如果有数以千计个对象,这样的浪费也是惊人的。 解决这样问题的最简单方案是使用静态属性。我们只需将程序中定义MAX、MIN的两个语句: final int MAX=700; //分数上限 final int MIN=596; //分数下限 修改为: public static final int MAX=700; //分数上限 public static final int MIN=596; //分数下限 即可。 通过上边的介绍,我们可以对变量说明若下: 1)以修饰符static说明的变量被称之为静态变量,其他为非静态变量; 2)以修饰符final说明的变量称之为最终变量即常量。它常常和static修饰符一起使用,表示静态的最终变量即静态常量。 3)静态变量在类被装入后即分配了存储空间,它是类成员,不属于一个具体的对象,而是所有对象所共享的。和静态方法类似,它可以由类直接引用也可由对象引用。由类直接引用的格式是:类名.成员名 4)非静态变量在对象被创建时分配存储空间,它是实例成员,属于本对象。只能由本对象引用。 2. 变量的初始化器(Initializer) 变量的初始化在前边我们已经作过简单介绍。这里介绍的初始化器是用大括号“{}”括起的一段程序代码为变量赋初值。初始化器可分为静态的(初始化静态变量)和非静态的(初始化非静态变量)两种。下边我们只介绍静态的初始化器,因为初始化非静态变量没有实际意义,非静态变量的初始化一般在构造方法中完成。静态的初始化器的一般格式如下: static { ……… //为静态变量赋值的语句及其他相关语句 } 下边我们举一个简单的例子说明初始化器的应用。 例4.10 在程序中定义两个静态变量,使用初始化器初始化其值。程序代码如下: /*这是一个测试静态初始化器的程序 *程序的名字是:StaticExam4-10.java */ public class StaticExam4_10 { static int var1; static int var2; public static void display() //显示属性值方法 { System.out.println("var1="+var1); System.out.println("var2="+var2); } //显示方法结束 static //初始化静态变量 { System.out.println("现在对变量进行初始化..."); var1=128; //为变量var1赋初值 var2=var1*8; //为变量var2赋初值 System.out.println("变量的初始化完成!!!"); } //初始化器结束 public static void main(String [] args) //主方法 { display(); //显示属性值 } //主方法结束 } 请读者运行程序,查看程序的执行结果。当系统把类装入到内存时,自动完成静态初始化器。 本章主要讲述了面向对象的程序设计的基本概念,面向对象的程序设计是以类为基础的。一个类包含两种成份,一种是数据成份(变量),一种是行为成份(方法)。根据这两种成份,在定义类时,数据成份被声明为类的成员变量,行为成份被声明为类的成员方法。 本章详细讨论了类的构成。类由类的声明部分和类体组成。类体包含以下内容: 1)成员变量 成员变量可分为静态的和非静态的。静态的成员变量也被称为类变量,它属于类可以被类的所有对象共享;非静态的成员变量也被称为实例变量,它只属于具体的对象。 2)成员方法 方法可分为构造方法和一般方法: 构造方法是一种特殊的方法,它用于构造对象。 一般方法又可分为静态方法和非静态方法。静态方法也被称为类方法,可以在不创建对象的情况下,直接使用类名引用;非静态方法也被称为实例方法,只能由对象引用。 此外还讨论了方法的重载。 本章重点:面向对象程序设计的概念,类的定义方法、各种数据成员和方法成员的概念及定义,对象的定义、创建及引用,方法的重载,方法参数的传递等。 本章是面向对象程序设计基础,必须切实掌握,才能更好地学习后边的内容。 1.举例说明类和对象的关系。 2.定义一个描述电话的类,至少描述电话类的两种属性和一种功能。 3.为什么说构造方法是一种特殊的方法,它与一般的成员方法有什么不同?为第2题的电话类定义构造方法,创建一个具体的电话对象并对其成员进行引用。 4.什么是方法的重载?编写一个类,定义3个重载的方法,并编写该类的测试程序。 5.举例说明类方法和实例方法以及类变量和实例变量的区别。 6.子类将继承父类的哪些成员变量和方法?子类在什么情况下隐藏父类的成员变量和方法?在子类中是否允许有一个方法和父类的方法名字和参数相同,而类型不同?说明理由。 4.7 上机实验一 类的定义与对象创建 实验目的: 本实验的目的是让学生掌握类的定义、变量声明、方法声明、对象的创建过程。 实验内容: 1.编写一个Java应用程序,描写一个矩形类,并输出某个矩形的长、宽、周长和面积。具体要求如下: (1) 定义Rectangle类,声明两个成员变量分别描述矩形的长和宽。 (2)在Rectangle中声明两个方法分别计算矩形的周长和面积。 (3) 编写应用程序类,创建一个具体的矩形对象,在屏幕上打印输出该矩形的长、宽、周长和面积。 2.按以下要求创建一个学生类(Student),并完成相应的操作: (1)其成员变量:姓名(name)、年龄(age)、身高(height)、体重(weight) (2)成员方法1:setAge—用于给变量age赋值 (3)成员方法2:out按一定格式输出各成员变量的值。 (4)构造方法:通过参数传递,分别对name、height、weight初始化。 (5)最后,创建这个类的对象,并完成对成员变量赋值和输出的操作。 实验要求: 1.以书面形式写出程序代码。 2.将代码输入计算机进行调试。 3.记录调试过程中的问题及解决方法。 4.总结实验过程中发现的问题及解决办法,写出实验报告。 4.8 上机实验二 方法的重载与static关键字 实验目的: 本实验的目的是让学生掌握方法重载的概念、运用方法重载编程的技巧以及类成员与实例成员的区别。 实验内容: 1、补充程序,验证方法的重载。 下面已给出Area类的定义,定义应用程序类AreaTest,创建Area类的对象并调用每一个成员方法,观察不同的参数与调用方法的之间的关系。 Area类程序清单: class Area { float getArea(float r) { System.out.print("方法一:"); return 3.14f*r*r; } double getArea(float x,int y) { System.out.print("方法二:"); return x*y; } float getArea(int x,float y) { System.out.print("方法三:"); return x*y; } double getArea(float x,float y,float z) { System.out.print("方法四:"); return (x+x+y*y+z*z)*2.0; } } 2、按程序模板(Test.java)要求编写源文件,将[代码x]按其后的要求替换为java程序代码并分析程序输出结果。 class A { [代码1]//声明一个float型的实例变量a [代码2]//声明一个float型的类变量b void setA(float a) { [代码3]//将参数a赋值给成员变量a } void setB(float b) { [代码4]//将参数b赋值给成员变量b } float getA() { return a; } static float getB() { return b; } void outA() { System.out.println (a); } [代码5]//定义方法outB(),输出变量b } public class Test { [代码6]//通过类名引用变量b,给b赋值为100 [代码7]//通过类名调用方法outB() A cat=new A(); A dog=new A(); [代码8]//通过cat调用方法setA(),将cat的成员变量a设置为200 [代码9]//通过cat调用方法setB(),将cat的成员变量b设置为300 [代码10]//通过dog调用方法setA(),将dog的成员变量a设置为400 [代码11]//通过dog调用方法setB(),将dog的成员变量b设置为500 [代码12]//通过cat调用outA() [代码13]//通过cat调用outB() [代码14]//通过dog调用outA() [代码15]//通过dog调用outB() } 实验要求: 1.补充程序。 2.调试程序。 3.总结实验中遇到的问题并写出实验报告。 上一章我们介绍了面向对象程序设计的基本概念,如类的定义、对象的创建(实例化)、类的成员等。本章将继续介绍类的继承性、类的访问限定、抽象类、匿名类以及包和接口等概念。 面向对象的重要特点之一就是继承。类的继承使得能够在已有的类的基础上构造新的类,新类除了具有被继承类的属性和方法外,还可以根据需要添加新的属性和方法。继承有利于代码的复用,通过继承可以更有效地组织程序结构,并充分利用已有的类来完成复杂的任务,减少了代码冗余和出错的几率。 5.1.1 类继承的实现 1. 问题的提出 在介绍类继承的实现之前,我们先看一下上一章介绍的Citizen(公民)类和ResultRegister(成绩登记)类,分析一下它们之间的关系。Citizen类的完整代码如下: /**这是一个公民类的定义 * 类名:Citizen */ import java.util.*; public class Citizen { //以下声明成员变量(属性) String name; String alias; String sex; Date brithday; //这是一个日期类的成员变量 String homeland; String ID; //以下定义成员方法(行为) public String getName() //获取名字方法 { //getName()方法体开始 return name; } //getName()方法体结束 /***下边是设置名字方法***/ public void setName(String name) { //setName()方法体开始 this.name=name; } //setName()方法体结束 /***下边是列出所有属性方法***/ public void displayAll() { //displayAll()方法体开始 System.out.println("姓名:"+name); System.out.println("别名:"+alias); System.out.println("性别:"+sex); if(brithday==null) brithday=new Date(0); System.out.println("出生:"+brithday.toString()); System.out.println("出生地:"+homeland); System.out.println("身份标识:"+ID); } displayAll()方法体结束 public void display(String str1,String str2,String str3) //重载方法1 { System.out.println(str1+" "+str2+" "+str3); } public void display(String str1,String str2,Date d1) //重载方法2 { System.out.println(str1+" "+str2+" "+d1.toString()); } public void display(String str1,String str2,Date d1,String str3)//…3 { System.out.println(str1+" "+str2+" "+d1.toString()+" "+str3); } public Citizen(String name,String alias,String sex,Date brithday,String homeland,String ID) //带参数构造方法 { this.name=name; this.alias=alias; this.sex=sex; this.brithday=brithday; this.homeland=homeland; this.ID=ID; } public Citizen() //无参构造方法 { name="无名"; alias="匿名"; sex=" "; brithday=new Date(); homeland=" "; ID=" "; } } ResultRegister类的代码如下: /* * 这是一个学生入学成绩登记的简单程序 * 程序的名字是:ResultRegister.java */ import javax.swing.*; public class ResultRegister { public static final int MAX=700; //分数上限 public static final int MIN=596; //分数下限 String student_No; //学号 int result; //入学成绩 public ResultRegister(String no, int res) //构造方法 { String str; student_No=no; if(res>MAX || res { str= result=Integer.parseInt(str); } else result=res; } //构造方法结束 public void display() //显示对象属性方法 { System.out.println(this.student_No+" "+this.result); } //显示对象属性方法结束 } 通过上一章对上述两类的介绍和示例演示,我们可以分析一下,在Citizen类中,定义了每个公民所具有的最基本的属性,而在ResultRegister类中,只定义了与学生入学成绩相关的属性,并没有定义诸如姓名、性别、年龄等这些基本属性。在登录成绩时,我们只需要知道学生号码和成绩就可以了,因为学生号码对每一个学生来说是唯一的。但在有些时候,诸如公布成绩、推荐选举学生干部、选拔学生参加某些活动等,就需要了解学生更多的信息。 如果学校有些部门需要学生的详细情况,既涉及到Citizen类中的所有属性又包含ResultRegister中的属性,那么我们是定义一个包括所有属性的新类还是修改原有类进行处理呢? 针对这种情况,如果建立新类,相当于从头再来,那么就和前边建立的Citizen和ResultRegister类没有什么关系了。这样做有违于面向对象程序设计的基本思想,也是我们不愿意看到的,因此我们应采用修改原有类的方法,这就是下边所要介绍的类继承的实现。 2. 类继承的实现 根据上边提出的问题,要处理学生的详细信息,已建立的两个类Citizen和ResultRegister已经含有这些信息,接下来的问题是在它们之间建立一种继承关系就可以了。从类别的划分上,学生属于公民,因此Citizen应该是父类,ResultRegister应该是子类。下边修改ResultRegister类就可以了。 定义类的格式在上一章已经介绍过,不再重述。将ResultRegister类修改为Citizen类的子类的参考代码如下: /* * 这是一个学生入学成绩登记的简单程序 * 程序的名字是:ResultRegister.java */ import java.util.*; import javax.swing.*; public class ResultRegister extends Citizen { public static final int MAX=700; //分数上限 public static final int MIN=596; //分数下限 String student_No; //学号 int result; //入学成绩 public ResultRegister() { student_No="00000000000"; result=0; } public ResultRegister(String name,String alias,String sex,Date brithday,String homeland,String ID,String no, int res) //构造方法 { this.name=name; this.alias=alias; this.sex=sex; this.brithday=brithday; this.homeland=homeland; this.ID=ID; String str; student_No=no; if(res>MAX || res { str= result=Integer.parseInt(str); } else result=res; } //构造方法结束 public void display() //显示对象属性方法 { displayAll(); System.out.println("学号="+student_No+" 入学成绩="+result); } //显示对象属性方法结束 } 在上边的类定义程序中,着重显示部分是修改添加部分。可以看出,由于它继承了Citizen类,所以它就具有Citizen类所有的可继承的成员变量和成员方法。 下边我们写一个测试程序,验证修改后的ResultRegister的功能。 例5.1 测试ResultRegister类的功能。程序参考代码如下: /* 这是一个测试ResultRegister类的程序 * 程序的名字是: TestExam5_1.java */ import java.util.*; public class TestExam5_1 { public static void main(String [] args) { ResultRegister s1,s2,s3; //声明对象s1,s2,s3 s1=new ResultRegister("丽柔","一刀","女",new Date("12/30/88"),"上海","421010198812302740","200608010201",724); //创建对象s1 s2=new ResultRegister("李明"," ","男",null,"南京","50110119850624273x","200608010202",657); //创建对象s2 s3=new ResultRegister(); s3.display(); //显示对象s1的属性 System.out.println("============================"); s2.display(); //显示对象s2的属性 System.out.println("============================"); s1.display(); //显示对象s3的属性 System.exit(0); //结束程序运行,返回到开发环境 } } 编译、运行程序,在程序执行过程中,由于生成对象s1时传递的成绩724超出了上限700,所以就出现了如图5-1的超限处理对话框,修正成绩后,按“确定”按钮确认,之后输出如图5-2的执行结果。 至此,我们已经完成了类继承的实现。但是,还有一些问题没有解决。如上边所述,不同的管理部门可能需要了解学生的不同信息。在上一章中,我们介绍了方法的重载,引用不同的重载方法来显示不同的属性信息。那是在父类中实现的。现在,我们要在子类中显示学生的不同的属性信息。解决这一问题,仍然可以使用重载方法的方式,不过要采用重载方法和覆盖方法并举的方式。下边将简要介绍覆盖方法的基本概念和覆盖方法的实现与应用。 5.1.2 覆盖(Override)方法 所谓方法的覆盖(,就是指在子类中重写了与父类中有相同签名的方法。这样做的好处是方法名一致易记易用,可以实现与父类方法不同的功能。 下边我们将ResultRegister中的display()更名为displayAll()并添加重写父类Citizen中的display()方法。重写的方法代码如下: public void displayAll() //重写displayAll() { //displayAll()方法体开始 super.displayAll(); //引用父类方法 System.out.println("学号:"+student_No); System.out.println("入学成绩:"+result); } //displayAll()方法体结束 public void display(String str1,String str2,String str3) //重写方法2 { super.display(str1,str2,str3);//引用父类方法 System.out.println(this.student_No+" "+result); } public void display(String str1,String str2,Date d1) //重写方法3 { super.display(str1,str2,d1);//引用父类方法 System.out.println(this.student_No+" "+result); } public void display(String str1,String str2,Date d1,String str3)//…4 { super.display(str1,str2,d1,str3);//引用父类方法 System.out.println(this.student_No+" "+result); } 注意:在引用父类方法时,我们使用了super关键字。在前边我们看到了this关键字的使用。this代表当前对象对本类成员的引用;而super则代表当前对象对父类成员的引用。 下边举例说明覆盖方法的应用。 例5.2 编写测试程序,测试ResultRegister类的覆盖方法。程序参考代码如下: /* 这是一个测试ResultRegister类覆盖方法的程序 * 程序的名字是: TestExam5_2.java */ import java.util.*; public class TestExam5_2 { public static void main(String [] args) { ResultRegister s1; //声明对象s1 s1=new ResultRegister("丽柔","一刀","女",new Date(0),"上海","421010198812302740","200608010201",654); //创建对象s1 s1.displayAll(); //显示对象s1的所有属性 System.out.println("============================"); s1.display(s1.ID,s1.name,s1.sex);//显示s1的身份标识,名字,性别及学号和成绩 System.out.println("============================"); s1.display(s1.name,s1.sex,s1.brithday);//显示s1的名字,性别,生日及学号和成绩 System.out.println("============================"); s1.display(s1.name,s1.sex,s1.brithday,s1.homeland);//显示s1的名字,性别,生日,出生地及学号和成绩 System.exit(0); //结束程序运行,返回到开发环境 } } 请读者编译、运行程序,分析程序的执行结果。 一般来说,在Java程序的编译过程中,当遇到一个对象引用方法时,系统首先在对象所在的类中寻找这个方法的签名,如果没有找到的话,系统会把查找传递到其父类中查找,如果仍然没找到,再转到父类的父类查找,依此下去,直到找到这个方法的签名为止。如果查找到基类(最顶层)也没查到,系统将提示找不到此方法的错误信息。 5.1.3 变量的隐藏(Hidded) 所谓变量的隐藏就是指在子类中定义的变量和父类中定义的变量有相同的名字或方法中定义的变量和本类中定义的变量同名。 事实上在前边介绍的类中已经遇到了这种情况。诸如,在Citizen类构造方法的形式参数中,就定义了和本类中同名的变量。在这种情况下,系统采用了局部优先的原则。即在方法中,同名的方法变量优先,直接引用。而成员变量(对象的属性)则被隐藏,需要引用时,应加上关键字this(本类)或super(父类)加以区分。因此在Citizen类的构造函数中,我们看到了这样的引用语句: this.name=name; //将方法变量name的值赋给对象的属性name 在程序中对变量的引用时,什么情况下不需要加this、super?什么情况下需要加,加哪个?其规则如下: 1) 当不涉及同名变量的定义时,对变量的引用不需要加this或super关键字。 2) 当涉及同名变量的定义时,分两种情况: a) 方法变量和成员变量同名,在引用成员变量时,前边加this; b) 本类成员变量和父类成员变量同名,在引用父类成员变量时,前边加super。 变量的隐藏有点相似于方法的覆盖,也可以称为属性的覆盖。只不过是为了区分是指变量而不是方法,用另一个名词“隐藏”称之而已。有关其他的应用,我们在使用时再作介绍。 5.1.4 应用示例 类的继承并不仅仅是把它们作为基础来定义新的类,实现类的重复使用。它还可以通过方法覆盖实现动态的多态性操作(运行时多态)。下边举一个简单的例子说明多态性操作。 例5.3:定义一个Teacher类。 // Teacher.java public class Teacher extends Citizen { public String teacherID; //声明教师代码 public String position; //教师职位 public String courseName; //主讲课程 //构造器 public Teacher( ) { teacherID=”0000”; position=”助教”; courseName=”习题课“; } public Teacher(String personID,String name,String sex,String birthday,String teacherID,String position,String courseName) { this.personID=personID; this.name=name; this.sex=sex; this.birthday=birthday; this.teacherID=teacherID; this.position=position; this.couseName=courseName; } public void display() //成员方法display() { super.display(); //执行父类的显示方法 System.out.println(“教师代码:”+teacherID); System.out.println(“教师职位:”+position); System.out.println(“主讲课程:”+courseName); } } 完成该类的定义之后,还需要一个程序使用这些类并对它们进行测试,测试程序如下: 例5.4:对上边创建的类的覆盖方法进行多态性测试。 //polymorphismDemo.java public class polymorphismDemo { public static void main(String [ ] args) { Citizen [] citizenObj={ new Student(“65410519700221275x”,”纪杨”,”男”,”1974.5.21”,”920150208”,”1992.9.1”,540),new Teacher (“65410519670221272x”,”姜氏”,”女”,”1967.2.21”, ”1201”,”教授”,”物理学”)}; for(int i=1; i<=citizenObj.length; i++) { System.out.println(“第”+i+”次:”); citizenObj[i-1].display(); //显示对象信息 } } } 在测试程序中,生成了一个Citizen类型数组,各元素是其不同子类的对象,这样就可以使用一个Citizen类型的变量来引用子类的对象,循环调用不同类型对象的display()方法,如图5.2所示。这是由两个子类的情况,当然在多个子类时,也可以产生随机下标,从数组中随机选择对象,这是一个多态性应用的简单例子。大家可以参照例5.1编译、运行程序,思考一下程序的运行结果。 要使用多态性机制,必须具备如下条件: 1)子类对象的方法调用必须通过一个父类的变量进行。比如上例的Citizen类型数组。 2)被调用的方法必须是覆盖的方法即在父类中也有该方法。 3)子类的方法特征与父类必须一致。 4)子类方法的访问权限不能比父类更严格。 类是对现实世界中实体的抽象,但我们不能以相同的方法为现实世界中所有的实体做模型,因为大多数现实世界的类太抽象而不能独立存在。 例如,我们熟悉的平面几何图形类,对于圆、矩形、三角形、有规则的多边形及其他具体的图形可以描述它的形状并根据相应的公式计算出面积来的。那么任意的几何图形又如何描述呢?它是抽象的,我们只能说它表示一个区域,它有面积。那么面积又如何计算呢,我们不能够给出一个通用的计算面积的方法来,这也是抽象的。在现实生活中,会遇到很多的抽象类,诸如交通工具类、鸟类等等。 本节我们简要介绍抽象类的定义和实现。 5.2.1 抽象类的定义 在Java中所谓的抽象类,即是在类说明中用关键字abstract修饰的类。 一般情况下,抽象类中可以包含一个或多个只有方法声明而没有定义方法体的方法。当遇到这样一些类,类中的某个或某些方法不能提供具体的实现代码时,可将它们定义成抽象类。 定义抽象类的一般格式如下: [访问限定符] abstract class 类名 { //属性说明 ………… //抽象方法声明 ………… //非抽象方法定义 …………… } 其中,声明抽象方法的一般格式如下: [访问限定符] abstract 数据类型 方法名([参数表]); 注意:抽象方法只有声明,没有方法体,所以必须以“;”号结尾。 有关抽象方法和抽象类说明如下: 1)所谓抽象方法,是指在类中仅仅声明了类的行为,并没有真正实现行为的代码。也就是说抽象方法仅仅是为所有的派生子类定义一个统一的接口,方法具体实现的程序代码交给了各个派生子类来完成,不同的子类可以根据自身的情况以不同的程序代码实现。 2)抽象方法只能存在于抽象类中,正像刚才所言,一个类中只要有一个方法是抽象的,则这个类就是抽象的。 3)构造方法、静态(static)方法、最终(final)方法和私有(private)方法不能被声明为抽象的方法。 3)一个抽象类中可以有一个或多个抽象方法,也可以没有抽象方法。如果没有任何抽象方法,这就意味着要避免由这个类直接创建对象。 4)抽象类只能被继承(派生子类)而不能创建具体对象即不能被实例化。 下边我们举例说明抽象类的定义。 例5.5 定义如上边所述的平面几何形状Shape类。 每个具体的平面几何形状都可以获得名字且都可以计算面积,我们定义一个方法getArea()来求面积,但是在具体的形状未确定之前,面积是无法求取的,因为不同形状求取面积的数学公式不同,所以我们不可能写出通用的方法体来,只能声明为抽象方法。定义抽象类Shape的程序代码如下: /* 这是抽象的平面形状类的定义 * 程序的名字是:Shape.java */ public abstract class Shape { String name; //声明属性 public abstract double getArea(); //抽象方法声明 } 在该抽象类中声明了name属性和一个抽象方法getArea()。 下边通过派生不同形状的子类来实现抽象类Shape的功能。 5.2.2 抽象类的实现 如前所述,抽象类不能直接实例化,也就是不能用new运算符去创建对象。抽象类只能做为父类使用,而由它派生的子类必须实现其所有的抽象方法,才能创建对象。 下边我们举例说明抽象类的实现。 例5.6 派生一个三角形类Tritangle,计算三角形的面积。计算面积的数学公式是: 其中: 1) a,b,c 表示三角形的三条边; 2) 参考代码如下: /*这是定义平面几何图形三角形类的程序 * 程序的名字是:Tritangle.java */ public class Tritangle extends Shape //这是Shape的派生子类 { double sideA,sideB,sideC; //声明实例变量三角形3条边 public Tritangle() //构造器 { name="示例全等三角形"; sideA=1.0; sideB=1.0; sideC=1.0; } public Tritangle(double sideA,double sideB,double sideC)//构造器 { name="任意三角形"; this.sideA = sideA; this.sideB = sideB; this.sideC = sideC; } //覆盖抽象方法 public double getArea() { double s=0.5*(sideA+sideB+sideC); return Math.sqrt(s*(s-sideA)*(s-sideB)*(s-sideC));//使用数学开方方法 } } 下边编写一个测试Tritangle类的程序。 例5.7 给出任意三角形的3条边为5、6、7,计算该三角形的面积。程序代码如下: /* 这是一个测试Tritangle类的程序 * 程序的名字为:Exam5_7.java */ public class Exam5_7 { public static void main(String [ ] args) { Tritangle t1,t2; t1=new Tritangle(5.0,6.0,7.0); //创建对象t1 t2=new Tritangle(); //创建对象t2 System.out.println(t1.name+”的面积=”+t1.getArea()); System.out.println(t2.name+”的面积=”+t2.getArea()); } } 编译、运行程序。程序的执行结果如下: 任意三角形的面积=14.696938456699069 示例全等三角形的面积=0.4330127018922193 对于圆、矩形及其他形状类的定义与三角形类似,作为作业留给大家,不再重述。 5.3 内部类、匿名类及最终类 内部类和匿名类是特殊形式的类,它们不能形成单独的Java源文件,在编译后也不会形成单独的类文件。最终类是以final关键字修饰的类,最终类不能被继承。 5.3.1 内部类 所谓内部类(Inner Class),是指被嵌套定义在另外一个类内甚至是一个方法内的类,因此也把它称之为类中类。嵌套内部类的类称为外部类(Outer Class),内部类通常被看成是外部类的一个成员。 下边举例说明内部类的使用。 例5.7 工厂工人加工正六边形的阴井盖,先将钢板压切为圆型,然后再将其切割为正六边形,求被切割下来的废料面积。 解决这个问题。只需要计算出圆的面积和正六边形的面积,然后相减即可。当然我们可以将正六边形化作六个全等三角形求其面积。下边建立一个圆类,并在圆类内定义内部类处理正六边形,这主要是说明内部类的应用。程序参考代码如下: /* 该程序主要演示内部类的应用 * 程序的名字:Circle.java * 在Circle类中嵌套了Polygon类 */ public class Circle extends Shape //继承Shape类 { double radius; public Circle() { name="标准圆"; radius=1.0; } public Circle(double radius) { name="一般圆"; this.radius=radius; } public double getArea() //覆盖父类方法 { return radius*radius*Math.PI; // 返回圆的面积 } public double remainArea() { Polygon p1=new Polygon(radius,radius,radius); //创建内部类对象 return getArea()-p1.getArea(); } class Polygon //定义内部类 { Tritangle t1; //声明三角形类对象 Polygon(double a,double b,double c) //内部类构造方法 { t1=new Tritangle(a,b,c); //创建三角形对象 } double getArea() //内部类方法 { return t1.getArea()*6; //返回正六边形面积 } } } 上边定义的Circle类是Shape类的派生类,它重写并实现了getArea()方法的功能。类中嵌套了Polygon内部类,在内部类中使用了前边定义的Tritangle 类对象,用于计算三角形的面积(正六边形可以由六个全等三角形组成),在内部类中定义了一个返回正六边形面积的方法getArea()。在外部类Circle类中还定义了remainArea()方法,该方法返回被剪切掉的废料面积。方法中创建了内部类对象,用于获取正六边形的面积。 下边我们给出测试程序。 例5.8 创建Circle对象,测试内部类的应用,显示废料面积。 /*这是一个测试程序 *程序名称是: */ public class TestInnerClassExam5_8 { public static void main(String [] args) { Circle c1=new Circle(0.5); System.out.println(“圆的半径为0.5米,剩余面积=”+c1.remainArea()); } } 编译、运行程序,执行结果如下: 圆的半径为0.5米,剩余面积=0.13587911055911928 内部类作为一个成员,它有如下特点: 1)若使用static修饰,则为静态内部类;否则为非静态内部类。静态和非静态内部类的主要区别在于: a) 内部静态类对象和外部类对象可以相对独立。它可以直接创建对象,即使用 new 外部类名.内部类名() 格式;也可通过外部类对象创建(如Circle类中,在remainArea()方法中创建)。非静态类对象只能由外部对象创建。 b) 静态类中只能使用外部类的静态成员不能使用外部类的非静态成员;非静态类中可以使用外部类的所有成员。 c) 在静态类中可以定义静态和非静态成员;在非静态类中只能定义非静态成员。 2) 外部类不能直接存取内部类的成员。只有通过内部类才能访问内部类的成员。 3) 如果将一个内部类定义在一个方法内(本地内部类),它完全可以隐藏在方法中,甚至同一个类的其他方法也无法使用它。 5.3.2 匿名类和最终类 所谓匿名类(Anonymouse Class)是一种没有类名的内部类,通常更多的出现在事件处理的程序中。在某些程序中,往往需要定义一个功能特殊且简单的类,而只想定义该类的一个对象,并把它作为参数传递给一个方法。此种情况下只要该类是一个现有类的派生或实现一个接口,就可以使用匿名类。有关匿名类的定义与使用,我们将在后边章节的实际应用中介绍。 所谓最终类即是使用“final”关键字修饰的类。一个类被声明为最终类,这就意味着该类的功能已经齐全,不能够由此类再派生子类。在定义类时,当你不希望某类再能派生子类,可将它声明为最终类。 5.4包及访问限定 在Java中,包(package)是一种松散的类的集合,它可以将各种类文件组织在一起,就像磁盘的目录(文件夹)一样。无论是Java中提供的标准类,还是我们自己编写的类文件都应包含在一个包内。包的管理机制提供了类的多层次命名空间避免了命名冲突问题,解决了类文件的组织问题,方便了我们的使用。 5.4.1 Java中常用的标准类包 SUN公司在JDK中提供了各种实用类,通常被称之为标准的API(Application Programming Interface),这些类按功能分别被放入了不同的包中,供大家开发程序使用。随着JDK版本的不断升级,标准类包的功能也越来越强大,使用也更为方便。 下边简要介绍其中最常用几个包的功能: Java提供的标准类都放在标准的包中,。常用的一些包说明如下: 1)java.lang 包中存放了Java最基础的核心类,诸如System、Math、String、Integer、Float类等等。在程序中,这些类不需要使用import语句导入即可直接使用。例如前边程序中使用的输出语句System.out.println()、类常数Math.PI、数学开方方法Math.sqrt()、类型转换语句Float.parseFloat()等等。 2)java.awt 包中存放了构建图形化用户界面(GUI)的类。如Frame、Button、TextField等,使用它们可以构建出用户所希望的图形操作界面来。 3)javax.swing 包中提供了更加丰富的、精美的、功能强大的GUI组件,是java.awt功能的扩展,对应提供了如JFrame、JButton、JTextField等等。在前边的例子中我们就使用过JoptionPane类的静态方法进行对话框的操作。它比java.awt相关的组件更灵活、更容易使用。 4)java.applet 包中提供了支持编写、运行applet(小程序)所需要的一些类。 5)java.util 包中提供了一些实用工具类,如定义系统特性、使用与日期日历相关的方法以及分析字符串等等。 6)java.io 包中提供了数据流输入/输出操作的类。如建立磁盘文件、读写磁盘文件等等。 7)java.sql 包中提供了支持使用标准SQL方式访问数据库功能的类。 8)java.net 包中提供与网络通讯相关的类。用于编写网络实用程序。 5.4.2 包(package)的创建及包中类的引用 如上所述,每一个Java类文件都属于一个包。也许你会说,在此之前,我们创建示例程序时,并没有创建过包,程序不是也正常执行了吗? 事实上,如果在程序中没有指定包名,系统默认为是无名包。无名包中的类可以相互引用,但不能被其他包中的Java程序所引用。对于简单的程序,使用不使用包名也许没有影响,但对于一个复杂的应用程序,如果不使用包来管理的类,将会对程序的开发造成很大的混乱。 下边我们简要介绍包的创建及使用。 1. 创建包 将自己编写的类按功能放入相应的包中,以便在其他的应用程序中引用它,这是对面向对象程序设计者最基本的要求。我们可以使用package语句将编写的类放入一个指定的包中。package语句的一般格式如下: package 包名; 需要说明的是: 1)此语句必须放在整个源程序第一条语句的位置(注解行和空行除外)。 2)包名应符合标识符的命名规则,习惯上,包名使用小写字母书写。可以使用多级结构的包名,如Java提供的类包那样:java.util、java.sql等等。事实上,创建包就是在当前文件夹下创建一个以包名命名的子文件夹并存放类的字节码文件。如果使用多级结构的包名,就相当于以包名中的“.”为文件夹分隔符,在当前的文件夹下创建多级结构的子文件夹并将类的字节码文件存放在最后的文件夹下。 下边举例说明包的创建。 例如,前边我们创建了平面几何图形类Shape、Triangle和Circle。现在要将它们的类文件代码放入shape包中,我们只须在Shape.java、Triangle.java和Circle.java三个源程序文件中的开头(作为第一个语句)各自添加一条如下的语句: packabe shape; 就可以了。 在完成对程序文件的修改之后,重新编译源程序文件,生成的字节码类文件被放入创建的文件夹下。 一般情况下,我们是在开发环境界面中(比如JCreator)单击编译命令按钮或图标执行编译的。但有时候,我们希望在DOS命令提示符下进行Java程序的编译、运行等操作。下边简要介绍一下DOS环境下编译带有创建包的源程序的操作。其编译命令的一般格式如下: javac –d [文件夹名] [.]源文件名 其中: 1) -d表明带有包的创建。 2). 表示在当前文件夹下创建包。 3)文件夹名是已存在的文件夹名,要创建的包将放在该文件夹下。 例如,要把上述的3个程序文件创建的包放在当前的文件夹下,则应执行如下编译操作: javac -d .Shape.java javac -d .Triangle.java javac -d .Circle.java 如果想将包创建在d:\java文件夹下,执行如下的编译操作: javac -d d:\java Shape.java javac -d d:\java Triangle.java javac -d d:\java Circle.java 在执行上述操作之后,我们可以查看一下所生成的包shape文件夹下的字节码类文件。 事实上,常常将包中的类文件压缩在JAR(Java Archive, 用ZIP压缩方式,以.jar为扩展名)文件中,一个JAR文件往往会包含多个包,Sun J2SE所提供的标准类就是压缩在rt.jar文件中。 2. 引用类包中的类 在前边的程序中,我们已经多次引用了系统提供的包中的类,比如,使用java.util包中的Date类,创建其对象处理日期等。 一般来说,我们可以如下两种方式引用包中的类。 1)使用import语句导入类,在前边的程序中,我们已经使用过,其应用的一般格式如下: import 包名.*; //可以使用包中所有的类 或: import 包名.类名; //只装入包中类名指定的类 在程序中import语句应放在package语句之后,如果没有package语句,则import语句应放在程序开始,一个程序中可以含有多个import语句,即在一个类中,可以根据需要引用多个类包中的类。 2)在程序中直接引用类包中所需要的类。其引用的一般格式是: 包名.类名 例如,可以使用如下语句在程序中直接创建一个日期对象: java.util.Date date1 = new java.util.Date( ); 在上边我们已经将Shape、Circle、Triangle三个类的字节码类文件放在了shape包中,下边我们举例说明该包中类的引用。 例5.9 求半径为7.711圆的面积以及圆内接正六边形的面积。 程序参考代码如下: /**这是一个测试程序 **程序的名字是:TestShapeExam5_9.java **主要是测试shape包中类的引用 **/ package shape; import shape.*; public class TestShapeExam5_9 { public static void main(String [] args) { Circle c1=new Circle(7.711); //创建Circle对象 Triangle t1=new Triangle(7.711,7.711,7.711); //创建Triangle对象 System.out.println ("半径为7.711圆的面积="+c1.getArea()); System.out.println ("圆的内接正六边形面积="+6*t1.getArea()); } } 编译、运行程序,执行结果如下: 半径为7.711圆的面积=186.79759435956802 圆的内接正六边形面积=154.48036704856298 在程序中,我们创建了一个Circle和一个Triangle两个对象。其实,要计算圆的面积和内接正六边形的面积,只需创建一个Circle对象就够了,引用对象的remainArea()方法获得剩余面积,圆面积减去剩余面积就是正内接六边形的面积。这一方法作为作业留给大家去验证一下结果。 5.4.3 访问限定 在前边介绍的类、变量和方法的声明中都遇到了访问限定符,访问限定符用于限定类、成员变量和方法能被其他类访问的权限,当时我们只是简单介绍了了其功能,且只使用了public(公有的)和默认(友元的)两种形式。在有了包的概念之后,我们将几种访问限定总结如下: 1. 默认访问限定 如果省略了访问限定符,则系统默认为是friendly(友元的)限定。拥有该限定的类只能被所在包内的其他类访问。 2. public访问限定 由public限定的类为公共类。公共类可以被所有的其他类访问。使用public限定符应注意以下两点: 1)public限定符不能用于限定内部类。 2)一个Java源程序文件中可以定义多个类,但最多只能有一个被限定为公共类。如果有公共类,则程序名必须与公共类同名。 3.private(私有的)访问限定 private限定符只能用于成员变量、方法和内部类。私有的成员只能在本类中被访问,即只能在本类的方法中由本类的对象引用。 4.protected(保护的)访问限定 protected限定符也只能用于成员变量、方法和内部类。用protected声明的成员也被称为受保护的成员,它可以被其子类(包括本包的或其他包的)访问,也可以被本包内的其他类访问。 综合上述,以表5-1简要列出各访问限定的引用范围。其中“√”表示可访问,“×”表示不可访问。 表5-1访问限定的引用域 访问范围 同一个类 同一个包 不同包的子类 不同包非子类 public √ √ √ √ 缺省 √ √ × × private √ × × × protected √ √ √ × 5.5 接口 在前边,我们介绍了抽象类的基本概念,在Java中可以把接口看作是一种特殊的抽象类,它只包含常量和和抽象方法的定义,而没有变量和方法的实现,它用来表明一个类必须做什么,而不去规定它如何做。因此我们可以通过接口表明多个类需要实现的方法。由于接口中没有具体的实施细节,也就没有和存储空间的关联,所以可以将多个接口合并起来,由此来达到多重继承的目的。 5.5.1 接口的定义 与类的结构相似,接口也分为接口声明和接口体两部分。定义接口的一般格式如下: [public] interface 接口名 [extends 父接口名列表] //接口声明 { //接口体开始 //常量数据成员的声明及定义 数据类型 常量名=常数值; …………… //声明抽象方法 返回值类型 方法名([参数列表]) [throw 异常列表] ; ………………… } //接口体结束 对接口定义说明如下: 1) 接口的访问限定只有public和缺省的。 2) interface是声明接口的关键字,与class类似。 3) 接口的命名必须符合标识符的规定,并且接口名必须与文件名相同。 4) 允许接口的多重继承,通过“extends 父接口名列表”可以继承多个接口。 5) 对接口体中定义的常量,系统默认为是“static final”修饰的,不需要指定。 6) 对接口体中声明的方法,系统默认为是“abstract”的,也不需要指定;对于一些特殊用途的接口,在处理过程中会遇到某些异常,可以在声明方法时加上“throw 异常列表”,以便捕捉出现在异常列表中的异常。有关异常的概念将在后边的章节讨论。 在前边,我们简要介绍了平面几何图形类,并定义了一个抽象类Shape。并由它派生出Circle、Triangle类。下边我们将Shape定义为一个接口,由几何图形类实现该接口完成面积和周长的计算。 例5.10 定义接口类Shape。程序代码如下: /*本程序是一个定义接口类的程序 *程序的名字是:Shape.java *接口名为:Shape、接口中包含常量PI和方法getArea()、getGirth()声明 */ package shape; public interface Shape { double PI=3.141596; double getArea(); double getGirth(); } 在定义接口Shape之后,下边我们在定义的平面图形类中实现它。 5.5.2 接口的实现 所谓接口的实现,即是在实现接口的类中重写接口中给出的所有方法,书写方法体代码,完成方法所规定的功能。定义实现接口类的一般格式如下: [访问限定符] [修饰符] class 类名 [extends 父类名] implements 接口名列表 { //类体开始标志 [类的成员变量说明] //属性说明 [类的构造方法定义] [类的成员方法定义] //行为定义 /*重写接口方法*/ 接口方法定义 //实现接口方法 } //类体结束标志 下边我们距离说明接口的实现。 例5.11 定义一个梯形类来实现Shape接口。程序代码如下: /**这是一个梯形类的程序 ** 程序的名字:Trapezium.java. 它实现了Shape接口。 */ package shape; public class Trapezium implements Shape { public double upSide; public double downSide; public double height; public Trapezium() { upSide=1.0; downSide=1.0; height=1.0; } public Trapezium(double upSide,double downSide,double height) { this.upSide=upSide; this.downSide=downSide; this.height=height; } public double getArea() //接口方法的实现 { return 0.5*(upSide+downSide)*height; } public double getGirth() //接口方法的实现 { //尽管我们不计算梯形的周长,但也必须实现该方法。 return 0.0; } } 在程序中,我们实现了接口shape中的两个方法。对于其他的几何图形,可以参照该例子写出程序来。 需要提醒的是,可能实现接口的某些类不需要接口中声明的某个方法,但也必须实现它。类似这种情况,一般以空方法体(即以“{}”括起没有代码的方法体)实现它。 下边我们我们对Shape接口作一个测试。 例5.12 计算上底为0.4,下底为1.2,高为4的梯形的面积。测试代码程序如下: /*这是一个测试接口使用的例子 *程序的名字是: */ package shape; public class TestInterfaceExam5_12 { public static void main(String [] args) { Trapezium t1=new Trapezium(0.4,1.2,4.0); System.out.println("上底为0.4,下底为1.2,高为4的梯形的面积="+t1.getArea()); } } 在后边的章节将对接口的应用作进一步的介绍,这里只是先对接口有一个基本概念上的认识。 本章小结 本章主要讨论了类之间的关系,包括类的继承、抽象类、内部类、匿名类、接口以及包的基本概念和特性。通过本章的学习,应进一步理解面向对象技术和面向对象的程序设计方法。由浅至深,逐步编写出简单的java类应用程序。 本章重点: 1)类继承的基本思想和概念及其应用。 2)方法的重载和方法覆盖(重写)及两者之间的区别,应正确使用它们。 3)包的基本概念及其应用,访问限定符的限定范围及使用。 本章难点: 1) 抽象类及抽象方法的基本概念及其应用。 2) 接口的基本概念及其应用,接口与抽象类的区别。 思考与练习 1.什么是抽象方法?什么是抽象类?如何使用抽象类? 2.什么是抽象方法的实现? 3.接口中的成员有什么特点?接口的访问控制能否声明为private,为什么? 4.什么是接口的实现? 5.接口是如何实现多继承的? 6.按要求编写程序: (1)定义一个接口Calculate,其中声明一个抽象方法用于计算图形面积。 (2)定义一个三角形(Triangle)类,描述三角形的底边及高,并实现Caculate接口。 (3)定义一个圆形(Circle)类,描述圆半径,并实现Caculate接口。 (4)定义一个圆锥(Taper)类,描述圆锥的底和高(底是一个圆对象),计算圆锥的体积(公式:底面积*高/3)。 (5)定义一个应用程序测试类,对以上创建的类中各成员进行调用测试。
JOptionPane.showInputDialog("请核对成绩:",String.valueOf(res));本章小结
思考与练习
第5章 类的继承、包及接口5.1 类的继承
JOptionPane.showInputDialog("请核对成绩:",String.valueOf(res));
JOptionPane.showInputDialog("请核对成绩:",String.valueOf(res));5.2抽象类
TestInnerClassExam5_8.java
TestInterfaceExam5_12.java