Java最新实用教程第六章 接口与多态
Java最新实用教程
2009年 7月 27日星期一 2
学习目的:
掌握接口的创建与实现
理解塑型
掌握多态,了解动态绑定
理解多态与构造方法
了解内部类
学习重点:
接口的创建与实现
多态
Java最新实用教程
2009年 7月 27日星期一 3
第 六 章 接口与多态本章提要:
接口
塑型
多态
多态与构造方法
内部类
Java最新实用教程
2009年 7月 27日星期一 4
6.1接口
6.1.1接口的作用及语法
1,接口的作用
java的接口也是面向对象的一个重要机制 。 它的引进是为了实现多继承,同时免除 C++中的多继承那样的复杂性 。 接口中的所有方法都是抽象的,这些抽象法由实现这一接口的不同类来具体完成 。 在使用中,接口类的变量可以用来代表任何实现了该接口的类的对象,这就相当于把类根据其实现的功能来分别代表,
接口 ( interface) 是抽象的概念能深入了一层 。 可将其想象为一个,纯,抽象类 。 它允许创建者规定一个类的基本形式:方法的名称,参数列表,返回类型,但不能规定方法主体 。 接口也包含了数据成员,但它们都默认为 static和 final。 接口只提供一种形式,并不提供实施的细节 。
Java最新实用教程
2009年 7月 27日星期一 5
而不必顾虑它所在的类继承层次 。 这样可以最大限度地利用动态绑定,隐藏实现细节 。 接口还可以用来实现不同类之间的常量共享 。
人们常使用接口来建立类和类之间的,协议,。 有些面向对象的程序设计语言采用了名为,protocol”( 协议 ) 的关键字,它做的便是与接口相同的事情 。
例 6.1:接口举例 。
保险公司具有车辆保险,人员保险,公司保险等很多保险业务
,这些保险业务的保险对象不同,但在对外提供服务方面具有相似性,如都需要计算保险费 ( premium) 等,因此可以声明一个 Insurable接口,接口中包含计算保险费所需要的方法,并使用不同的类如车辆类,人员类等分别实现这个接口 。
总地来说,接口本质上是一种特殊的抽象类,目的是用来实现多继承 。 接口与抽象类都是声明多个类的共同属性 。 但接口
Java最新实用教程
2009年 7月 27日星期一 6
与抽象类不同的是:接口允许在看起来不相干的类之间定义共同行为 。
2,接口的语法使用关键字 interface来声明接口 。 Java接口中只有方法的声明,
而没有直接定义方法的内容 。 它的声明格式为:
[接口修饰符 ] interface 接口名称 [extends 父类名 ]
{
… //方法的声明或静态常量
}
接口与一般类的声明基本一样,本身也具有数据成员与方法,
但数据成员一定要赋初值,且此值将不能再更改,即为 final常量,而方法必须为,抽象方法,,由前面 5.4.2节中的抽象方法知,抽象方法必须被修饰为 public abstract,不过在接口中成员数据前的 final关键字,方法的 public关键字,abstract关键字可以省略 。
Java最新实用教程
2009年 7月 27日星期一 7
可以将例 6.1中的 Insurable接口声明如下:
public interface Insurable
{
public int getNumber();
public int getCoverageAmount();
public double caculatePremium();
public Date getExpiryDate();
}
从中可以看出,接口中的方法值提供一种形式,而没有具体实施的细节 。
例 6.2:接口应用举例 。
声明一个接口 Shap2D,可以用它来实现二维的几何形状类
Circle和 Rectangle。 对二维的几何形状而言,面积的计算是很重要的 。 因此可以把计算面积的方法声明在接口里,并且
Circle类的面积计算需要常量 pi,所以接口的内容就包含了常量 pi和求面积的方法 。
可以定义出如下接口:
Java最新实用教程
2009年 7月 27日星期一 8
interface Shape2D //声明接口 Shape2D
{
final double pi=3.14; //数据成员一定要初始化
public double getArea(); //抽象方法
}
6.1.2接口的实现前面提到,接口是一种特殊的抽象类,所以接口也不能用 new
运算符直接实例化 。 但是可以利用接口的特性来构造新的类,
再用它来创建对象 。 利用接口构造新类的过程,称为接口的实现 。
一个类如要使用一个或一组接口的特性,要使用 implements关键字 。 实现接口的语法如下:
public class 类名 [extends 父类名 ] implements 接口名 [,接口名 [… ]]
{

… //接口中的方法实现
}
Java最新实用教程
2009年 7月 27日星期一 9
完成接口的类必须实现接口中的所有方法,具体实现了一个接口后,就获得了一个普通的类,可用标准方式对其进行使用或扩展 。 注意在实现一个接口的时候,来自接口的方法必须声明成 public。
例 6.3:实现接口 Insurable。
下面声明汽车类实现例 6.1中的 Insurable接口,并且实现了接口中的所有抽象方法 。
Public class Car implements Isurable //使用接口 Insurable
{
public int getNumber() { //方法体语句
}
public int getCoverageAmount() { //方法体语句
}
public double caculatePremium() { //方法体语句
}
public Date getExpiryDate(){ //方法体语句
}
}
Java最新实用教程
2009年 7月 27日星期一 10
例 6.4:实现例 6.2中的接口 Shape2D。
下面声明 Circle类计算圆面积,Rectangle类计算矩形面积,
分别实现 Shape2D接口 。
class Circle implements Shape2D
{
double r;
public Circle(double r)
{
this.r=r;
}
public double getArea() //实现抽象方法
{
return (pi*r*r);
}
}
class Rectangle implements Shape2D
{
double w,h;
public Rectangle(double width,double height)
{
w=width;
h=height;
}
Java最新实用教程
2009年 7月 27日星期一 11
public double getArea() //实现抽象方法
{
return (w*h);
}
}
//测试类声明如下:
public class Interface_test
{
public static void main(String args[])
{
Circle ovel=new Circle(2.0);
System.out.println("圆 ovel的面积为 "+ovel.getArea());
Rectangle rect=new Rectangle(2.0,3.0);
System.out.println("矩形 rect的面积为 "+rect.getArea());
}
}
Java最新实用教程
2009年 7月 27日星期一 12
运行步骤:
>javac Shape2D.java
>javac Interface_test.java
>java Interface_test
运行显示结果:
圆 ovel的面积为 12.56
矩形 rect的面积为 6.0
由本例可以看出,通过接口以及实现接口的类,可以编写出跟简洁的程序代码 。
6.1.3 利用接口实现类的多重继承有时候,人们会希望一个子类同时继承自两个以上的父类,以便使用每一个父类的功能,但 java并不允许多个父类的继承,
其中的原因很简单,因 java的设计是以简单实用为导向,而利用类的多重继承将使得问题复杂化,这与 java设计的原意相违背 。
Java最新实用教程
2009年 7月 27日星期一 13
虽然 java不允许一个类有多个父类,但允许一个类可以实现多个接口,通过这种机制可以实现多重继承 。
一个类实现多个继承的方法很简单,只要将接口名放在
implements后面,接口名以逗号隔开即可 。 当然此时需要在继承的类中实现每一个接口中的所有抽象方法 。
假设对于二维图形来说,需要计算面积的同时设置图形的颜色
。 此时可以声明一个 Color接口如下 。
interface Color{
… //接口内容
}
此时例 6.4中的 Circle类或 Rectangle类都可以同时继承 Shape2D
和 Color接口,以 Circle类为例,继承过程如下:
class Circle implements Shape2D,Color{
… // Shape2D,Color接口中的方法实现及其它
}
Java最新实用教程
2009年 7月 27日星期一 14
6.1.4接口的扩展接口与一般类一样,可通过扩展 ( extends) 技术来派生出新的接口,原来的接口称为基本接口 ( base interface) 或父接口 (
super interface),派生出的接口称为派生接口 ( derived
interface) 或子接口 ( sub interface) 。 派生接口不仅可以保有父接口的成员,同时也可以加入新的成员以满足实际问题的需要 。
接口的扩展或继承也是通过关键字 extends。 但是一个接口可以继承多个接口,这点与类的继承有所不同 。 接口扩展的语法如下:
interface 子接口名 extends 父接口名 1,父接口名 2,…
{

}
例 6.5:接口扩展举例 。
Java最新实用教程
2009年 7月 27日星期一 15
已知规则的 3维图形都有 2维的底,二维图形又有共用的变量 pi
和设置颜色的行为,所以创建三维图形类如球类 Sphere或盒类
Box的过程可以这样设计:建立基本接口 Shape,二维接口
Shape2D继承接口 Shape,sphere或 Box类实现接口 Shape2D。
部分代码如下:
interface Shape //声明接口
{
double pi=3.14;
void setColor(String str);
}
interface Shape2D extends Shape
{
double getArea();
}
public class Box implements Shape2D //Box类假设为圆柱形
{
double r,h;String c;
public Box(double r,double h)
{
this.r=r;
this.h=h;
}
Java最新实用教程
2009年 7月 27日星期一 16
public double getArea() //实现接口的抽象方法
{
return (pi*r*r);
}
public void setColor(String c) //实现父接口的抽象方法
{
this.c=c;
}
public static void main(String args[])
{
Box box=new Box(2.0,5.0);
System.out.println("该盒式容器的颜色为 "+box.c);
System.out.println("\n该盒式容器的容积为 "+box.h*box.getArea());
}
}
运行结果如图 6-1。
图 6-1 接口扩展注意:虽然接口和抽象类非常相似,但有一点:抽象类可以有构造方法,接口不能有构造方法 。
Java最新实用教程
2009年 7月 27日星期一 17
6.2塑型
6.2.1塑型的概念
java支持隐式(自动)类型转换,及显式(强制)的类型转换
。在 2.2.5节中已经讲解了基本数据类型的转换,本节主要讲解对象的类型转换。类型转换也称为塑型( type-casting),对象塑型比基本数据类型的塑型要复杂得多。对象只能被塑型为:
任何一个父类类型。即任何一个子类的应用变量(或对象)都可以被当成一个父类应用变量(或对象)来对待。因为子类继承了父类的属性和行为;但反过来却并不成立。如一个圆形类 Circle肯定是一个图形类 Shape,但一个 Shape却并不一定是一个 Circle。
对象所属的类实现的一个接口 。 虽然不能用接口生成对象,但可以声明接口的引用变量,接口的引用变量可以指向任何实现了此接口的对象 。
Java最新实用教程
2009年 7月 27日星期一 18
或者回到它自己所在的类。一个对象被塑型为父类或接口后,还可以再被塑型,回到它自己所在的类。
例如,前面我们学过的人员类 Person继承自 Object类,职员类
Employee和顾客类 Customer继承自 Person,管理员 Manager类又继承自 Employee; 同时 Person类又实现了接口 Insurable。
此时,Manger类的对象可以被塑型为 Employee,Person、
Object或 Insurable。
1,隐式(自动)的类型转换对于基本数据类型,相容类型之间存储容量低的自动向存储容量高的类型转换;对于引用变量,当一个类需要被塑型成更一般的类(父类)或接口时,系统会进行自动塑型。
例如下面的操作是合法的,即可以将 Manager类型的对象直接赋给 Employee类的引用变量,系统会进行自动塑型,将
Manager对象塑型为 Employee类。
Java最新实用教程
2009年 7月 27日星期一 19
Employee emp;
Emp=new Manager();
对象也可以被塑型为对象所属类实现的接口类型,如:
Car jetta=new Car();
Insurable item=jetta;
2,显式 ( 强制 ) 得类型转换当隐式类型转换不可能时,需要进行显式的类型转换 。 例如

