- 了解接口的用途
- 学习定义接口的语法
- 知道如何实现接口
- 学习使用接口和将接口引用赋给变量的语法
接口实战
下面这个简短视频实现了本单元的所有 学习目标。我将展示接口是什么,如何定义并实现接口和引用分配规则的工作原理。观看该视频之后,您可以通过阅读本单元的文字,更仔细地查看代码和复习这些概念。
接口:它们有什么好处?
从上一单元已经知道,根据设计,抽象方法指定一个契约(通过方法名、参数和返回类型)但未提供可重用的代码。当在抽象类的一个子类中实现行为的方式与另一个子类中不同时,抽象方法(在抽象类上定义)就会很有用。
当您在应用程序中看到一组可分组到一起的常见行为(想想 java.util.List
),但它们存在两个或多个实现时,可以考虑使用接口 定义该行为,这正是 Java 语言提供此特性的原因。但是,这个高级特性很容易被滥用、混淆和变成最讨厌的形式(我亲自验证过),所以使用接口时需要小心谨慎。
以这种方式考虑接口可能有所帮助:它们像仅包含抽象方法的抽象类;它们仅定义契约,而不定义实现。
定义一个接口
定义接口的语法很简单:
public interface InterfaceName {
returnType methodName(argumentList);
}
|
接口声明看起来像类声明,但使用了 interface
关键字。可以将接口命名为您想要的任何(符合语言规则的)名称,但根据约定,接口名称像类名称一样。
接口中定义的方法没有方法主体。接口的实现者负责提供方法主体(与抽象方法一样)。
接口分层结构的定义也与类一样,但一个类可实现任意多个想要的接口。请记住,一个类仅可继承一个类。如果一个类继承了另一个类并实现了一个或多个接口,这些接口会在继承的类后列出,类似这样:
public class Manager extends Employee implements BonusEligible, StockOptionRecipient {
// And so on
}
|
接口完全不需要拥有任何主体。例如,下面的定义完全可以接受:
public interface BonusEligible {
}
|
一般而言,这些接口被称为标记接口,因为它们将一个类标记为实现该接口,但未提供任何特殊的显式行为。
了解这一点后,实际定义接口就变得非常简单:
public interface StockOptionRecipient {
void processStockOptions(int numberOfOptions, BigDecimal price);
}
|
实现接口
要在类上定义一个接口,则需要实现 该接口,这意味着提供一个方法主体来进一步提供履行接口契约的行为。可以使用implements
关键字实现接口:
public class ClassName extends SuperclassName implements InterfaceName {
// Class Body
}
|
假设您在 Manager
类上实现了 StockOptionRecipient
接口,如清单 1 所示:
清单 1. 实现一个接口
public class Manager extends Employee implements StockOptionRecipient {
public Manager() {
}
public void processStockOptions (int numberOfOptions, BigDecimal price) {
log.info("I can't believe I got " + number + " options at $" +
price.toPlainString() + "!");
}
}
|
实现该接口时,需要提供该接口上的一个或多个方法的行为。必须使用与接口上的签名匹配的签名来实现这些方法,还需要添加 public
访问修饰符。
抽象类可声明它将实现某个特定的接口,但不需要实现该接口上的所有方法。抽象类不需要提供它们声称要实现的所有方法的实现。但是,第一个具体类(即第一个可实例化的类)必须实现分层结构没有实现的所有方法。
备注:实现接口的具体类的所有子类,不需要提供它们自己对该接口的实现(因为接口上的方法已由超类实现)。
在 Eclipse 中生成接口
如果您认为一个类应实现一个接口,Eclipse 可轻松地为您生成正确的方法签名。只需更改类签名来实现该接口即可。Eclipse 在该类下画了一条红色的波浪线,将它标记为错误,因为该类没有提供接口上的方法。单击类名,按 Ctrl + 1,Eclipse 会为您提供 “快速修复” 建议。在这些建议中,选择 Add Unimplemented Methods,Eclipse 会为您生成这些方法,将它们放在源文件的底部。
使用接口
接口定义了一种新的引用 数据类型,您可以使用该类型在您将引用类的任何地方引用接口。此能力可在您声明一个引用变量或从一种类型转换为另一种时使用,如清单 2 所示。
清单 2. 将一个新的 Manager
实例赋给一个 StockOptionEligible
引用
package com.makotojava.intro;
import java.math.BigDecimal;
import org.junit.Test;
public class ManagerTest {
@Test
public void testCalculateAndAwardStockOptions() {
StockOptionEligible soe = new Manager();// perfectly valid
calculateAndAwardStockOptions(soe);
calculateAndAwardStockOptions(new Manager());// works too
}
public static void calculateAndAwardStockOptions(StockOptionEligible soe) {
BigDecimal reallyCheapPrice = BigDecimal.valueOf(0.01);
int numberOfOptions = 10000;
soe.awardStockOptions(numberOfOptions, reallyCheapPrice);
}
}
|
如您所见,将一个新的 Manager
实例赋给一个 StockOptionEligible
引用是有效的,将一个新的 Manager
实例传递给一个想要 StockOptionEligible
引用的方法也是有效的。
赋值:接口
可以将来自实现某个接口的类的引用赋给一个接口类型的变量,但要遵守一些规则。在 清单 2 中,可以看到将一个 Manager
实例赋给 StockOptionEligible
变量引用是有效的。原因是 Manager
类实现了该接口。但是,以下赋值无效:
Manager m = new Manager();
StockOptionEligible soe = m; //okay
Employee e = soe; // Wrong!
|
因为 Employee
是 Manager
的超类型,所以此代码可能初看起来没有问题,但其实不然。为什么存在问题?因为 Manager
实现了 StockOptionEligible
接口,而 Employee
未实现。
像这样的赋值应遵守在 第 16 单元:继承 中看到的赋值规则。而且与类一样,仅能将一个接口引用赋给一个具有相同类型或超接口类型的变量。
接口是Java中的引用类型。它类似于类。它是一个抽象方法的集合。类实现了一个接口,从而继承了接口的抽象方法。
除了抽象方法,接口还可以包含常量,默认方法,静态方法和嵌套类型。方法体仅存在于默认方法和静态方法。
编写接口类似于编写类。但是类描述了对象的属性和行为。接口包含一个类实现的行为。
除非实现接口的类是抽象的,接口的所有方法都需要在类中定义。
接口类似于类在以下方式 -
-
接口可以包含任何数量的方法。
-
接口写入具有.java扩展名的文件中,接口名称与文件名称匹配。
-
接口的字节码出现在.class文件中。
-
接口出现在包中,并且其对应的字节码文件必须位于与包名称匹配的目录结构中。
但是,接口与类在几个方面不同,包括 -
-
您不能实例化接口。
-
接口不包含任何构造函数。
-
接口中的所有方法都是抽象的。
-
接口不能包含实例字段。只能在接口中出现的字段必须声明为static和final。
-
接口不是由类扩展的; 它由一个类实现。
-
接口可以扩展多个接口。
声明接口
该接口关键字用于声明一个接口。这里是一个简单的例子来声明一个接口 -
例
下面是一个接口的例子 -
/* File name : NameOfInterface.java */ import java.lang.*; // Any number of import statements public interface NameOfInterface { // Any number of final, static fields // Any number of abstract method declarations\ }
接口具有以下属性 -
-
接口是隐式抽象的。在声明接口时,不需要使用abstract关键字。
-
接口中的每个方法也是隐式抽象的,因此不需要抽象关键字。
-
接口中的方法是隐式公开的。
例
/* File name : Animal.java */ interface Animal { public void eat(); public void travel(); }
实现接口
当一个类实现一个接口时,你可以认为该类是签署一个契约,同意执行接口的具体行为。如果一个类不执行接口的所有行为,类必须将它自己声明为抽象。
类使用implements关键字来实现接口。implements关键字出现在声明的extends部分之后的类声明中。
例
/* File name : MammalInt.java */ public class MammalInt implements Animal { public void eat() { System.out.println("Mammal eats"); } public void travel() { System.out.println("Mammal travels"); } public int noOfLegs() { return 0; } public static void main(String args[]) { MammalInt m = new MammalInt(); m.eat(); m.travel(); } }
这将产生以下结果 -
输出
Mammal eats Mammal travels
当在接口中定义重写方法时,有几个规则要遵循 -
-
检查异常不应该在接口方法声明的实现方法或接口方法声明的子类的声明之外。
-
当覆盖方法时,应保持接口方法的签名和相同的返回类型或子类型。
-
实现类本身可以是抽象的,如果是,则不需要实现接口方法。
当实现接口时,有几个规则 -
-
类可以一次实现多个接口。
-
一个类可以只扩展一个类,但实现了很多接口。
-
接口可以扩展另一个接口,类似于类可以扩展另一个类。
扩展接口
接口可以以类可以扩展另一个类的相同方式扩展另一个接口。该扩展关键字来扩展接口,并且接口子女继承父接口的方法。
以下Sports接口通过Hockey和Football接口扩展。
例
// Filename: Sports.java public interface Sports { public void setHomeTeam(String name); public void setVisitingTeam(String name); } // Filename: Football.java public interface Football extends Sports { public void homeTeamScored(int points); public void visitingTeamScored(int points); public void endOfQuarter(int quarter); } // Filename: Hockey.java public interface Hockey extends Sports { public void homeGoalScored(); public void visitingGoalScored(); public void endOfPeriod(int period); public void overtimePeriod(int ot); }
Hockey界面有四个方法,但它继承了两个来自Sports; 因此,实现Hockey的类需要实现所有六种方法。类似地,实现Football的类需要定义Football中的三个方法和Sports中的两个方法。
扩展多个接口
Java类只能扩展一个父类。不允许多重继承。但是,接口不是类,并且接口可以扩展多个父接口。
extends关键字使用一次,父接口在逗号分隔的列表中声明。
例如,如果Hockey接口扩展了Sports和Event,它将被声明为 -
例
public interface Hockey extends Sports, Event
标记接口
当父接口不包含任何方法时,最常见的扩展接口使用。例如,java.awt.event包中的MouseListener接口扩展了java.util.EventListener,它定义为 -
例
package java.util; public interface EventListener {}
其中没有方法的接口被称为标记接口。标签接口有两个基本的设计目的 -
创建一个公共父 - 就像EventListener接口一样,它由Java API中的其他几个接口扩展,你可以使用标记接口在一组接口中创建一个公共父接口。例如,当接口扩展EventListener时,JVM知道此特定接口将在事件委派场景中使用。
向类添加数据类型 - 这种情况是术语,标记来自的地方。实现标记接口的类不需要定义任何方法(因为接口没有任何方法),但类通过多态性成为接口类型。
------------------------------------------------------------------------------------------------------------------------------------
接口
在软件工程中有一些情况,当不同的程序员团队同意一个“合同”,说明他们的软件如何交互是很重要的。每个组应该能够编写他们的代码,而不知道如何编写另一组的代码。一般来说,接口就是这样的契约。
例如,想象一个未来派的社会,计算机控制的机器人汽车通过城市街道运输乘客,没有人类操作员。汽车制造商编写操作汽车停止,启动,加速,左转等软件(Java,当然)。另一个工业集团,电子引导仪器制造商,使计算机系统接收GPS(全球定位系统)位置数据和无线传输的交通状况,并使用该信息来驱动汽车。
汽车制造商必须发布一个行业标准接口,详细说明可以调用哪些方法使汽车移动(任何汽车,从任何制造商)。引导制造商然后可以编写调用在界面中描述的方法来命令汽车的软件。无论是工业集团需要知道如何对另一组的软件实现。事实上,每个组都认为其软件高度专有,并保留随时修改它的权利,只要它继续坚持已发布的接口。
Java中的接口
在Java编程语言中,一个接口是引用类型,类似于类,它可以包含只常量,方法签名,默认的方法,静态方法和嵌套类型。方法体仅存在于默认方法和静态方法。接口不能被实例化,它们只能实现由类或扩展其他接口。扩展将在本课后面讨论。
定义接口类似于创建一个新类:
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 }
请注意,方法签名没有大括号,并以分号结束。
要使用接口,您需要编写一个实现接口的类。当可实例化类实现一个接口时,它为接口中声明的每个方法提供一个方法体。例如,
public class OperateBMW760i implements OperateCar { // OperateCar方法签名,带实现 - // 例如: int signalTurn(Direction direction,boolean signalOn){ //代码,让宝马的LEFT转向指示灯亮起 //代码,将宝马LEFT转向指示灯关闭 //代码,让宝马的RIGHT转向指示灯亮起 //代码,以关闭宝马的RIGHT转向指示灯 }} //其他成员,根据需要 - 例如,辅助类不是 //对接口的客户端可见 }}
在上述机器人车的例子中,汽车制造商将实现该接口。雪佛兰的实施将与丰田显着不同,当然,但两家制造商将坚持同样的接口。作为接口客户的指导制造商将构建系统,使用GPS数据在汽车的位置,数字街道地图和交通数据来驱动汽车。在这样做时,引导系统将调用界面方法:转向,改变车道,制动,加速等。
接口作为API
机器人车示例示出了被用作工业标准应用编程接口(API)的接口。API在商业软件产品中也很常见。通常,公司销售包含另一公司想要在其自己的软件产品中使用的复杂方法的软件包。一个示例是一组数字图像处理方法,其被销售给制造最终用户图形程序的公司。图像处理公司编写它的类来实现一个接口,它向其客户公开。然后,图形公司使用在接口中定义的签名和返回类型来调用图像处理方法。虽然图像处理公司的API被公开(对其客户),但是其API的实现被保持为密切保护的秘密 - 事实上,它可以稍后修改实现,只要其继续实现原始接口其客户依赖。