java 消息、继承与多态


第5章 消息、继承与多态
5.1
消息
5.2
访问控制
5.3
多态机制
5.4
继承机制
5.5
抽象类、接口与包

5.1


5.1.1
消息的概念
在日常生活中,人与人之间要进行交流,某人可以向别人提
供服务,例如,他可以开汽车,教学生学习等;同时他也需要别
人为他提供服务,例如,他要吃饭但不可能自己去种地,要穿衣
不可能自己去织布,他必须请求别人帮助;同样,他什么时间讲
课,也必须得到他人的请求或命令。“请求”或“命令”便是人与人
进行交流的手段。

在面向对象的系统中,把“请求”或“命令”抽象成“消息”,
对象之间的联系是通过消息传递来实现的。当系统中的其他对
象请求这个对象执行某个服务时,它就响应这个请求,完成指
定的服务。通常,我们把发送消息的对象称为发送者,把接收
消息的对象称为接收者。对象间的联系,只能通过消息传递来
进行。对象也只有在收到消息时,才被激活,去完成消息要求
的功能。

消息就是向对象发出服务请求,是对数据成员和成员方法的
引用。因此,它应该含有下述信息:提供服务的对象标识——对
象名、服务标识——方法名、输入信息——实际参数、回答信
息——返回值或操作结果。消息具有三个性质:
(1) 同一对象可接收不同形式的多个消息,产生不同的响应。
(2) 相同形式的消息可以发送给不同对象,所做出的响应可以
是截然不同的。
(3) 消息的发送可以不考虑具体的接收者,对象可以响应消
息,也可以对消息不予理会,对消息的响应并不是必须的。

5.1.2
公有消息和私有消息
在面向对象系统中,消息分为两类:公有消息和私有消息。
当有一批消息同属于一个对象时,由外界对象直接发送给这个
对象的消息称为公有消息;对象自己发送给本身的消息称为私
有消息。私有消息对外是不开放的,外界不必了解它。外界对
象只能向此对象发送公有消息,而不能发送私有消息,私有消
息是由对象自身发送的。

5.1.3
特定于对象的消息
特定于对象的消息是指将所有能支持此对象可接受消息的
方法集中在一起,形成一个大消息,称为特定于对象的消息。
这些消息让对象执行这个方法而不管它可能做什么及怎么做。
特定于对象的消息可分为三种类型:
(1) 可以返回对象内部状态的消息。
(2) 可以改变对象内部状态的消息。
(3) 可以做一些特定操作,改变系统状态的消息。

【示例程序c5_1.java】
class student
{ public String name;
public char sex;
public int no;
public int age;
student(int cno,String cname, char csex, int cage)
{ name=cname;
sex=csex;
no=cno;
age=cage;
}

public void showNo( ){System.out.println("No:"+no);}
public void showName( ){System.out.println("Name:"+name );}
public void showSex( ){System.out.println("Sex:"+sex );}
public void showAge( ){System.out.println("age:"+age );}
}
class studentScore
{ private int no;
private double score;
public void sendScore(int cno,double cscore)
{ //下面两句是对象发送给自身的消息,要求给自己的数据成员赋值,
//这是一种私有消息,外界是不知道的。
no=cno;
score=cscore;
}

void printScore( ){System.out.println("No:"+no +" score:"+score);}
}
public class c5_1
{
public static void main(String[ ] args)
{ int m;
//下面两句发送new消息给类student,要求创建类student的对象st1,st2
student st1=new student(101,"zhang li",'F',18);
student st2=new student(102,"hong bing",'M',17);
//发送new消息给类studentScore,要求创建类studentScore的对象sc1,sc2
studentScore sc1=new studentScore( );
studentScore sc2=new studentScore( );

/* 向对象st1发送显示学号、名字、年龄的消息。这些消息都是公有消息,形
成了同一对象可接收不同形式的多个消息,产生不同的响应。*/
st1.showNo( ); //显示学号的消息
st1.showName( ); //显示姓名的消息
st1.showAge( ); //显示年龄的消息
st1.age=20; // 修改对象数据成员的消息,修改st1的年龄。
m=st1.age; //返回对象的数据成员的消息,将返回消息赋给变量m。
System.out.println("m="+m);
/* 向对象st2发送2个显示信息的消息,与st1相同,显示学号及名字。这
些消息都是公有消息,说明了相同形式的消息可以送给不同对象,所
做出的响应可以是截然不同的。*/

st2.showNo( );
st2.showName( );
//向对象sc1,sc2各发送一个按学号输入成绩单的消息,这些消息都是公
有消息
sc1.sendScore(101,97);
sc2.sendScore(102,84);
//向对象sc1,sc2各发送一个打印消息,这些消息都是公有消息
sc1.printScore( );
sc2.printScore( );
}
}

运行结果如下:
No:101
Name:zhang li
age:18
m=20
No:102
Name:hong bing
No:101 score:97.0
No:102 score:84.0

5.2
访



一个类总能够访问自己的数据成员和成员方法。但是,其他
类是否能访问这个类的数据成员或成员方法,是由该类的访问控
制符及该类数据成员和成员方法的访问控制符决定的。这就是
说,访问控制符是一组限定类、数据成员或成员方法是否可以被
其他类访问的修饰符。类的访问控制符只有public一个,缺省访
问控制符时具有“友好访问”的特性。数据成员和成员方法的访问
控制符有public、private、protected和缺省访问控制符等几种。见
表5.1。

表5.1
类、数据成员和成员方法的访问控制符及其作用

数据成员与方法
Public 缺省
public 所有类包中类(含当前类)
protected 包中类(含当前类),所有子类包中类(含当前类)
缺省(friendly) 包中类(含当前类) 包中类(含当前类)
private 当前类本身当前类本身

5.2.1
公共访问控制符public
Java的类是通过包的概念来组织的,简单地说,定义在同一
个程序文件中的所有类都属于同一个包。处于同一个包中的类都
是可见的,即可以不需任何说明而方便地互相访问和引用。而对
于不同包中的类,一般说来,它们相互之间是不可见的,当然也
不可能互相引用。然而,当一个类被声明为public时,只要在其
他包的程序中使用import语句引入这个public类,就可以访问和引
用这个类,创建这个类的对象,访问这个类内部可见的数据成员
和引用它的可见的方法。例如,Java类库中的许多类都是公共
类,我们在程序中就是通过import语句将其引入的。

当一个类的访问控制符为public时,表明这个类作为整体对
其他类是可见和可使用的,这个类就具有了被其他包中的类访问
的可能性。但是,处于不同包中的public类作为整体对其他类是
可见的,并不代表该类的所有数据成员和成员方法也同时对其他
类是可见的,这得由这些数据成员和成员方法的修饰符来决定。
只有当public类的数据成员和成员方法的访问控制符也被声明为
public时,这个类的所有用public修饰的数据成员和成员方法也同
时对其他类是可见的。在程序设计时,如果希望某个类能作为公
共工具供其他的类和程序使用,则应该把类本身和类内的方法都
定义成public。例如,Java类库中的标准数学函数类math和标准数
学函数方法。
需要注意的是,数据成员和成员方法的访问控制符被声明为
public时,会造成安全性和封装性下降,所以一般应尽量少用。

5.2.2
缺省访问控制符
如果一个类没有访问控制符,说明它具有缺省的访问控制特
性,这种缺省的访问控制特性称为“友好访问”。友好访问规定只
有在同一个包中的对象才能访问和引用这些类,因此,又称为
包访问性。同样道理,类内的数据成员和成员方法如果没有访
问控制符来限定,也具有“友好访问”的特性,它们也具有包访问
性,可以被同一个包中的其他类所访问和引用。