(int)871.34354; //结果为 87
(char)65; //结果为 ’ A’
(long)453; //结果为 453L
对于引用变量,通过强制塑型,将其还原为本来的类型 。 例如

Employee emp;
Manager man;
Emp=new Manager();
Java最新实用教程
2009年 7月 27日星期一 20
Man= (Manager) emp; //将 emp强制塑型为它本来的类型基本数据类型的塑型与对象的塑型含义是不同的 。 对于基本数据类型,塑型是将值从一种形式转换成另一种形式的过程;对于对象,塑型并不是转换,而仅仅是将对象暂时当成更一般的对象来对待,并没有改变其类型 。
6.2.2塑型的应用塑型主要应用于以下场合:
赋值转换 。 在进行赋值运算时,赋值号右边的表达式类型或对象转换为左边的类型 。
方法调用转换 。 在进行参数传递时,要将实参的类型转换为形参的类型 。
算法表达式转换 。 在进行字符串连接运算时,如果一个操作数为字符串,一个操作数为数值型,则数值型将自动转换为字符串 。
Java最新实用教程
2009年 7月 27日星期一 21
当一个类对象被塑型为其父类对象后,它提供的方法会减少。
以上一章图 5-5所示的公司管理系统类层次为例,人员类 Person
,职员类 Employee和顾客类 Customer继承自 Person,管理员类
Manager又继承自 Employee; 假设 Person类中有方法 getName(),
Employee 有方法 getEmployeeNumber(),Manager 类有方法
getSalary()。
在下面的代码中,当 Manager对象 man被塑型为 Employee之后,
会将 man暂时当成 Employee类对象,它只能接收 getName()以及
getEmployeeNumber()方法,不能接收 getSalary()方法 。 当将其塑型为本来的对象后,又能接收 getSalary()方法了 。
Manager man;
Employee emp;
man = new Manager();
man.getName();
man,getEmployeeNumber();
man,getSalary();
emp = (Employee)man; //或者使用自动塑型 emp = man;
emp,getName(); //有效
Java最新实用教程
2009年 7月 27日星期一 22
emp,getEmployeeNumber(); //有效
emp,getSalary(); //无效
Manager man1;
Man1 = (Manager)emp;
Man1.getSalary(); //有效或
((Manager)emp).getSalary(); //有效从这个例子可以看出,对象的塑型并没有改变它的类型,而是暂时将其当成更一般的类型。
当对象做为方法的参数时,经常进行自动塑型,如有对象 x中有方法 doSomething定义如下:
public void doSomthing(Employee emp) //参数为 Employee类对象
{…}
而方法的调用过程为:
Manager man = new Manager();
Java最新实用教程
2009年 7月 27日星期一 23
x.doSomething(man); //此时,man被塑型为 Employee类对象。
在 java中,很多方法对任意的对象都能工作,这些方法通常以
Object类(所有类的基类)对象作为参数。如方法 public boolean
equals(Object obj){…} 。 在这种情况下,任何类型的对象都可以作为参数传递,并自动塑型为 Object类,当然在塑型后,对象就只能使用 Object类声明的方法了。
6.2.3方法的查找如果在塑型前和塑型后的类中都提供了相同的方法,并将此方法发送给塑型后的对象,那么系统将会调用哪一个类中的方法

