当前位置: 首页 > 文档资料 > Java 入门教程 >

接口

优质
小牛编辑
144浏览
2023-12-01

目标

  • 了解接口的用途
  • 学习定义接口的语法
  • 知道如何实现接口
  • 学习使用接口和将接口引用赋给变量的语法

接口实战

下面这个简短视频实现了本单元的所有 学习目标。我将展示接口是什么,如何定义并实现接口和引用分配规则的工作原理。

接口:它们有什么好处?

从上一单元已经知道,根据设计,抽象方法指定一个契约(通过方法名、参数和返回类型)但未提供可重用的代码。当在抽象类的一个子类中实现行为的方式与另一个子类中不同时,抽象方法(在抽象类上定义)就会很有用。

当您在应用程序中看到一组可分组到一起的常见行为(想想 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 单元:继承 中看到的赋值规则。而且与类一样,仅能将一个接口引用赋给一个具有相同类型或超接口类型的变量。