【示例程序c5_2.java】 计算矩形面积。
class classArea
{
public double lon,wid;//数据成员的修饰符为public
public double area(double x,double y)// 成员方法的修饰符为public
{ double s; //方法内的变量
lon=x;
wid=y;
s=lon*wid; //求矩形面积
return s; //返回面积值
}
}

public class c5_2
{
public static void main(String[ ] args)
{
double a=2.2,b=3.1,z;
/*在类c5_2中创建被访问类classArea的对象ss,ss可以访问类
classArea内的数据成员和成员方法。这就是说,同一包中的类是
可见的,可以互相引用*/

classArea ss=new classArea( );
z=ss.area(a,b); //对象ss引用类classArea内的成员方法。
System.out.println("z="+z);
}
}
运行结果:
z=6.8200000000000001

程序c5_2java中定义了两个类classArea和c5_2。由于它们是
同一个程序文件c5_2.java中的类,所以属于同一个包,它们之间
是可见的,所以可以互相引用。如果将这个例子中的类classArea
的数据成员和成员方法改写为缺省访问控制符,并不影响程序
的正确运行和所得结果,原因是它们属于同一个包。

class classArea
{
double lon,wid; //缺省数据成员的修饰符
double area(double x,double y) // 缺省成员方法的修饰符
{ double s; //方法内的变量
lon=x;
wid=y;
s=lon*wid; //求矩形面积
return s; //返回面积值
}
}