对于实例方法,方法调用时总是从原始类开始,沿类层次向上查找。在图 5-5所示的类层次结构,如果 Employee类及 Manager
类中都声明了计算工资的实例方法 computePay()。 在下面的代码段中,当将 Manager类对象 man塑型为 Employee类之后,
Java最新实用教程
2009年 7月 27日星期一 24
当调用它的 computePay()方法时,首先在它的本来类 Manager
中查找,如果不存在,才在其父类中进行查找 。
Manager man = new Manager();
Employee emp1 = new Employee();
Employee emp2 = (Employee)man;
emp1.computePay(); //调用 Employee类中的 computePay()方法
man,computePay(); //调用 Manager类中的 computePay()方法
emp2.computePay(); //调用 Manager类中的 computePay()方法对于类方法,查找在编译时进行,所以总是在变量声明时所属的类中进行查找。如上面的 computePay()方法为类方法,此时程序段中的方法调用如下:
Manager man = new Manager();
Employee emp1 = new Employee();
Employee emp2 = (Employee)man;
emp1.computePay(); //调用 Employee类中的 computePay()方法
man,computePay(); //调用 Manager类中的 computePay()方法
emp2.computePay(); //调用 Employee类中的 computePay()方法注:因为 emp2是由 Employee类所声明的,所以在 Employee类中查找方法所需要的静态方法。
Java最新实用教程
2009年 7月 27日星期一 25
6.3 多态的概念
6.3.1多态的目的将一条消息发给对象时,可能不知道运行时对象的具体类型会是什么,但采取的行动同样是正确的,这种情况就叫做“多态性”( Polymorphism)。 多态性是指不同类型的对象可以响应相同的消息。
其实已经接触了多态性( Polymorphism) 的情形,例如所有的
Object类的对象都响应 toString()方法,所有的 BankAcount类都响应 deposit()方法。由于所有的对象都可以被塑性为相同的类型,因此可以响应相同的消息,从而使代码变得简单且容易理解。
通过塑型可以将一个对象当做它的基类对象对待,这种能力是十分重要的。从相同的基类派生出来的多个类可被当作同一种类型对待,可对这些不同的类型进行同样的处理。而且由于
Java最新实用教程
2009年 7月 27日星期一 26
多态性,这些不同派生类对象响应同一方法的行为是有所差别的,这也正是这些相似的类之间彼此区别的不同之处。
考虑如图 6-2所示的类层次,如果希望能够画出任意子类型对象的形状,可以在 Shape类中声明一个绘图方法。
图 6-2 几何图形类由于在编写程序时可能无法预见运行时将处理哪个子类型的对象,所以可能会像下面这样写代码:
if (aShape instanceof Circle)
aShape.drawCircle();
if (aShape instanceof Triangle)
Java最新实用教程
2009年 7月 27日星期一 27
aShape.drawTriangle();
if (aShape instanceof Rectangle)
aShape.drawRectangle();
这段代码的目的是,对于不同的实际对象,采用不同的画法。
然而更好的方法是,在每一个子类中都声明同名的 draw()方法
,如图 6-3。
图 6-3 改进后的几何图形类
Java最新实用教程
2009年 7月 27日星期一 28
此时,上述代码可以修改为:
if (aShape instanceof Circle)
aShape.draw ();
if (aShape instanceof Triangle)
aShape.draw ();
if (aShape instanceof Rectangle)
aShape.draw ();
显然,这里的 if语句是多余的,整段代码用一条语句即可:
aShape.draw();
例如在具体使用时可以写成:
Shape s = new Circle;
s.draw();
因为按照继承关系,Circle属于 Shape的一种,系统会执行自动塑型。而且当调用方法 draw时,实际调用的是 Circle.draw(),因为在程序运行时才进行绑定。下节介绍绑定的概念。
Java最新实用教程
2009年 7月 27日星期一 29
6.3.2绑定的概念将一个方法调用同一个方法主体连接到一起就称为,绑定,(
binding) 。 根据绑定的时期不同,可将绑定分为,早期绑定,
及,后期绑定,两种 。
若在程序运行以前执行绑定 ( 由编译器和链接程序,如果有的话 ),就叫做,早期绑定,。 如果绑定在运行期间进行,就称为,后期绑定,。 后期绑定也叫做,动态绑定,或,运行期绑定,。 若一种语言实现了后期绑定,同时必须提供一些机制,
可在运行期间进行判断对象的类型,并分别调用适当的方法 。
例 6.6:动态绑定举例 。
以图 6-3中的类层次为例,代码可编写如下:
//基类 Shape建立了一个通用接口,也就是说,所有 ( 几何 ) 形状都可以描绘和删除
class Shape
{
void draw()
{}
void erase()
{}
}
Java最新实用教程
2009年 7月 27日星期一 30
//派生类覆盖了 draw方法,为每种特殊类型的几何形状都提供了独一无二的行为
class Circle extends Shape
{
void draw()
{
System.out.println("圆形 ");
}
void erase()
{
System.out.println("取消圆 ");
}
}
class Square extends Shape
{
void draw()
{
System.out.println("矩形 ");
}
void erase()
{
System.out.println("取消矩形 ");
}
}
class Triangle extends Shape
Java最新实用教程
2009年 7月 27日星期一 31
{
void draw()
{
System.out.println("三角形 ");
}
void erase()
{
System.out.println("取消三角形 ");
}
}
//下面主类测试动态绑定
public class Binding_test
{
public static void main(String args[])
{
Shape[] s = new Shape[9];
int n;
for(int i = 0;i<s.length;i++)
{
n=(int)(Math.random()*3);
switch(n)
{
case 0,s[i] = new Circle();break;
case 1,s[i] = new Square();break;
Java最新实用教程
2009年 7月 27日星期一 32
case 2,s[i] = new Triangle();break;
}
}
for(int i = 0;i < s.length;i++)
s[i].draw();
}
}
运行结果如图 6-4。
图 6-4 动态绑定在主方法的循环体中,每次根据运行时产生的随机数生成指向一个 Circle,Square或者 Triangle的引用,而在编译时是无法知道 s数组元素的具体类型到底是什么,根据声明只知道会获得一个单纯的 Shape引用。
当然,由于几何形状是每次随即选择的,所以每次运行都可
Java最新实用教程
2009年 7月 27日星期一 33
能有不同的结果。
在上例中,如果将 Shape改为如下的抽象类,看看是否能得到正确的结果。
abstract class Shape
{
abstract void draw();
abstract void erase();
}
6.4 多态的应用利用向上塑形技术,一个父类的引用变量可以指向不同的子类对象,而利用动态绑定技术,可以在运行时根据父类引用变量所指对象的实际类型执行相应的子类方法,从而实现多态性。
多态性使得编程简单,容易理解,且使程序具有很好的“扩展性”。
在实际应用中,经常会利用多态性进行二次分发( double
Java最新实用教程
2009年 7月 27日星期一 34
dispatching),具体简下面的例子。
例 6.7:多态的应用举例。
在此例中声明一个抽象类 Driver及两个子类 FemaleDriver及
MaleDriver。 在 Driver类中声明了抽象方法 drives,在两个子类中对这个方法进行了重写。代码如下:
abstract class Driver
{
public abstract void drives();
}
class FemaleDriver extends Driver
{
public void drives()
{
System.out.println("女司机开车 ");
}
}
class MaleDriver extends Driver
{
public void drives()
{
System.out.println("男司机开车 ");
}
Java最新实用教程
2009年 7月 27日星期一 35
}
public class Polymorphism_test
{
public static void main(String args[])
{
Driver a = new FemaleDriver();
Driver b = new MaleDriver();
a.drives();
b.drives();
} }
运行显示结果如下:
一个女司机开汽车一个男司机开汽车试想有不同种类的车( vehicle),如公共汽车( bus) 及小汽车( car),由此可以声明一个抽象类 Vehicle及两个子类 Bus和
Car。 并对前面的 Drives方法进行改进,使其接收一个 Vehicle类的参数,当不同类型的交通工具被传送到此方法时,可以输出具体的交通工具。测试代码可改写如下:
Java最新实用教程
2009年 7月 27日星期一 36
public class Polymorphism_test
{
public static void main(String args[])
{
Driver a = new FemaleDriver();
Driver b = new MaleDriver();
Vehicle x = new Car();
Vehicle y = new Bus();
a.drives(x);
b.drives(y);
}
}
并希望此时程序测试程序有如下效果:输出内容中司机的类别和所开的车的类型对应信息。
车类( Vehicle) 及其子类( Bus,Car) 声明如下:
abstract class Vehicle
{
private String type;
public Vehicle(){}
public Vehicle(String s)
{
type = s;
}
public abstract void driveByFemaleDriver(Driver who);
Java最新实用教程
2009年 7月 27日星期一 37
public class Bus extends Vehicle
{
public Bus(){}
public void driveByFemaleDriver(Driver who)
{
System.out.println("女司机开公共汽车 ");
}
public void driveByMaleDriver(Driver who)
{
System.out.println("男司机开公共汽车 ");
}
}
class Car extends Vehicle
{
public Car(){}
public void driveByFemaleDriver(Driver who)
{
System.out.println("女司机开小汽车 ");
}
public void driveByMaleDriver(Driver who)
{
System.out.println("男司机开小汽车 ");
}
}
Java最新实用教程
2009年 7月 27日星期一 38
为了达到既定的输出效果,还要对 FemaleDriver和 MaleDriver类中的 drives方法进行改进,在 drives方法的定义体中不直接输出结果,而是调用 Bus及 Car类中的相应方法。该今后的代码如下:
abstract class Driver{
public abstract void drives();
}
class FemaleDriver extends Driver{
public void drives() {
v.driveByFemaleDriver(this);
}
}
class MaleDriver extends Driver{
public void drives() {
v.driveByMaleDriver(this);
} }
这种技术称为二次分发( double dispatching),即对输出消息的请求被分发两次:首先根据驾驶员的类型发送给一个类,之后根据交通工具的类型发送给另一个类。
Java最新实用教程
2009年 7月 27日星期一 39
6.5 构造方法与多态构造方法与其它方法是有区别的,尽管构造方法并不具有多态性,但仍然非常有必要理解构造方法如何在复杂的分级结构中随同多态性一同使用的情况 。
6.5.1构造方法的调用顺序基类的构造方法肯定会在派生类的构造方法中被调用,而且逐渐向上链接,使每个基类使用的构造方法都能得到调用。一个派生类只能访问它自己的成员,不能访问具有 private属性的基类成员。只有基类的构造方法在初始化自己的元素时才知道正确的方法以及拥有适当的权限。所以,必须使所有构造方法都得到调用,否则整个对象的构建就可能不正确。前面 5.1.4中提到,若没有在派生类构造方法中明确指定对基类构造方法的调用,则系统会自动调用基类的默认构造方法(无参数)。如果不存在默认构造方法,编译器就会报告一个错误。
Java最新实用教程
2009年 7月 27日星期一 40
例 6.8:构造方法的调用顺序举例 1,分析下面程序的功能。
class Point
{
private double xCoordinate;
private double yCoordinate;
public Point(){}
public Point(double x,double y)
{
xCoordinate=x;
yCoordinate=y;
}
public String toString()
{
return "("+Double.toString(xCoordinate)+","+Double.toString(yCoordinate)+")";
}
//下面还可以有其它方法
}
public class Ball
{
private Point center; //中心点
private double radius; //半径
private String colour; //颜色
public Ball(){}
Java最新实用教程
2009年 7月 27日星期一 41
public Ball(double x,double y,double r)//具有中心点及半径的构造方法
{
center=new Point( x,y); //调用类 Point的构造方法
radius=r;
}
public Ball(double x,double y,double r,String c)//具有中心点,半径及颜色的方法
{
this(x,y,r); //调用 Ball(doublb x,double y,double r)
colour=c;
}
public String toString()
{
return"一个球中心在 "+center.toString()+",半径等于 "+Double.toString(radius)+"颜色为 "+colour;
}
}
由于在类 Ball中不能直接存取类 Point中的 xCoordinate及
yCoordinate属性值,Ball中的 toString方法调用 Point类中的
toString方法输出中心点的值。定义下面的 MovingBall类。
Java最新实用教程
2009年 7月 27日星期一 42
public class MovingBall extends Ball
{
private double speed;
public MovingBall(){}
public MovingBall(double x,double y,double r,String c,double s)
{
super(x,y,r,c); //调用父类 Ball的构造方法 Ball(double x,double y,double r,String c)
speed=s;
}
public String toString()
{
return super.toString()+",速度为 "+Double.toString(speed);
}
}
在 MovingBall类的 toString方法中,super.toString调用父类 Ball
的 toString方法输出 Ball中声明的属性值,由于在子类中不能直接存取父类中声明的私有数据成员,这是显示父类数据成员的惟一方法。public class Ball_test{
public static void main(String args[]) {
MovingBall ball = new MovingBall(10,20,40,"green",25);
System.out.println(ball);
}
}
Java最新实用教程
2009年 7月 27日星期一 43
分别编译主类 Ball,MovingBall,Ball_test后,运行 Ball_test程序的结果如下:
一个球中心在( 10.0,20.0),半径等于 40.0颜色为 green,速度为 25.0
在上面的代码中,构造方法调用顺序为:
MovingBall(double x,double y,double r,String c,double s)

Ball(double x,double y,double r,String c)

Ball(doublb x,double y,double r)

Point(double x,double y)
下面的例子能更清楚地看到构造方法的调用顺序。
例 6.9:构造方法的调用顺序典型示例 2。
Java最新实用教程
2009年 7月 27日星期一 44
class Meal
{
Meal()
{System.out.println("Meal()");}
}
class Bread
{
Bread()
{System.out.println("Bread()");}
}
class Cheese
{
Cheese()
{System.out.println("Cheese()");}
}
class Lettuce
{
Lettuce()
{System.out.println("Lettuce()");}
}
class Lunch extends Meal
{
Lunch()
{System.out.println("Lunch()");}
}
Java最新实用教程
2009年 7月 27日星期一 45
class PortableLunch extends Lunch
{
PortableLunch()
{System.out.println("PortableLunch()");}
}
public class Sandwich extends PortableLunch
{
Bread b=new Bread();
Cheese c=new Cheese();
Lettuce l=new Lettuce();
Sandwich()
{System.out.println("Sandwich()");}
public static void main(String[] args)
{new Sandwich();}
}
这个例子在其他类的外部创建了一个复杂的类,而且每个类都有一个自己的构造方法。其中最重要的类是 Sandwich,它反映出了三个级别的继承以及三个成员对象。在 main()里创建了一个 Sandwich对象后,输出结果如图 6-5。
从上面的例子可以看出创建一个派生类,构造方法的调用次序为:基类构造方法;成员变量初始化;派生类构造方法。
图 6-5 构造方法的调用顺序
Java最新实用教程
2009年 7月 27日星期一 46
这意味着对于一个复杂的对象,构造方法的调用遵循下面的顺序:
( 1)调用基类的构造方法。这个步骤会不断重复下去,首先得到构建的是分级结构根部,然后是下一个派生类,等等。直到抵达最深一层的派生类。
( 2)按声明顺序调用成员初始化模块。
( 3)调用派生构造方法。
构造方法的调用顺序是十分重要的。进行继承时,知道关于基类的一切,并且能访问基类的任何 public和 protected成员。这意味着当在构造派生类的时候,必须能假定基类的所有成员都是有效的。在构造方法内部,必须保证使用的所有成员都已构造
。为达到这个要求,惟一的办法就是首先调用基类构造方法,
然后在进入派生类构造方法以后,在基类能够访问的所有成员都已得到初始化。
Java最新实用教程
2009年 7月 27日星期一 47
6.5.2构造方法中的多态方法若在一个构造方法的内部调用准备构造的那个对象的一个动态绑定方法,那么会出现什么情况呢?
若在一个构造方法内部调用一个动态绑定的方法,可能造成一些难于发现的程序错误。
从概念上讲,构造方法的职责是让对象实际进入存在状态。在任何构造方法内部,整个对象可能只是得到部分初始化,但却不知道哪些类已经继承。然而,一个动态绑定的方法调用却会调用位于派生类里的一个方法。如果在构造方法内部做这件事情,那么对于调用的方法,它要操纵的成员可能尚未得到正确的初始化,这显然不是人们所希望的,而且可能造成一些难于发现的程序错误。
例 6.10:构造方法中的多态方法举例:
下面的代码中,在 Glyph中声明一个抽象方法,并在构造方法内部调用了该抽象方法。
Java最新实用教程
2009年 7月 27日星期一 48
abstract class Glyph
{
abstract void draw();
Glyph()
{
System.out.println("Glyph() before drow()");
draw();
System.out.println("Glyph() before drow()");
}
}
class RoundGlyph extends Glyph
{
int radius=1;
RoundGlyph(int r)
{
radius=r;
System.out.println("RoundGlyph.RoundGlyph(),radius="+radius);
}
void draw()
{
System.out.println("RoundGlyph.draw(),radius="+radius);
}
}
Java最新实用教程
2009年 7月 27日星期一 49
public class PolyConstructors
{
public static void main(String[] args)
{
new RoundGlyph(5);
}
}
运行结果如图 6-6。 图 6-6 构造方法与多态在 Glyph中,draw()方法是抽象方法,在子类 RoundGlyph中对此方法进行了覆盖,Glyph的构造方法调用了这个方法。从运行的结果可以看到:当 Glyph的构造方法调用 draw()时,radius的值甚至不是默认的处世值 1,而是 0。
在定义构造方法时,遵循以下原则能够有效地避免错误:
用尽可能少的动作把对象的状态设置好。
如果可以避免,不要调用任何方法。
在构造方法内惟一能够安全调用的是在基类中具有 final属性的那些方法(也适用于 private方法,他们自动具有 final属性)。
这些方法不能被覆盖,所以不会出现上述潜在问题。
Java最新实用教程
2009年 7月 27日星期一 50
6.6 内部类在 Java 1.1版本以后,允许将一个类声明置入另一个类声明中,
称这种类为“内部类”。在内部类中可以访问它的外部类的所有数据成员和方法成员。内部类对我们非常有用,利用它可对那些逻辑上相互联系的类进行分组;可控制一个类在另一个类里的“可见性”,对于同一个包中的其他类来说,内部类能够隐藏起来;使用内部类可以非常方便地编写事件驱动程序。
可使用两种方法声明内部类:
( 1)声明命名的内部类。可以在类中(甚至方法中)声明内部类,并在类的内部多次使用。
( 2)声明匿名内部类。可以在 new关键字后声明内部类,并立即创建一个对象。
假设外层类名为 Myclass,则该类的内部类名为 Myclass $
c1.class(c1为命名的内部类名 ),或 Myclass $ 1.class( 表示类中声明的第一个匿名内部类)。
Java最新实用教程
2009年 7月 27日星期一 51
6.7 本章小结本章介绍了 java语言的接口以及多态机制 。
接口是面向对象的一个重要机制 。 使用借口可以实现多继承的效果,同时免除 C++中的多继承那样的复杂性 。 接口中的所有方法都是抽象的,这些抽象法由实现这一接口的不同类来具体完成,这样可以最大限度地利用动态绑定,隐藏实现细节 。 使用接口还可以在看起来不相干的类之间定义共同行为 。
多态性是指不同类型的对象可以响应相同的消息 。 由于 Java采用的是动态绑定,因而为多态提供了很好的支持 。 利用多态性
,并通过塑型可将一个对象当作它的基类对象对待,对这些不同的类型进行同样的处理 。 而且这些不同派生类对象响应同一方法时的行为是有所差别的 。
在构造方法中尽可能避免调用具有多态性的其他方法,以免在对象还没有完全构造以前调用其方法成员 。 也就是说,构造
Java最新实用教程
2009年 7月 27日星期一 52
方法要尽可能简单,其目的仅仅是给对象进行初始化,不要在构造方法中编写与初始化无关的其他功能 。
本章最后简要介绍了内部类的概念 。