5.5 默认方法冲突

问题

一个类实现了两个接口,每个接口包含的默认方法相同,但实现不同。

方案

在类中实现方法。借由关键字 super,实现仍然可以使用接口提供的默认方法。

讨论

Java 8 支持在接口中使用静态和默认方法。默认方法提供由类继承的实现,这使得接口可以在不破坏现有类实现的情况下添加新的方法。

由于一个类可以实现多个接口,它既可能继承具有相同签名但实现不同的默认方法,也可能已包含自己的默认方法。

此时需要考虑以下三种情况。

  • 如果类的方法和接口的默认方法发生冲突,则类的方法始终优先。
  • 如果两个接口(其中一个接口是另一个的后代)发生冲突,则后代接口优先;如果两个类(其中一个类是另一个的后代)发生冲突,则后代类优先。
  • 如果两个默认方法之间不存在继承关系,则类无法编译。

对于第三种情况,只需在类中实现方法即可,第三种情况将简化为第一种情况。

观察例 5-13 所示的 Company 接口和例 5-14 所示的 Employee 接口。

例 5-13 包含默认方法的 Company 接口

  1. public interface Company {
  2. default String getName() {
  3. return "Initech";
  4. }
  5.  
  6. // 其他方法
  7. }

关键字 defaultgetName 方法指定为默认方法,它提供一个返回企业名称的实现。

例 5-14 包含默认方法的 Employee 接口

  1. public interface Employee {
  2. String getFirst();
  3.  
  4. String getLast();
  5.  
  6. void convertCaffeineToCodeForMoney();
  7.  
  8. default String getName() {
  9. return String.format("%s %s", getFirst(), getLast());
  10. }
  11. }

Employee 接口同样定义了一个名为 getName 的默认方法,其签名与 Company 接口中的 getName 方法相同,但二者的实现不同。如例 5-15 所示,CompanyEmployee 类实现了 CompanyEmployee 两个接口,从而导致冲突。

例 5-15 最初的 CompanyEmployee 类(无法编译)

  1. public class CompanyEmployee implements Company, Employee {
  2. private String first;
  3. private String last;
  4.  
  5. @Override
  6. public void convertCaffeineToCodeForMoney() {
  7. System.out.println("Coding...");
  8. }
  9.  
  10. @Override
  11. public String getFirst() {
  12. return first;
  13. }
  14.  
  15. @Override
  16. Public String getLast() {
  17. return last;
  18. }
  19. }

由于 CompanyEmployee 类继承了与 getName 无关的默认方法,它无法编译。为解决这个问题,需要在 CompanyEmployee 类中添加用户自定义的 getName 方法,它将重写两个默认方法。

不过,借由关键字 super,仍然可以使用所提供的默认方法,如例 5-16 所示。

例 5-16 调整后的 CompanyEmployee

  1. public class CompanyEmployee implements Company, Employee {
  2.  
  3. @Override
  4. public String getName() {
  5. return String.format("%s working for %s",
  6. Employee.super.getName(), Company.super.getName());
  7. }
  8.  
  9. // 其余代码和之前一样
  10. }

❶ 实现 getName 方法

❷ 通过 super 访问默认实现

可以看到,CompanyEmployee 类中的 getName 方法根据 CompanyEmployee 接口定义的两个默认方法 getName 构建了一个 String

最好的消息是,这与默认方法一样复杂。读者现在已经了解了如何处理默认方法冲突。

实际上,我们还要考虑一种极端情况。如果 Company 接口定义了 getName 方法但没有将其指定为 default(也不存在相应的实现,即 getName 成为抽象方法),那么当 Employee 接口也定义了 getName 方法时,是否还会导致冲突呢?答案是肯定的。有趣的是,仍然需要在 CompanyEmployee 类中提供一个实现。

在 Java 8 之前,如果两个接口包含相同的方法且没有指定为默认方法,这并不会导致冲突,但类必须提供一个实现。

另见

有关接口中默认方法的讨论请参见范例 1.5。