接口:它们有什么用
正如您从前一节中所了解,抽象方法在设计上通过方法名、参数和返回类型来指定 契约,但不提供可重用的代码。抽象方法(基于抽象类定义)在行为实现方式可能从抽象类的一个子类实现方式更改为另一个子类实现方式时非常有用。
当您看到应用程序中的一组常见行为(想想 Java.U.L.List
),可以将它们组合在一起并命名,但其中存在两个或多个实现时,您可以考虑用 接口 定义该行为,这就是 Java 语言提供此特性的原因。然而,这个相当高级的特性很容易被滥用、混淆,并被扭曲成最令人发指的形状(正如我亲眼所见),所以谨慎使用接口。
这样考虑接口可能会有所帮助:它们就像只包含抽象方法的抽象类;它们只定义契约,而不定义任何实现。
定义一个接口
定义接口的语法非常简单:
public interfaceinterfaceName {
returnType methodName(argumentList);
}
接口声明看起来很像类声明,不过要使用 interface
关键字。可将接口命名为想要的任何(符合语言规则的)名称,但根据约定,接口名称要与类名称类似。
接口中定义的方法没有方法主体。接口的实现者负责提供方法主体(与抽象方法一样)。
定义接口的分层结构时也与类一样,但一个类可实现任意多个想要的接口。记住,一个类只能扩展一个类。如果一个类扩展了另一个类并实现了一个或多个接口,这些接口会在扩展的类后面列出,类似这样:
public class Manager extends Employee implements BonusEligible, StockOptionRecipient {
// Etc...
}
接口完全不需要拥有任何主体。举例而言,完全可以接受下面的定义:
public interface BonusEligible {
}
一般而言,这些接口称为 标记接口 ,因为它们将一个类标记为实现该接口,但未提供任何特殊的显式行为。
了解这一点后,实际定义接口就很轻松了:
public interface StockOptionRecipient {
void processStockOptions(int numberOfOptions, BigDecimal price);
}
实现接口
要使用一个接口,需要 实现 它,这意味着要提供一个方法主体来进一步提供可履行接口契约的行为。可使用 implements
关键字来实现接口:
public class ClassName extends SuperclassName implements InterfaceName {
// Class Body
}
假设您在 Manager
类上实现 StockOptionRecipient
接口,如清单 17 所示:
清单 17. 实现一个接口
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 就会生成这些方法,将它们放在源文件的底部。
使用接口
接口定义了一种全新的 引用 数据类型,在您要引用类的任何地方,可使用该类型引用接口。声明一个引用变量或从一种类型转换为另一种类型时可使用该功能,如清单 18 所示。
清单 18. 将一个新 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);
}
}