Interfaces 接口
在软件工程中,由一份“契约”规定来自不同开发小组的软件之间如何相互作用是非常常见的。每个小组都可以在不知道其他小组代码的前提下独立开发自己的代码。Java中的interface就是这样的一份“契约”
举个例子,假设在未来社会有一种智能汽车,它可以在自动运载旅客而不需要人工操作。汽车生产商开发了软件(当然是用Java)来控制这种汽车停止,发动,加速,左转等等。电子导航仪器生产商负责开发接受GPS位置数据和交通状况无线传输的电脑系统,并且应用这些信息来驾驶汽车。
汽车生产商必须公布工业标准interface,该interface需详细解释哪些methods可以用于控制汽车运动(该标准适用于任何汽车,任何生产商)。导航系统生产商就可以应用这个interface所介绍的各种methods来控制汽车。任何一个工业厂商都不需了解其他厂商是如何实现他们的软件的。事实上,只要大家都严格遵守所公布的interface,每个厂商对其自己的软件都有高度所有权,并且保有随时修改的权利。
在java中的interface
在java编程语言里,一个interface是引用类型(reference),它与class相似,因此只能包含常量(constants),方法签名(method signatures)和嵌套类型(nested types)。Interface不得含有方法的具体代码(method body)。 Interface 不可被实例化(instantiated),只能被其它class实现(implemented)或者被其它interface继承。
定义一个interface与创建一个新类类似:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
public
interface
OperateCar {
// constant declarations, if any
// method signatures
// An enum with values RIGHT, LEFT
int
turn(Direction direction,
double
radius,
double
startSpeed,
double
endSpeed);
int
changeLanes(Direction direction,
double
startSpeed,
double
endSpeed);
int
signalTurn(Direction direction,
boolean
signalOn);
int
getRadarFront(
double
distanceToCar,
double
speedOfCar);
int
getRadarRear(
double
distanceToCar,
double
speedOfCar);
……
// more method signatures
}
|
如想使用上面这个interface,你需要写一个class来实现它。当一个可被实例化的class实现某个接口时,它需要提供所有该interface所声明的所有方法(methods)的具体代码。
在上面的自动化汽车的例子中,汽车生产商即是接口实现者。由雪佛兰公司的实现方法当然不同于本田公司的方法,但是它们都遵循同一个接口。 导航系统生产商是这个接口的使用者,他们的系统将根据汽车方位的GPS数据,数字化地图和交通情况来驾驶汽车。因此,这个导航系统将会涉及以下的方法(methods): 转弯,换车道,刹车,加速等等。
API 接口
自动化汽车的例子展示了interface在工业标准应用程序接口(API, Application Programming Interface)中的应用。在商业软件中,API也很常见。 通常来说, 一个公司发售的软件包中会含有其他公司希望应用在自己的产品中的复杂方法(methods)。比如一个包含了数字图形处理方法的软件包就可以出售给开发终端客户图像软件的公司。购买后,该公司就可以应用interface所定义的方法。当图像处理公司向所有客户公开它的API的同时,这些API的实现方法是高度保密的。事实上,只要保留住原始的interface不被改变,这些API的实现方法很可能在将来被重写。
Interfaces 和多重继承
在java编程语言里,interface还有另外一个重要作用。 尽管Interface是与类一起使用的,但它并不是类的层次结构的一部分。java编程语言不支持多重继承,但是interface提供了替代方案。
在java中,一个类只能继承于单一的类,但是它可以实现多个接口。因此,对象可以有多重类型:属于它自身类的类型,和属于它所继承的所有接口的类型。这意味着,如果声明一个变量是某个接口类型,这个变量可以指代任何实现该接口的类的实例。这部分会在“使用接口类型”中详细讨论。
定义一个interface
一个接口的定义是由 修饰词(modifiers),关键词 interface, 接口名称,由逗号分隔开的父接口(parent interfaces),和接口实体(interface body)。
例子如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
public
interface
GroupedInterface
extends
Interface1, Interface2, Interface3 {
// constant declarations
// base of natural logarithms
double
E =
2.718282
;
// method signatures
void
doSomething (
int
i,
double
x);
int
doSomethingElse(String s);
}
|
Public规定了这个接口可以被任何包中的任何类所使用。如果你声明这个接口是public的,它只能被同一个包里的类所使用。
一个接口可以继承其它接口,就像一个类能后继承其它类一样。但是类只能继承一个父类,而接口却可以继承任何数目的接口。
接口实体(interface body)
接口实体中含有它所包含的所有方法的声明。每个声明都以引号为结束,因为接口不用实现它所声明的方法。接口中所有的方法都默认是public的,因此修饰词public可以被省略。
接口还可以声明常量。同样的,常量的修饰词public, static和final可以被省略。
接口的实现
为了声明某个类实现了某个接口,你需要在类的声明中使用implements。你的类可以实现多个接口,所以implements关键词后面可以跟随多个由逗号分隔的接口名称。为了方便,implements关键词多跟在extends关键词的后面。
一个接口实例—Relatable
Relatable是一个用来比较两个对象大小的接口。
1
2
3
4
5
6
7
8
9
|
public
interface
Relatable {
// this (object calling isLargerThan)
// and other must be instances of
// the same class returns 1, 0, -1
// if this is greater // than, equal
// to, or less than other
public
int
isLargerThan(Relatable other);
}
|
如果你想比较两个相似的对象的大小,不管该对象属于什么类,这个类需要实现Relatable接口。
只要有办法可以比较对象的相对大小,任何类都可以实现Relatable接口。对字符串来说,可以比较字符数;对书来说,可以比较页数;对学生来说,可以比较体重。对平面几何对象来说,比较面积是很好的选择;对三维对象来说,就需要比较体积了。所有以上的类都能实现int isLargerThan()方法。
如果你知道某个类实现了Relatable接口,你可以比较从这个类实例化的对象了。
Relatable接口的实现
下面是一个三角形类,它实现了Relatable接口。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
public
class
RectanglePlus
implements
Relatable {
public
int
width =
0
;
public
int
height =
0
;
public
Point origin;
// four constructors
public
RectanglePlus() {
origin =
new
Point(
0
,
0
);
}
public
RectanglePlus(Point p) {
origin = p;
}
public
RectanglePlus(
int
w,
int
h) {
origin =
new
Point(
0
,
0
);
width = w;
height = h;
}
public
RectanglePlus(Point p,
int
w,
int
h) {
origin = p;
width = w;
height = h;
}
// a method for moving the rectangle
public
void
move(
int
x,
int
y) {
origin.x = x;
origin.y = y;
}
// a method for computing
// the area of the rectangle
public
int
getArea() {
return
width * height;
}
// a method required to implement
// the Relatable interface
public
int
isLargerThan(Relatable other) {
RectanglePlus otherRect
= (RectanglePlus)other;
if
(
this
.getArea() < otherRect.getArea())
return
-
1
;
else
if
(
this
.getArea() > otherRect.getArea())
return
1
;
else
return
0
;
}
}
|
使用接口类型
在你定义一个新的接口时,你其实在定义一个新的引用类型。在你能使用数据类型名称的地方,都可以使用接口名称。如果你定义了一个类型为接口的引用变量,该变量能指向的对象所在的类必须实现了该接口。
下例是一个在一对对象中返回较大对象的方法:
1
2
3
4
5
6
7
8
|
public
Object findLargest(Object object1, Object object2) {
Relatable obj1 = (Relatable)object1;
Relatable obj2 = (Relatable)object2;
if
((obj1).isLargerThan(obj2) >
0
)
return
object1;
else
return
object2;
}
|
通过把数据类型object1转换成Relatable,对象obj1可以调用isLargerThan方法。
同理,只要是实现了Relatable的类,也可以使用下面的方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
public
Object findSmallest(Object object1, Object object2) {
Relatable obj1 = (Relatable)object1;
Relatable obj2 = (Relatable)object2;
if
((obj1).isLargerThan(obj2) <
0
)
return
object1;
else
return
object2;
}
public
boolean
isEqual(Object object1, Object object2) {
Relatable obj1 = (Relatable)object1;
Relatable obj2 = (Relatable)object2;
if
( (obj1).isLargerThan(obj2) ==
0
)
return
true
;
else
return
false
;
}
|
这些方法适用于任何“Relatable”的类,而无关它们的继承关系。实现了Relatable的类,它们既属于自身(或者父类)的类型,也属于Relatable类型。这使得它们具有了多重继承的优点,因为它们可以同时具备父类和接口的行为。
重写接口
假设你开发了一个接口名为DoIt:
1
2
3
4
|
public
interface
DoIt {
void
doSomething(
int
i,
double
x);
int
doSomethingElse(String s);
}
|
然后,你想加入一个新的方法在这个接口里,因此代码变成:
1
2
3
4
5
|
public
interface
DoIt {
void
doSomething(
int
i,
double
x);
int
doSomethingElse(String s);
boolean
didItWork(
int
i,
double
x, String s);
}
|
如果你这么修改了,所有实现了旧的DoIt接口的类都会出错,因为它们不再正确的实现这个接口。所有使用这个接口的程序员会严重抗议你的修改。
你需要预估你的接口用户的需求,并从开始就完善的设计好这个接口。但是这常常是无法做到的。另一个解决方法就是再写一个接口。例如,你可以写一个DoItPlus的接口继承原有的接口。
1
2
3
|
public
interface
DoItPlus
extends
DoIt {
boolean
didItWork(
int
i,
double
x, String s);
}
|
现在你的用户可以选择继续使用旧接口DoIt,或是升级的新接口DoItPlus。
总结
接口就是两个对象间的沟通协议。
一个接口的声明包含一些方法的签名(signatures),但不需要实现它们;也可能含有一些常量。
实现某接口的类必须实现该接口所声明的所有的方法。
在任何使用类型名称的地方都可以使用接口的名字。