Java的接口
[b]4.4.1 引进接口的目的[/b]
它的引进是为了实现多继承,同时免除C++中的多继承那样的复杂性。前面讲过,抽象类中包含一个或多个抽象方法,该抽象类的子类必须实现这些抽象方法。接口类似于抽象类,只是接口中的所有方法都是抽象的。这些方法由实现这一接口的不同类具体完成。在使用中,接口类的变量可用来代表任何实现了该接口的类的对象。这就相当于把类根据其实现的功能来分别代表,而不必顾虑它所在的类继承层次。这样可以最大限度地利用动态绑定,隐藏实现细节。接口还可以用来实现不同类之间的常量共享。
为了说明接口的作用,我们不妨假设有一系列的图形类,其中一部分在图形中加入了文字,成为可编辑的,它们应当支持最普遍的编辑功能:
cut,copy,paste和changeFont
将这些方法的原型统一组合在一个EditShape接口中,就可以保证方法名的规范统一和使用的方便。我们画出这个假想的类和接口的继承关系图,可以更直观地了解。
Object
↓
Shape
┌────────────┼─────────────┐
↓ ↓ ↓
Circle Rectangle Triangle
↙ ↘ ↙ ↘ ↙ ↘
PaintCircle TextCircle PaintRectangle TextRectangle PaintTriangle TextTrangle
↑ ↑ ↑
└───────────┼───────────────┘
EditShape
图 Shape 和 EditShape
以图中类Circle的两个子类为例。类PaintCircle未实现EditShape接口,不支持上述编辑功能。而类TextCircle既是Cricle的子类,又实现了EditShape接口,因而不但具有Circle类的图形牲,又支持EditShape定义的编辑功能。而在TextCircle,TextRectangle和TextTriangle中,支持这些编辑功能的方法是同名同参的(与EditShape的定义一致),这又提供了使用上的方便。
4.4.2 接口的声明和使用
Java的接口类似于抽象类,因而它的声明也和抽象类类似,只定义了类中方法的原型,而没有直接定义方法的内容。它的声明格式为:
[接口修饰符] interface 接口名 [extends 父类名]
{...//方法的原型定义或静态常数}
接口修饰符可以是public或abstract,其中abstract缺省时也有效。public的含义与类修饰符是一致的。要注意的是一个编译单元,即一个.java文件中最多只能有一个public的类或接口,当存在public的类或接口时,编译单必须与这个类或接口同名。
被声明的变量总是被视为static和final的,因而必须在声明时给定初值。被声明的方法总是abstract的,abstarct缺省也有效。与抽象类一样,接口不需要构造方法。接口的继承与为是一样的,当然一个接口的父类也必须是接口。下面是一个接口的例子:
interface EditShape{
void cut();
void copy();
void paste();
void changeFont();
}
在使用时,为了将某个接口实现,必须使用关键字implements。格式是这样的:
[类修饰符] class 类名 [extends 父类名] [implements 接口名表]
其中,接口名表可包括多个接口名称,各接口间用逗号分隔。“实现(implements)“了一个接口的非抽象类必须写出实现接口中定义的方法的具体代码,同时可以读取使用接口中定义的任何变量。
例4.10 接口的实现
class TextCircle extends Circle implements EditShape
{...
void cut() {...//具体实现代码}
void copy() {...//具体实现代码}
void paste() {...//具体实现代码}
void changeFont {...//具体实现代码}
...
}
4.4.3 多继承
在Java中,类之间只允许单继承,但我们可以把一个类实现的接口类也看作这个类的父类。类从它实现的接口那里“继承”了变量和方法,尽管这些变量是静态常量,这些方法是未实现的原型。如果一个类实现的接口类不止一个,那么所有这些接口类都被视为它的“父类”。这样,实现了一个或多个接口的类就相当于是从两个(加上该类原有意义上的父类)或两个以上的类派生出来的。Java的多继承正是建立在这种意义之上。通过接口的继承,相当于只选择了一部分需要的特征汇集在接口中由不同的类共享并继承下去,而不必通过父子类间的继承关系将所有的方法和变量全部传递给子类。所以我们又可以把Java的这种多继承称为“有选择的多继承”。这种多继承与一般的多继承相比,更为精简,复杂度也随之大大降低。
在多继承时,一个子类可能会从它的不同父类那里继承到同名的不同变量或方法,这往往会引起两义性问题,即不知道子类中这样的变量或方法究竟是继承了哪一个父类的版本,在Java中,为了防止出现这样的两义性问题,规定不允许一个子类继承的父类和实现的接口类中定义同名的不同变量,否则编译该子类时将出错,无法通过。而对于方法,由于接口类中定义的总是abstract的方法原型,而没有实际代码,所以不会出现类似的两义性问题。相反,常会存在这样的情况:当接口类中要求实现的方法子类没有实现,而子类的父类中定义有同名方法时,编译器将子类从父继承的该方法视为对接口的的实现。这样的继承和实现都被认为是合法的。
4.5 实现了接口的邮件类例子
这一节我们将4.3节邮件类的例子加以改进和扩展,加入有关接口的内容,以说明接口和多继承的概念。
首先定义一个名为MailPost的接口,其中没有定义变量,而是给出两个有关邮寄方法原型。
calPrice()计算邮费并以浮点数形式返回;
post()完成邮寄。
例4.11 接口MailPost。
//MailPost.java
package ch4package;
public interface MailPost{
public float claPrice();
public void post();
}
接下来在包裹Parcel和汇款Remittance的基础上分别派生出可邮寄的包裹和汇款:PostParcel和PostRemit两个子类。
例4.12 子类PostParcel和PostRemit。
---------------------------------
//PostParcel.java
package ch4package;
import java.lang.*;
public class PostParcel extends Parcel implements MailPost{
protected int postage;
protected boolean postable;
protected boolean posted;
PostParcel(Ttring address1,String address2,int w,intp){
//构造方法
super(address1,address2,w);
postage=p;
postable=false;
posted=false;
}
public float calPrice(){//计算邮资
return((float)0.05*weight);
}
public void post(){//邮寄包裹
float price=calPrice();
postable=(price
[b]4.4.1 引进接口的目的[/b]
它的引进是为了实现多继承,同时免除C++中的多继承那样的复杂性。前面讲过,抽象类中包含一个或多个抽象方法,该抽象类的子类必须实现这些抽象方法。接口类似于抽象类,只是接口中的所有方法都是抽象的。这些方法由实现这一接口的不同类具体完成。
为了说明接口的作用,我们不妨假设有一系列的图形类,其中一部分在图形中加入了文字,成为可编辑的,它们应当支持最普遍的编辑功能:
cut,copy,paste和changeFont
将这些方法的原型统一组合在一个EditShape接口中,就可以保证方法名的规范统一和使用的方便。我们画出这个假想的类和接口的继承关系图,可以更直观地了解。
Object
↓
Shape
┌────────────┼─────────────┐
↓ ↓ ↓
Circle Rectangle Triangle
↙ ↘ ↙ ↘ ↙ ↘
PaintCircle TextCircle PaintRectangle TextRectangle PaintTriangle TextTrangle
↑ ↑ ↑
└───────────┼───────────────┘
EditShape
图 Shape 和 EditShape
以图中类Circle的两个子类为例。类PaintCircle未实现EditShape接口,不支持上述编辑功能。而类TextCircle既是Cricle的子类,又实现了EditShape接口,因而不但具有Circle类的图形牲,又支持EditShape定义的编辑功能。而在TextCircle,TextRectangle和TextTriangle中,支持这些编辑功能的方法是同名同参的(与EditShape的定义一致),这又提供了使用上的方便。
4.4.2 接口的声明和使用
Java的接口类似于抽象类,因而它的声明也和抽象类类似,只定义了类中方法的原型,而没有直接定义方法的内容。它的声明格式为:
[接口修饰符] interface 接口名 [extends 父类名]
{...//方法的原型定义或静态常数}
接口修饰符可以是public或abstract,其中abstract缺省时也有效。public的含义与类修饰符是一致的。要注意的是一个编译单元,即一个.java文件中最多只能有一个public的类或接口,当存在public的类或接口时,编译单必须与这个类或接口同名。
被声明的变量总是被视为static和final的,因而必须在声明时给定初值。被声明的方法总是abstract的,abstarct缺省也有效。与抽象类一样,接口不需要构造方法。接口的继承与为是一样的,当然一个接口的父类也必须是接口。下面是一个接口的例子:
interface EditShape{
void cut();
void copy();
void paste();
void changeFont();
}
在使用时,为了将某个接口实现,必须使用关键字implements。格式是这样的:
[类修饰符] class 类名 [extends 父类名] [implements 接口名表]
其中,接口名表可包括多个接口名称,各接口间用逗号分隔。“实现(implements)“了一个接口的非抽象类必须写出实现接口中定义的方法的具体代码,同时可以读取使用接口中定义的任何变量。
例4.10 接口的实现
class TextCircle extends Circle implements EditShape
{...
void cut() {...//具体实现代码}
void copy() {...//具体实现代码}
void paste() {...//具体实现代码}
void changeFont {...//具体实现代码}
...
}
4.4.3 多继承
在Java中,类之间只允许单继承,但我们可以把一个类实现的接口类也看作这个类的父类。类从它实现的接口那里“继承”了变量和方法,尽管这些变量是静态常量,这些方法是未实现的原型。如果一个类实现的接口类不止一个,那么所有这些接口类都被视为它的“父类”。这样,实现了一个或多个接口的类就相当于是从两个(加上该类原有意义上的父类)或两个以上的类派生出来的。Java的多继承正是建立在这种意义之上。通过接口的继承,相当于只选择了一部分需要的特征汇集在接口中由不同的类共享并继承下去,而不必通过父子类间的继承关系将所有的方法和变量全部传递给子类。所以我们又可以把Java的这种多继承称为“有选择的多继承”。这种多继承与一般的多继承相比,更为精简,复杂度也随之大大降低。
在多继承时,一个子类可能会从它的不同父类那里继承到同名的不同变量或方法,这往往会引起两义性问题,即不知道子类中这样的变量或方法究竟是继承了哪一个父类的版本,在Java中,为了防止出现这样的两义性问题,规定不允许一个子类继承的父类和实现的接口类中定义同名的不同变量,否则编译该子类时将出错,无法通过。而对于方法,由于接口类中定义的总是abstract的方法原型,而没有实际代码,所以不会出现类似的两义性问题。相反,常会存在这样的情况:当接口类中要求实现的方法子类没有实现,而子类的父类中定义有同名方法时,编译器将子类从父继承的该方法视为对接口的的实现。这样的继承和实现都被认为是合法的。
4.5 实现了接口的邮件类例子
这一节我们将4.3节邮件类的例子加以改进和扩展,加入有关接口的内容,以说明接口和多继承的概念。
首先定义一个名为MailPost的接口,其中没有定义变量,而是给出两个有关邮寄方法原型。
calPrice()计算邮费并以浮点数形式返回;
post()完成邮寄。
例4.11 接口MailPost。
//MailPost.java
package ch4package;
public interface MailPost{
public float claPrice();
public void post();
}
接下来在包裹Parcel和汇款Remittance的基础上分别派生出可邮寄的包裹和汇款:PostParcel和PostRemit两个子类。
例4.12 子类PostParcel和PostRemit。
---------------------------------
//PostParcel.java
package ch4package;
import java.lang.*;
public class PostParcel extends Parcel implements MailPost{
protected int postage;
protected boolean postable;
protected boolean posted;
PostParcel(Ttring address1,String address2,int w,intp){
//构造方法
super(address1,address2,w);
postage=p;
postable=false;
posted=false;
}
public float calPrice(){//计算邮资
return((float)0.05*weight);
}
public void post(){//邮寄包裹
float price=calPrice();
postable=(price