5.2.3
私有访问控制符private
用private修饰的数据成员或成员方法只能被该类自身所访问
和修改,而不能被任何其他类(包括该类的子类)来访问和引用。
它提供了最高的保护级别。当其他类希望获取或修改私有成员
时,需要借助于类的方法来实现。
【示例程序c5_3.java】 用private修饰的数据成员。
class P1
{ private int n=9; //私有数据成员n
int nn;
P1( )//构造方法
{ nn=n++; //可以被该类的对象自身访问和修改 }

void ma( )
{ System.out.println("n="+n);// 可以被该类的对象自身访问 }
}
public class c5_3 extends P1// 类class c5_3是类P1的子类
{
public static void main(String[ ] args)
{
P1 m1=new P1( );
System.out.println("m1.nn="+m1.nn);
// System.out.println("m1.n="+m1.n); 错,不能引用父类的私有成员
m1.ma( ); //可以引用P1类自身的成员方法
}
}
运行结果:
m1.nn=9 n=10

5.2.4
保护访问控制符protected
用protected修饰的成员变量可以被三种类引用:该类自身、
与它在同一个包中的其他类、在其他包中的该类的子类。使用
protected修饰符的主要作用是允许其他包中的它的子类来访问父
类的特定属性。
【示例程序c5_4.java】
class classArea
{
private double lon,wid; // private修饰的私有数据成员
protected double area(double x,double y)
// protected修饰的成员方法

{ double s;
lon=x;
wid=y;
s=lon*wid;
return s;
}
}
public class c5_4 //类c5_4与类classArea在一个包中
{
public static void main(String[ ] args)
{ double a=2.2,b=3.1,z;

/*在类c5_4中创建被访问类classArea的对象ss,ss可以访问
类classArea内的成员方法,因为该方法是protected修饰的。但不
能访问该类的数据成员,因为它们是用private修饰的私有数据
成员。*/
classArea ss=new classArea( );
z=ss.area(a,b);
System.out.println("z="+z);
}
}
运行结果:
z=68.200000000000001

5.3




多态是面向对象系统中的又一重要特性,它描述的是同名
方法可以根据发送消息的对象传送参数的不同,采取不同的行
为方式的特性。面向对象系统中采用多态,大大提高了程序的
抽象程度和简洁性,更重要的是,它最大限度地降低了类和程
序模块之间的耦合性,提高了类模块的封闭性,使得它们不需
了解对方的具体细节,就可以很好地共同工作。这一点对程序
的设计、开发和维护都有很大的好处。

5.3.1
多态的概念
多态是指一个程序中同名的不同方法共存的情况。这些方
法同名的原因是它们的最终功能和目的都相同,但是由于在完
成同一功能时,可能遇到不同的具体情况,所以需要定义含不
同的具体内容的方法,来代表多种具体实现形式。它是面向对
象程序设计中的一个特性,其目的是为了提高程序的抽象度、
封闭性和简洁性,统一一个或多个相关类对外的接口。
Java中提供两种多态机制:重载与覆盖。

5.3.2
方法重载
在同一类中定义了多个同名而不同内容的成员方法时,我
们称这些方法是重载(override)的方法。重载的方法主要通过形
式参数列表中参数的个数、参数的数据类型和参数的顺序等方
面的不同来区分。在编译期间,Java编译器检查每个方法所用
的参数数目和类型,然后调用正确的方法。

【示例程序c5_5.java】 加法重载的例子。
import java.awt.*;
import java.applet.*;
public class c5_5 extends Applet
{
int add(int a,int b) //重载的方法1
{ return(a+b);}
double add(double x,double y) // 重载的方法2
{ return(x+y); }
double add(double x,double y, double z) // 重载的方法3
{ return(x+y+z); }
public void paint(Graphics g)
{ g.drawString("Sum is:"+add(8.5,2.3),5,10);
g.drawString("Sum is:"+add(21,38),5,30);
g.drawString("Sum is:"+add(8.5,2.3,8.5+2.3),5,50);
}
}

运行结果如下:
Sum is: 10.8
Sum is: 59
Sum is: 21.6
该类中定义了三个名为add的方法:第一个方法是计算两个
整数的和;第二个方法是计算两个浮点数的和;第三个方法是
计算三浮点数据的和。编译器根据方法引用时提供的实际参数
选择执行对应的重载方法。

5.3.3
覆盖
由于面向对象系统中的继承机制,子类可以继承父类的方
法。但是,子类的某些特征可能与从父类中继承来的特征有所
不同,为了体现子类的这种个性,Java允许子类对父类的同名方
法重新进行定义,即在子类中定义与父类中已定义的相同名而
内容不同的方法。这种多态被称为覆盖(overload)。
由于覆盖的同名方法是存在于子类对父类的关系中,所以
只需在方法引用时指明引用的是父类的方法还是子类的方法,
就可以很容易地把它们区分开来。

import java.io.*;
public class AutInheritance{
public static void main(String args[]){
subClass subW = new subClass();
subW.doPrint();
}
} class superClass{
int x;
superClass(){
x=3;
System.out.println("in superClass:x="+x);
}
void doPrint(){
System.out.println("in superClass.doPrint()");
}
}

class subClass extends superClass{
int x;
subClass(){
super(); //调用superClass的构造方法
x=8;
System.out.println("in subClass() :x="+x);
}
void doPrint(){
super.doPrint(); //调用superClass方法
System.out.println("in subClass.doPrint()");
System.out.println("super.x="+super.x+" sub.x="+x);
}
}

5.4




5.4.1
继承的概念
同类事物具有共同性,在同类事物中,每个事物又具有其
特殊性。运用抽象的原则舍弃对象的特殊性,抽取其共同性,
则得到一个适应于一批对象的类,这便是一般类,而把具有特
殊性的类称为特殊类。也就是说,如果类B具有类A的全部属性
和方法,而且又具有自己特有的某些属性和方法,则把类A称作
一般类,把类B叫做类A的特殊类。例如:考虑轮船和客轮这两
个类。轮船具有吨位、时速、吃水线等属性,并具有行驶、停
泊等服务;客轮具有轮船的全部属性与服务,又有自己的特殊
属性(如载客量)和服务(如供餐等)。若把轮船看做一般类,则客
轮是轮船的特殊类。

在面向对象程序设计中运用继承原则,就是在每个由一般
类和特殊类形成的一般—特殊结构中,把一般类的对象实例和
所有特殊类的对象实例都共同具有的属性和操作一次性地在一
般类中进行显式的定义,在特殊类中不再重复地定义一般类中
已经定义的东西,但是在语义上,特殊类却自动地、隐含地拥
有它的一般类(以及所有更上层的一般类)中定义的属性和操作。
特殊类的对象拥有其一般类的全部或部分属性与方法,称作特
殊类对一般类的继承。

继承所表达的就是一种对象类之间的相交关系,它使得某
类对象可以继承另外一类对象的数据成员和成员方法。若类B
继承类A时,则属于B的对象便具有类A的全部或部分性质(数据
属性)和功能(操作)。我们称被继承的类A为基类、父类或超
类,而称继承类B为A的派生类或子类。父类与子类的层次关系
如图5.1所示。

图5.1 父类与子类的层次关系
运输工具
汽车轮船飞机
载重汽车公共汽车货轮客轮空中加油机客机
上一类的子类
下一类的父类
子类

继承避免了对一般类和特殊类之间共同特征进行的重复描
述。同时,通过继承可以清晰地表达每一项共同特征所适应的
概念范围——在一般类中定义的属性和操作适应于这个类本身
以及它以下的每一层特殊类的全部对象。运用继承原则使得系
统模型比较简练也比较清晰。

5.4.2
继承的特征
一般来说,继承具有下述特征:
(1) 继承关系是传递的。若类C继承类B,类B继承类A时,则
类C既有从类B继承下来的属性与方法,也有从类A中继承下来的
属性与方法,还可以有自己新定义的属性和方法。继承来的属
性和方法尽管是隐式的,却仍是类C的属性和方法。继承是在一
些比较一般的类的基础上构造、建立和扩充新类的最有效的手
段。

(2) 继承简化了人们对事物的认识和描述,能清晰体现相关
类间的层次结构关系。
(3) 提供软件复用功能。若类B继承类A,建立类B时只需要
再描述与基类(类A)不同的少量特征(数据成员和成员方法)。这
种做法能减小代码和数据的冗余度,大大增加程序的重用性。
(4) 通过增强一致性来减少模块间的接口和界面,大大增加
程序的易维护性。

(5) 提供多重继承机制。从理论上说,一个类可以是多个一
般类的特殊类,它可以从多个一般类中继承属性与方法,这便
是多重继承。而Java出于安全性和可靠性的考虑,仅支持单重
继承,而通过使用接口机制来实现多重继承。如图5.2为一个单
重继承与多重继承的例子。
在这个模型中,“本科生”、“研究生”、“脱产研究生”都为
单继承,而“在职研究生” 为多重继承,因为它不仅继承“学
生”/“研究生”的属性和行为,还继承“教师”的属性和行为。

图5.2 单重继承与多重继承
学生
本科生
教师
研究生
脱产
研究生
在职
研究生

5.4.3 Java
用extends
指明继承关系
在Java程序设计中,继承是通过extends
关键字来实现的。
在定义类时使用extends关键字指明新定义类的父类,新定义的
类称为指定父类的子类,这样就在两个类之间建立了继承关系。
这个新定义的子类可以从父类那里继承所有非private的属性和方
法作为自己的成员。实际上,在定义一个类而不给出extends关
键字及父类名时,默认这个类是系统类object的子类。下面分不
同情况来讲解。

1
.数据成员的继承
子类可以继承父类的所有非私有的数据成员。
【示例程序c5_6.java】
class a1
{ int x=25;
private int z; //不能被子类继承的私有数据成员z
}
class c5_6 extends a1 //a1是c5_6的父类,c5_6是a1的子类
{
public static void main(String[ ] argS)
{ c5_6 p=new c5_6( );
System.out.println("p.x="+p.x); //输出继承来的数据成员的值
//System.out.println("p.z="+p.z); 错,不能继承private修饰的z
}
} 运行结果:
p.x=25

2
.数据成员的隐藏
数据成员的隐藏是指在子类中重新定义一个与父类中已定
义的数据成员名完全相同的数据成员,即子类拥有了两个相同
名字的数据成员,一个是继承父类的,另一个是自己定义的。
当子类引用这个同名的数据成员时,默认操作是它自己定义的
数据成员,而把从父类那里继承来的数据成员“隐藏”起来。当子
类要引用继承自父类的同名数据成员时,可使用关键字super

导,这部分内容在5.4.4节介绍。

【示例程序c5_7.java】
class a1
{ int x=8; } //父类中定义了数据成员x
class c5_7 extends a1
{ int x=24; //子类中也定义了数据成员x
public static void main(String[ ] args)
{ int s1,s2;
a1 p=new a1( ); //创建父类的对象p
c5_7 p1=new c5_7( ); // 创建子类的对象p1
s1=p.x;
s2=p1.x; //子类对象引用自己的数据成员,把父类数据成员“隐藏”起来
System.out.println("s1="+s1);
System.out.println("s2="+s2);
}
}
运行结果:
s1=8 s2=24

3
.成员方法的继承
子类可以继承父类的非私有成员方法。下面的程序说明这一问题。
【示例程序c5_8.java】
class a1
{ int x=0,y=1;
void Myp( )
{ System.out.println("x="+x+" y="+y); }
private void Printme( )
{ System.out.println("x="+x+" y="+y); }
}

public class c5_8 extends a1
{
public static void main(String arg[ ])
{ int z=3;
c5_8 p1=new c5_8( );
p1.Myp( );
// p1.Printme( ); 错,不能继承父类的private方法
}
}
运行结果:
x=0 y=1

4
.成员方法的覆盖(overload)
子类可以重新定义与父类同名的成员方法,实现对父类方法
的覆盖。方法的覆盖与数据成员的隐藏的不同之处在于:子类
隐藏父类的数据成员只是使之不可见,父类同名的数据成员在
子类对象中仍然占有自己的独立的内存空间;子类方法对父类
同名方法的覆盖将清除父类方法占用的内存,从而使父类方法
在子类对象中不复存在。

【示例程序c5_9.java】
class a1
{ int x=10; int y=31;
public void Printme( )
{ System.out.println("x="+x+" y="+y);}
}
public class c5_9 extends a1
{ int z=35;
public void Printme( ) //子类中定义了与父类同名的成员方法,实现覆盖
{ System.out.println(" z="+z); }
public static void main(String arg[ ])
{ a1 p2=new a1( ); // 创建父类对象p2
c5_9 p1=new c5_9( ); // 创建子类对象p1
p1.Printme( ); //子类对象引用子类方法,覆盖了父类的同名方法
p2.Printme( ); //父类对象引用父类方法
}
}

运行结果是:
z=35
x=10 y=31
方法的覆盖中需要注意的是:子类在重新定义父类已有的
方法时,应保持与父类完全相同的方法名、返回值类型和参数
列表,否则就不是方法的覆盖,而是子类定义自己特有的方
法,与父类的方法无关。

5.4.4 this
与super
1
.this
的使用场合
在一些容易混淆的场合,例如,当成员方法的形参名与数
据成员名相同,或者成员方法的局部变量名与数据成员名相同
时,在方法内借助this来明确表示引用的是类的数据成员,而不
是形参或局部变量,从而提高程序的可读性。简单地说,this代
表了当前对象的一个引用,可将其理解为对象的另一个名字,
通过这个名字可以顺利地访问对象、修改对象的数据成员、调
用对象的方法。归纳起来,this的使用场合有下述三种:

(1) 用来访问当前对象的数据成员,其使用形式如下:
this.数据成员
(2) 用来访问当前对象的成员方法,其使用形式如下:
this.成员方法(参数)
(3) 当有重载的构造方法时,用来引用同类的其他构造方
法,其使用形式如下:
this(参数)

【示例程序c5_10.java】
class a1
{ int x=0;int y=1;
public void Printme( )
{ System.out.println("x="+x+" y="+y);
System.out.println("I am "+this.getClass( ).getName( ));
//用this来访问当前对象的成员方法,通过this表示当前对象来
打印当前对象的类名。其中
// getClass( )和getName( )是系统类库中提供的方法。
}
}

public class c5_10 extends a1
{
public static void main(String arg[ ])
{ c5_10 p1=new c5_10( );
p1.Printme( );
}
}
运行结果如下:
x=0 y=1
I am c5_10

【示例程序c5_11.java】
class classArea
{
double x,y;
double area(double x,double y)
{
double s;
this.x=x; // 借助this来表示引用的是类数据成员
y=y;//赋值号左边的y是数据成员,右边的y是参数,请复习4.1.9节
s=this.x*y; /*不能写成this.y,因为上一句没有这样写。如果写成
this.y,会产生什么结果?*/
return s;
}
}

public class c5_11 extends classArea
{
public static void main(String[ ] args)
{
double a=2.2,b=3.1,z;
c5_11 ss=new c5_11( );//创建一个ss对象
z=ss.area(a,b);//引用父类对象的成员方法求面积
System.out.println("z="+z);
}
}
运行结果是:
z=6.820000000000001

【示例程序c5_12.java】 计算圆的面积和周长。
public class c5_12
{
public static void main(String[ ] args)
{ double x;
circle cir=new circle(5.0);
x=cir.area( );
System.out.println("圆的面积="+x);
x=cir.perimeter( );
System.out.println("圆的周长="+x);
}
}

class circle
{
double r; //定义半径
final double PI=3.14159265359; //定义圆周率
public circle(double r) //类的构造方法
{ this.r=r; }
double area( ) //计算圆面积的方法
{ return PI*r*r; } //通过构造方法给r赋值
double perimeter( ) //计算圆周长的方法
{
return 2*(this.area( )/r); //使用this变量获取圆的面积
}
}

运行结果:
圆的面积=78.53981633974999
圆的周长=31.415926535899995

2
.super
的使用场合
super
表示的是当前对象的直接父类对象,是当前对象的直
接父类对象的引用。所谓直接父类是相对于当前对象的其他“祖
先”类而言。例如,假设类A派生出子类B,类B又派生出自己的
子类C,则B是C的直接父类,而A是C的祖先类。super代表的就
是直接父类。若子类的数据成员或成员方法名与父类的数据成员
或成员方法名相同时,当要调用父类的同名方法或使用父类的同
名数据成员,则可用关键字super来指明父类的数据成员和方法。
super的使用方法有三种:

(1) 用来访问直接父类隐藏的数据成员,其使用形式如下:
super.数据成员
(2) 用来调用直接父类中被覆盖的成员方法,其使用形式如下:
super.成员方法(参数)
(3) 用来调用直接父类的构造方法,其使用形式如下:
super(参数)

【示例程序c5_13.java】
class a1
{
int x=4;int y=1;
public void Printme( )
{
System.out.println("x="+x+" y="+y);
System.out.println("class name: "+this.getClass( ).getName( ));
}
}
public class c5_13 extends a1
{

int x;
public void Printme( )
{ int z=super.x+6; //引用父类(即a1类)的数据成员
super.Printme( ); //调用父类(即a1类)的成员方法
System.out.println("I am "+this.getClass( ).getName( ));
x=5;
System.out.println(" z="+z+" x="+x);//打印子类的数据成员
}

public static void main(String arg[ ])
{ int k;
a1 p1=new a1( );
c5_13 p2=new c5_13( );
p1.Printme( );
p2.Printme( );
// super.Printme( );//错,在static方法中不能引用非static成员方法
// k=super.x+23;//错,在static方法中不能引用非static数据成员
}
}

运行结果如下:
x=4 y=1
class name: a1
x=4 y=1
class name: c5_13
I am c5_13
z=10 x=5

5.4.5
构造方法的重载与继承
1
.构造方法的重载
一个类的若干个构造方法之间可以相互调用。当一个构造
方法需要调用另一个构造方法时,可以使用关键字this,同时这
个调用语句应该是整个构造方法的第一个可执行语句。使用关
键字this来调用同类的其他构造函数时,优点同样是可以最大限
度地提高对已有代码的利用程度,提高程序的抽象度和封装
性,减少程序的维护工作量。

【示例程序c5_14.java】
class addclass
{
public int x=0,y=0,z=0;
//以下是多个同名不同参数的构造方法
addclass(int x) //可重载的构造方法1
{ this.x=x; }
addclass(int x,int y) //可重载的构造方法2
{
this(x);// 当前构造方法调用可重载的构造方法1
this.y=y;
}

addclass(int x,int y,int z) //可重载的构造方法3
{
this(x,y);// 当前构造方法调用可重载的构造方法2
this.z=z;
}
public int add( )
{ return x+y+z;}
}
public class c5_14
{

public static void main(String[ ] args)
{
addclass p1=new addclass(2,3,5);
addclass p2=new addclass(10,20);
addclass p3=new addclass(1);
System.out.println("x+y+z="+p1.add( ));
System.out.println("x+y="+p2.add( ));
System.out.println("x="+p3.add( ));
}
}

运行结果:
x+y+z=10
x+y=30
x=1

2
.构造方法的继承
子类可以继承父类的构造方法,构造方法的继承遵循以下的
原则:
(1) 子类无条件地继承父类的不含参数的构造方法。
(2) 如果子类自己没有构造方法,则它将继承父类的无参数构
造方法作为自己的构造方法;如果子类自己定义了构造方法,则
在创建新对象时,它将先执行继承自父类的无参数构造方法,然
后再执行自己的构造方法。
(3) 对于父类的含参数构造方法,子类可以通过在自己的构造
方法中使用super关键字来调用它,但这个调用语句必须是子类构
造方法的第一个可执行语句。

【示例程序c5_15.java】
class addclass
{
public int x=0,y=0,z=0;
addclass(int x) //父类可重载的构造方法1
{ this.x=x; }
addclass(int x,int y) //父类可重载的构造方法2
{ this.x=x; this.y=y; }
addclass(int x,int y,int z) //父类可重载的构造方法3
{ this.x=x; this.y=y; this.z=z; }
public int add( )
{ return x+y+z;}
}

public class c5_15 extends addclass
{ int a=0,b=0,c=0;
c5_15(int x) //子类可重载的构造方法1
{ super(x);
a=x+7;
}
c5_15(int x,int y) //子类可重载的构造方法2
{ super(x,y);
a=x+5; b=y+5;
}
c5_15(int x,int y,int z) //子类可重载的构造方法3
{ super(x,y,z);
a=x+4; b=y+4; c=z+4;
}

public int add( )
{
System.out.println("super: x+y+z="+super.add( ));
return a+b+c;
}
public static void main(String[ ] args)
{
c5_15 p1=new c5_15(2,3,5);
c5_15 p2=new c5_15(10,20);
c5_15 p3=new c5_15(1);
System.out.println("a+b+c="+p1.add( ));
System.out.println("a+b="+p2.add( ));
System.out.println("a="+p3.add( ));
}
}

运行结果如下:
super: x+y+z=10
a+b+c=22
super: x+y+z=30
a+b=40
super: x+y+z=1
a=8

5.4.6
向方法传递对象
前面已讲过传递给方法的参数可以是表达式(如常量、变
量、对象)等,并说明传递给方法的参数若是变量,则只能由
实参传递给形参,而不能由形参带回,它是一种单向值传递。
也就是说,在方法的引用过程中,对于形参变量值的修改并不
影响实参变量的值。但是,传递给方法的参数若是对象,则方
法可以对其做永久性修改。

【示例程序c5_16.java】
import java.awt.*;
import java.applet.*;
class student
{ public String Name;
public int age=16;
public int score=0;
public void ShowStudent(Graphics g,int x,int y)
{ g.drawString("Name:"+Name,x,y);
g.drawString("age:"+age,x,y+20);
g.drawString("score:"+score,x,y+40);
}
}

public class c5_16 extends Applet
{
public void studentAttributes(student s,
String Name, int age, int score)
{ s.Name=Name; s.age=age;
s.score=score;
}

public void paint(Graphics g)
{ student st1=new student( ); //创建对象st1
student st2=new student( ); //创建对象st2
studentAttributes(st1,"zhang",23,81); //对象st1作为实参
studentAttributes(st2,"li",24,90); //对象st2作为实参
st1.ShowStudent(g,25,25); //执行此方法可发现对象st1将新值带回
st2.ShowStudent(g,25,120); //再次执行此方法可发现对象st2将新值带回
}
}
运行结果如图5.3所示。

图5.3 程序c5_16的运行结果

5.4.7
类转换
类转换就是指父类对象与子类对象之间在一定条件下的相互
转换。父类对象与子类对象之间的相互转换规则如下:
(1) 父类对象与子类对象之间可以隐式转换(也称默认转换),
也可以显式转换(也称强制转换)。
(2) 处于相同类层次的类的对象不能进行转换。
(3) 子类对象可以转换成父类对象,但对数据成员的引用必
须使用强制转换。

【示例程序c5_17.java】
class SuperClass //定义父类
{ int x=1,y=2,t=98;
void show( )
{ System.out.println("x+y="+(x+y)); }
}
class SubClass extends SuperClass //定义子类
{ int y=9,z=7;
void show( )
{ System.out.println("y*z="+(y*z)); }
}

public class c5_17 //使用父类与子类
{
public static void main(String[ ] args)
{ SuperClass sc,scf; //声明父类对象
SubClass sb,sbf; //声明子类对象
sc=new SuperClass( );
sb=new SubClass( );
System.out.println("sc.x="+sc.x+" sc.y="+sc.y+" sc.t="+sc.t);
//System.out.println(" sc.z="+sc.z); 错,父类对象不能引用子类数据成员z
sc.show( );

//子类对象可以被视为是其父类的一个对象,因此可以引用父类的数据成员
System.out.println("sb.y="+sb.y+" sb.z="+sb.z+" sb.t="+sb.t);
sb.show( );
scf=(SuperClass)sb; // 强制转换,子类引用转换为父类引用
//也可以用默认转换,写成:scf=sb;
//下一条语句中,scf引用的数据成员y是父类的y,scf不可引用z
System.out.println("scf.x="+scf.x+" scf.y="+scf.y);
System.out.print("scf.show ( ):/t"); //引用子类的成员方法
scf.show( );
sbf=(SubClass)scf; //强制转换,父类引用转换为子类引用
System.out.println("sbf.x="+sbf.x+" sbf.y="+sbf.y+" sbf.z="+sbf.z);
System.out.print("sbf.show ( ):/t");
sbf.show( );
}
}

运行结果:
sc.x=1 sc.y=2 sc.t=98
x+y=3
sb.y=9 sb.z=7 sb.t=98
y*z=63
scf.x=1 scf.y=2
scf.show( ): y*z=63
sbf.x=1 sbf.y=9 sbf.z=7
sbf.show( ): y*z=63

5.4.8
继承与封装的关系
在面向对象系统中,有了封装机制以后,对象之间只能通过
消息传递进行通信。那么,继承机制的引入是否削弱了对象概念
的封装性?继承和封装是否矛盾?其实这两个概念并没有实质性
的冲突,在面向对象系统中,封装性主要指的是对象的封装性,
即将属于某一类的一个具体的对象封装起来,使其数据和操作成
为一个整体。
在引入了继承机制的面向对象系统中,对象依然是封装得很
好的实体,其他对象与它进行通讯的途径仍然只有一条,那就是
发送消息。类机制是一种静态机制,不管是基类还是派生类,对
于对象来说,它仍然是一个类的实例,既可能是基类的实例,也
可能是派生类的实例。因此继承机制的引入丝毫没有影响对象的
封装性。

从另一角度看,继承和封装机制还具有一定的相似性,它
们都是一种共享代码的手段。继承是一种静态共享代码的手
段,通过派生类对象的创建,可以接受某一消息,启动其基类
所定义的代码段,从而使基类和派生类共享了这一段代码。封
装机制所提供的是一种动态共享代码的手段,通过封装,我们
可将一段代码定义在一个类中,在另一个类所定义的操作中,
我们可以通过创建该类的实例,并向它发送消息而启动这一段
代码,同样也达到共享的目的。

5.5
抽象类、接口与包
5.5.1
抽象类
假设我们要编写一个计算矩形、三角形和圆的面积与周长
的程序,若按前面所学的方式编程,我们必须定义四个类:圆
类、三角形类、矩形类和使用前三个类的公共类,它们之间没
有继承关系,如图5.4所示。程序写好后虽然能执行,但从程序
的整体结构上看,三个类之间的许多共同属性和操作在程序中
没有很好地被利用,致使重复编写代码,降低了程序的开发效
率,且使出现错误的机会增加。

图5.4 具有相同特征却彼此独立的几个类
圆类
圆心座标
半径
计算面积
计算周长
三角形类
底边长

计算面积
计算周长
矩形类


计算面积
计算周长

仔细分析上面例子中的三个类,可以看到这三个类都要计
算面积与周长,虽然公式不同但目标相同。因此,我们可以为
这三个类抽象出一个父类,在父类里定义圆、三角形和矩形三
个类共同的数据成员及成员方法。把计算面积与周长的成员方
法名放在父类给予说明,而具体的计算公式再在子类中实现。
如图5.5所示。这样,我们通过父类就大概知道子类所要完成的
任务,而且,这些方法还可以应用于求解平行四边形、梯形等
图形的周长与面积。这种结构就是抽象类的概念。

图5.5 抽象类及其应用
圆类
圆心座标
半径
三角形类
底边长

计算面积
计算周长


抽象类
梯形类
计算面积
计算周长
抽象方法
计算面积
计算周长
矩形类
计算面积
计算周长
上、下底边
长高

在Java程序中用抽象类(abstract class)来实现自然界的抽象概
念。抽象类的作用在于将许多有关的类组织在一起,提供一个公
共的类,即抽象类,而那些被它组织在一起的具体的类做为它的
子类由它派生出来。抽象类刻画了公有行为的特征,并通过继承
机制传送给它的派生类。在抽象类中定义的方法称为抽象方法,
这些方法只有方法头的声明,而用一个分号来代替方法体的定
义,即只定义成员方法的接口形式,而没有具体操作。只有派生
类对抽象成员方法的重定义才真正实现与该派生类相关的操作。
在各子类继承了父类的抽象方法之后,再分别用不同的语句和方
法体来重新定义它,形成若干个名字相同,返回值相同,参数列
表也相同,目的一致但是具体实现有一定差别的方法。抽象类中
定义抽象方法的目的是实现一个接口,即所有的子类对外都呈现
一个相同名字的方法。

抽象类是它的所有子类的公共属性的集合,是包含一个或
多个抽象方法的类。使用抽象类的一大优点就是可以充分利用
这些公共属性来提高开发和维护程序的效率。对于抽象类与抽
象方法的限制如下:
(1) 凡是用abstract
修饰符修饰的类被称为抽象类。凡是用
abstract修饰符修饰的成员方法被称为抽象方法。
(2) 抽象类中可以有零个或多个抽象方法,也可以包含非抽
象的方法。

(3) 抽象类中可以没有抽象方法,但是,有抽象方法的类必
须是抽象类。
(4) 对于抽象方法来说,在抽象类中只指定其方法名及其类
型,而不书写其实现代码。
(5) 抽象类可以派生子类,在抽象类派生的子类中必须实现
抽象类中定义的所有抽象方法。
(6) 抽象类不能创建对象,创建对象的工作由抽象类派生的
子类来实现。

(7) 如果父类中已有同名的abstract方法,则子类中就不能再
有同名的抽象方法。
(8) abstract不能与final并列修饰同一个类。
(9) abstract 不能与private ,static,final或native并列修饰同一
个方法。
(10) abstract 类中不能有private的数据成员或成员方法。

【示例程序c5_18.java】 抽象类应用。
import java.awt.*;
import java.applet. *;
abstract class Shapes //定义一个抽象类Shapes
{
public int x,y; //x、y为画图的坐标
public int width,height;
public Shapes(int x,int y,int width,int height)
{ this.x=x;
this.y=y;
this.width=width;
this.height=height;
}

abstract double getArea( ); //求图形面积的抽象方法
abstract double getPerimeter( ); //求图形周长的抽象方法
}
class Square extends Shapes //由抽象类Shapes派生的子类——矩形类
{
public double getArea( ){return(width*height);}
public double getPerimeter( ){return(2*width+2*height);}
public Square(int x,int y,int width,int height)
{ super(x,y,width,height); }
}
class Triangle extends Shapes //由抽象类Shapes派生的子类——三角形类
{

public double c; //斜边
public double getArea( ){return(0.5*width*height);}
public double getPerimeter( ){return(width+height+c);}
public Triangle(int x,int y,int base,int height)
{
super(x,y,base,height);
c=Math.sqrt(width*width+height*height);
}
}
class Circle extends Shapes //由抽象类Shapes派生的子类——圆类
{

public double r; //半径
public double getArea( ){return(r*r*Math.PI);}
public double getPerimeter( ){return(2*Math.PI*r);}
public Circle(int x,int y,int width,int height)
{
super(x,y,width,height);
r=(double)width/2.0;
}
}
public class c5_18 extends Applet
{

Square Box=new Square(5,15,25,25);
Triangle tri=new Triangle(5,50,8,4);
Circle Oval=new Circle(5,90,25,25);
public void paint(Graphics g)
{
//画正方形
g.drawRect(Box.x,Box.y,Box.width,Box.height);
g.drawString("Box Area:"+Box.getArea( ),50,35);
g.drawString("Box Perimeter:"+Box.getPerimeter( ),50,55);
g.drawString("tri Area:"+tri.getArea( ),50,75);

g.drawString("tri Perimeter:"+tri.getPerimeter( ),50,95);
g.drawOval(Oval.x,Oval.y,Oval.width,Oval.height); //画圆
g.drawString("oval Area:"+Oval.getArea( ),50,115);
}
}
运行结果如图5.6所示。
从本例可以看出,类Square、类Circle及类Triangle都由抽象
类Shape派生而来,都实现了getArea 和getPerimeter抽象方法。

图5.6 程序c5_18的运行结果

5.5.2
接口
多重继承是指一个子类可以有多个直接父类,该子类可以全
部或部分继承所有直接父类的数据成员及成员方法。例如,冷藏
车既是一种汽车,也是一种制冷设备,所以它是汽车的子类也是
制冷设备的子类。自然界中这种多继承结构到处可见。
在面向对象的程序设计语言中,有些语言(如C++)提供了多继
承机制。而Java出于安全性、简化程序结构的考虑,不支持类间
的多继承而只支持单继承。然而在解决实际问题的过程中,在很
多情况下仅仅依靠单继承不能将复杂的问题描述清楚。为了使
Java程序的类间层次结构更加合理,更符合实际问题的本质,
Java
语言提供接口来实现多重继承机制。

1
.声明接口
声明接口的格式如下:
[修饰符] interface接口名[extends 父接口名列表]
{
常量数据成员声明
抽象方法声明
}

说明:
(1) interface是声明接口的关键字,可以把它看成一个特殊
类。
(2) 接口名要求符合Java标识符规定。
(3) 修饰符有两种:public 和默认。public修饰的接口是公共
接口,可以被所有的类和接口使用;默认修饰符的接口只能被
同一个包中的其他类和接口使用。

(4) 父接口列表:接口也具有继承性。定义一个接口时可以
通过extends关键字声明该接口是某个已经存在的父接口的派生接
口,它将继承父接口的所有属性和方法。与类的继承不同的是一
个接口可以有一个以上的父接口,它们之间用逗号分隔。
(5) 常量数据成员声明:常量数据成员前可以有也可没有修
饰符。修饰符是public final static和fina1 static;接口中的数据成
员都是用 final修饰的常量,写法如下:
修饰符 数据成员类型 数据成员名=常量值

数据成员名=常量值

例如:
public final static double PI=3.14159;
final static int a=9;
int SUM=100;(等价于final static int SUM=100;)

(6) 抽象方法声明:接口中的方法都是用abstract修饰的抽象
方法。在接口中只能给出这些抽象方法的方法名、返回值和参
数列表,而不能定义方法体,即这些接口仅仅是规定了一组信
息交换、传输和处理的“接口”。格式如下:
返回值类型 方法名(参数列表);
其中:接口中的方法默认为public abstract方法。接口中方
法的方法体可以由Java语言书写,也可以由其他语言书写。方
法体由其他语言书写时,接口方法由native修饰符修饰。

从上面的格式可以看出,定义接口与定义类非常相似。实
际上完全可以把接口理解成为一种特殊的类,由常量和抽象方
法组成的特殊类。一个类只能有一个父类,但是它可以同时实
现若干个接口。这种情况下,如果把接口理解成特殊的类,那
么这个类利用接口实际上就获得了多个父类,即实现了多重继
承。
接口定义仅仅是实现某一特定功能的一组功能的对外接口
和规范,而不能真正地实现这个功能,这个功能的真正实现是
在“继承”这个接口的各个类中完成的,即要由这些类来具体定义
接口中各抽象方法的方法体。因而在Java中,通常把对接口功能
的“继承”称为“实现”。

2
.定义接口注意事项
定义接口要注意以下几点:
(1) 接口定义用关键字interface,而不是用class。
(2) 接口中定义的数据成员全是final static,即常量。
(3) 接口中没有自身的构造方法,所有成员方法都是抽象
方法。
(4) 接口也具有继承性,可以通过extends关键字声明该接
口的父接口。

3.
类实现接口的注意事项
一个类要实现接口,即一个类要调用多个接口时,要注意
以下几点:
(1) 在类中,用implements关键字就可以调用接口。一个类
若要调用多个接口时,可在implements后用逗号隔开多个接口的
名字。
(2) 如果实现某接口的类不是abstract的抽象类,则在类的定
义部分必须实现指定接口的所有抽象方法,即为所有抽象方法
定义方法体,而且方法头部分应该与接口中的定义完全一致,
即有完全相同的返回值和参数列表。

(3) 如果实现某接口的类是abstract的抽象类,则它可以不实
现该接口所有的方法。但是对于这个抽象类的任何一个非抽象
的子类而言,它们的父类所实现的接口中的所有抽象方法都必
须有实在的方法体。这些方法体可以来自抽象的父类,也可以
来自子类自身,但是不允许存在未被实现的接口方法。这主要
体现了非抽象类中不能存在抽象方法的原则。
(4) 接口的抽象方法的访问限制符都己指定为public,所以
类在实现方法时,必须显式地使用public修饰符,否则将被系统
警告为缩小了接口中定义的方法的访问控制范围。

【示例程序c5_19.java】 将例c5_18.java改写为接口程序。
import java.awt.*;
import java.applet.*;
interface Shapes//定义一个接口
{
abstract double getArea( );
abstract double getPerimeter( );
}
class Square implements Shapes //类要实现接口
{

public int x,y;
public int width,height;
public double getArea( ){return(width*height);}
public double getPerimeter( ){return(2*width+2*height);}
public Square(int x,int y,int width,int height)
{
this.x=x;
this.y=y;
this.width=width;
this.height=height;
}
}

class Triangle implements Shapes //类要实现接口
{
public int x,y;
public int width,height;
public double c;
public double getArea( ){return(0.5*width*height);}
public double getPerimeter( ){return(width+height+c);}
public Triangle(int x,int y,int base,int height)
{

this.x=x;
this.y=y;
width=base;
this.height=height;
c=Math.sqrt(width*width+height*height);
}
}
class Circle implements Shapes //类要实现接口
{

public int x,y;
public int width,height;
public double r;
public double getArea( ){return(r*r*Math.PI);}
public double getPerimeter( ){return(2*Math.PI*r);}
public Circle(int x,int y,int width,int height)
{
this.x=x;
this.y=y;
this.width=width;
this.height=height;
r=(double)width/2.0;
}
}

public class c5_19 extends Applet
{ Square Box=new Square(5,15,25,25);
Triangle tri=new Triangle(5,50,8,4);
Circle Oval=new Circle(5,90,25,25);
public void paint(Graphics g)
{
g.drawRect(Box.x,Box.y,Box.width,Box.height);
g.drawString("Box Area:"+Box.getArea( ),50,35);
g.drawString("Box Perimeter:"+Box.getPerimeter( ),50,55);
g.drawString("tri Area:"+tri.getArea( ),50,75);
g.drawString("tri Perimeter:"+tri.getPerimeter( ),50,95);
g.drawOval(Oval.x,Oval.y,Oval.width,Oval.height);
g.drawString("oval Area:"+Oval.getArea( ),50,115);
}
}

图5.7 程序c5_19的运行结果

【示例程序c5_20.java】 将例c5_19.java改写为既有继承类又有接口的程序。
import java.awt.*;
import java.applet.*;
interface Shapes
{ abstract double getArea( );
abstract double getPerimeter( );
}
class Coordinates
{
int x,y;
public Coordinates(int x,int y)
{
this.x=x;
this.y=y;
}
}

//Square Coordinates Shapes
class Square extends Coordinates implements Shapes
{
public int width,height;
public double getArea( ){return(width*height);}
public double getPerimeter( ){return(2*width+2*height);}
public Square(int x,int y,int width,int height)
{
super(x,y);
this.width=width;
this.height=height;
}
}

class Triangle extends Coordinates implements Shapes
{
public int width,height;
public double c;
public double getArea( ){return(0.5*width*height);}
public double getPerimeter( ){return(width+height+c);}
public Triangle(int x,int y,int base,int height)
{
super(x,y);
width=base;
this.height=height;
c=Math.sqrt(width*width+height*height);
}
}

class Circle extends Coordinates implements Shapes
{
public int width,height;
public double r;
public double getArea( ){return(r*r*Math.PI);}
public double getPerimeter( ){return(2*Math.PI*r);}
public Circle(int x,int y,int width,int height)
{
super(x,y);
this.width=width;
this.height=height;
r=(double)width/2.0;
}
}

public class c5_20 extends Applet
{
Square Box=new Square(5,15,25,25);
Triangle tri=new Triangle(5,50,8,4);
Circle Oval=new Circle(5,90,25,25);
public void paint(Graphics g)
{
g.drawRect(Box.x,Box.y,Box.width,Box.height);
g.drawString("Box Area:"+Box.getArea( ),50,35);
g.drawString("Box Perimeter:"+Box.getPerimeter( ),50,55);
g.drawString("tri Area:"+tri.getArea( ),50,75);
g.drawString("tri Perimeter:"+tri.getPerimeter( ),50,95);
g.drawOval(Oval.x,Oval.y,Oval.width,Oval.height);
g.drawString("oval Area:"+Oval.getArea( ),50,115);
}
}
运行结果与示例程序c5_19.java的相同。

5.5.3
包与程序复用
前面已介绍过,Java语言提供了很多包,例如,Java.io 、
Java.awt 、Java lang等, 这些包中存放着一些常用的基本类,
如System类、String类、Math类等,它们被称为Java类库中的包。
使用这些包使我们的编程效率大大提高。大家不妨想一想,直
接使用Java类库中Math.sqrt( )方法求解任意非负实数的平方根与
自己动手编写这个程序,哪个效率高?在许多场合反复使用那
些早已编写好的,且经过严格测试的程序的技术被称为软件复
用,在面向对象的程序设计中称为对象复用。

对象复用是面向对象编程的主要优点之一,它是指同一对象
在多个场合被反复使用。在Java语言中,对象是类的实例,类是
创建对象的模板,对象是以类的形式体现的。因此,对象复用也
就体现在类的重用上。
利用面向对象技术开发一个实际的系统时,编程人员通常需
要定义许多类共同工作,且有些类可能要在多处反复使用。在
Java程序中,如果要想使一个类在多个场合下反复使用,可以把
它存放在一个称之为“包”的程序组织单位中。可以说,包是接口
和类的集合,或者说包是接口和类的容器。使用包有利于实现不
同程序间类的重用。Java语言为编程人员提供了自行定义包的机
制。

包的作用有两个:一是划分类名空间,二是控制类之间的
访问。这就需要我们注意下述两点。第一,包是一个类名空
间,所以,同一个包中的类(包括接口)不能重名,不同包中的
类可以重名。第二,类之间的访问控制是通过类修饰符来实现
的,若类声明修饰符为public,则表明该类不仅可供同一包中的
类访问,也可以被其他包中的类访问。若类声明无修饰符,则
表明该类仅供同一包中的类访问。

1.
创建包
包的创建就是将源程序文件中的接口和类纳入指定的包。在
一般情况下Java源程序的构成由四部分组成:
(1) 一个包(package)说明语句(可选项)。其作用是将本源文件
中的接口和类纳入指定包。源文件中若有包说明语句,必须是第
一个语句。
(2) 若干个(import)语句(可选项)。其作用是引入本源文件中
需要使用的包。
(3) 一个public的类声明。在一个源文件中只能有一个public
类。
(4) 若干个属于本包的类声明(可选)。

包的声明语句格式:
package
包名;
利用这个语句就可以创建一个具有指定名字的包,当
前.java
文件中的所有类都被放在这个包中。例如下面的语句是
合法的创建包的语句:
package shape;
package shape.shapeCircle;

创建包就是在当前文件夹下创建一个子文件夹,存放这个
包中包含的所有类的.class文件。package shape.shapeCircle;语
句中的符号“.”(注意不是“,”)代表了目录分隔符,说明这
个语句创建了两个文件夹:第一个是当前文件夹下的子文件夹
shape;第二个是shape下的子文件夹shapeCircle,当前包中的所
有类就存放在这个文件夹(ShapeCircle)里。
若源文件中未使用package,则该源文件中的接口和类位于
Java的无名包中(无名包又称缺省包),它们之间可以相互引用非
private的数据成员或成员方法。无名包中的类不能被其他包中
的类引用和复用。

【示例程序c5_21.java】 改写示例程序c5_20.java,将接口
与类纳入包。
第一步,建立5个源文件,文件的接口与类都属于包shape。
设这些文件都存入当前文件夹d:/myjava中。
(1) 名为Shapes.java的文件为:
package shape;
public interface Shapes
{ abstract double getArea( );
abstract double getPerimeter( );
}

(2) 名为Coordinates.java的文件为:
package shape;
class Coordinates
{ public int x,y;
public Coordinates(int x,int y)
{ this.x=x;
this.y=y;
}
}

(3) 名为Square.java的文件为:
package shape;
public class Square extends Coordinates implements Shapes
{
public int width,height;
public double getArea( ){return(width*height);}
public double getPerimeter( ){return(2*width+2*height);}
public Square(int x,int y,int width,int height)
{
super(x,y);
this.width=width;
this.height=height;
}
}

(4) 名为Triangle.java的文件为:
package shape;
public class Triangle extends Coordinates implements Shapes
{
public int width,height;
public double c;
public double getArea( ){return(0.5*width*height);}
public double getPerimeter( ){return(width+height+c);}
public Triangle(int x,int y,int base,int height)
{
super(x,y);
width=base;
this.height=height;
c=Math.sqrt(width*width+height*height);
}
}

第二步,对上面的5个文件进行编译。
d:/myjava>javac - d d:/myjava Shapes.java
d:/myjava>javac - d d:/myjava Coordinates.java
d:/myjava>javac - d d:/myjava Triangle.java
d:/myjava>javac - d d:/myjava Square.java
d:/myjava>javac - d d:/myjava Circle.java
在编译过程中,若不存在d:/myjava/shape文件夹,则系统自
动创建此文件夹。编译命令中的“-d”是编译开头,其作用是通知
javac将编译好的类文件(.class)存入文件夹 d:/myjava/shape中。

第三步,shape包的引用。
建立一个引用程序:c5_21.java,并将该程序存放在
d:/myjava文件夹中。
package p2;
import java.awt.*;
import java.applet.*;
import shape.*; //引入我们创建的包
public class c5_21 extends Applet
{

Square Box=new Square(5,15,25,25);
Triangle tri=new Triangle(5,50,8,4);
Circle Oval=new Circle(5,90,25,25);
public void paint(Graphics g)
{
g.drawRect(Box.x,Box.y,Box.width,Box.height);
g.drawString("Box Area:"+Box.getArea( ),50,35);
g.drawString("Box Perimeter:"+Box.getPerimeter( ),50,55);
g.drawString("tri Area:"+tri.getArea( ),50,75);
g.drawString("tri Perimeter:"+tri.getPerimeter( ),50,95);
g.drawOval(Oval.x,Oval.y,Oval.width,Oval.height);
g.drawString("oval Area:"+Oval.getArea( ),50,115);
}
}

使用下述命令编译该程序:
d:/myjava>javac -d d:/myjava c5_21.java
对其编译时,javac从环境变量CLASSPATH(类似于DOS操
作系统中的PATH,它指明所有缺省的类字节码文件路径)提供的
路径去搜索名为shape的子目录,找到后引用该文件夹下的“.class”
文件。编译结束后,在文件夹d:/myjava/p2下产生c5_21.class文件。
由于类属于p2包,在执行c5_21.class时应在c5_21.class前加包名
和小数点。见下面两行列出的c5_21.html文件内容。
<APPLET CODE="p2.c5_21.class" width=150 height=100>
</APPLET>
运行结果与c5_20.java相同。

2
.包的引用
将类组织成包的目的是为了更好地利用包中的类。通常一
个类只能引用与它在同一个包中的类。如果需要使用其他包中
的public类,则可以使用如下的几种方法。
(1) 在引入的类前加包名。
一个类要引用其他类有两种方式:一是对于同一包中的其
他类可直接引用,如c5_18.java中的引用。二是对于不同包中的
其他类引用时需在类名前加包名,例如,若你的源文件中要引
用包shape中的类Circle,可在源文件中的Circle之前加
“shape.”,如shape.Circle c=new shape.Circle(25,25,5,3)。这种类
名前加包名的引用方式适用于在源文件中使用少的情况。

(2) 用import关键字加载需要使用的类。
上面的方法使用起来比较麻烦,还可以在当前程序中利用
import关键字加载需要使用的类,这样在程序中引用这个类的地
方就不需要再使用包名作为前缀。例如上面的语句在程序开始
处增加了:
import shape.Circle ;//在程序开始加载其他包的类
语句之后,就可以直接写成:
Circle c=new Circle(25,25,5,3);

(3) 用import关键字加载整个包。
上面的方法利用import语句加载了其他包中的一个类。若希
望引入整个包也可以直接利用import语句。加载整个包的import
语句可以写为:
import shape.*;
import java.awt.*;
上面第一个语句见c5_21.java例,第二个语句在前面的例子
中已多次使用,它的作用是把Java系统有关抽象窗口工具的包(系
统类库)加载到当前程序中。与加载单个类相同,加载整个包
后,凡是用这个包中的类,都不需要再使用包名前缀。

(4) 设置CLASSPATH环境变量。
包是一种组织代码的有效手段,包名实际上就指出了程序
中需要使用的.class文件的所在之处。另一个能指明.class文件夹
所在结构的是环境变量CLASSPATH。当一个程序找不到它所
需使用的其他类的.class文件时,系统会自动到CLASSPATH环
境变量所指明的路径中去寻找。
设置CLASSPATH环境变量可以通过编辑系统的
AUTOEXEC.BAT文件,或使用相关的DOS命令。例如下面的语

SET CLASSPATH=c:/jdk1.3.1/lib/tools.jar;

将c:/jdk1.3.1/lib/tools.jar设置为当前CLASSPATH目录。
对于Java Application程序,还可以通过为Java解释器设置
参数来指定类文件路径。例如,对于JDK中的Java解释器
java.exe,有开关参数-classpath。假设当需要解释执行的
test.class文件不在当前目录而在C盘的TEMP目录下时,可以使
用如下的命令行语句
java test -classpath c:/temp
来运行这个程序。

阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页