【问题标题】:Must implement default interface method?必须实现默认接口方法吗?
【发布时间】:2016-06-16 15:38:15
【问题描述】:

这是一个显示我的问题的简化示例:

import java.util.List;

public interface SingleTask extends List<Runnable>, Runnable {
    default Runnable get(final int x) {
        if (x != 0) {
            throw new IndexOutOfBoundsException();
        }
        return this;
    }

    default int size() {
        return 1;
    }
}

import java.util.AbstractList;

public class MyTask extends AbstractList<Runnable> implements SingleTask {
    @Override
    public void run() {
        System.out.println("hello");
    }
}

SingleTask 中,我提供了getsize 方法的实现,它们是AbstractList 中仅有的抽象方法。但是,当我编译 MyTask 时,我仍然会收到如下错误:

MyTask 类型必须实现继承的抽象方法 AbstractCollection.size()

MyTask.java:3: 错误:MyTask 不是抽象的并且不会覆盖 AbstractList 中的抽象方法 get(int)

(取决于编译器)。当然,我使用的是 java 8。

所以我有两个问题:

  1. 为什么会出现这些错误?我期待它能够识别默认实现。
  2. 如果不应该这样工作,那么在不复制整个代码的情况下,在MyTask 中使用这两种方法的最简单方法是什么?

【问题讨论】:

  • 不要让你的任务扩展List&lt;Runnable&gt;(这在语义上意味着什么?),而是使用public List&lt;Runnable&gt; getRunnables(); 方法创建一个接口Task。如果做不到这一点,只需将SingleTask 设为抽象类而不是接口。
  • 值得注意的是,Eclipse 在 size() 未实现时感到窒息:这可能是一个 Eclipse 错误。 javac 1.8.0_51 因get(int) 未实施而窒息,这是正确的:未实施。
  • @Tunaki sizeget 同样实现/未实现
  • @aditsu SingleTask is a singleton implementation 好了,你刚刚说了。如果它是一个实现,它应该是一个类,而不是一个接口。你在这里尝试使用默认方法的东西看起来有点像特征,它们不是为它设计的。
  • 如果这行得通,你会得到一个List 实现,其中甚至像equalshashCode 这样的关键方法也会被破坏,因为它们最终可能会得到StackOverflowError。这种递归数据结构的意义何在?

标签: java java-8 default-method


【解决方案1】:

强制SingleTask 实现者也实现List 的所有方法并不是很优雅,并且默认方法不打算用于定义类似特征的实体,就像您的SingleTask 接口一样。

默认方法作为特征是一个坏主意的原因有几个,最明显的原因是任何实现者都可以简单地覆盖您的默认方法,从而破坏您的特征。

这正是这里发生的事情:因为AbstractList 明确将get()size() 声明为abstract,这意味着SingleTask 将继承它们,而不是您可能在超级界面。

JLS 8.4.8:

一个类 C 继承自它的直接超类和直接 超接口所有抽象和默认(第 9.4 节)方法 m 以下都是正确的:

...

  • C 从其直接超类继承的具体方法没有一个签名是 m 签名的子签名。

记住所有这些,最简单的解决方案可能是这样的:

public abstract class SingleTask extends AbstractList<Runnable> implements Runnable {
    @Override
    public final Runnable get(final int x) {
        if (x != 0) {
            throw new IndexOutOfBoundsException();
        }
        return this;
    }

    @Override
    public final int size() {
        return 1;
    }

    @Override
    public abstract void run();
}

它的缺点是你的任务必须扩展SingleTask,因此不能扩展其他任何东西,虽然他们不需要处理同样是List的任务,但他们只需要实现run().

但从长远来看,我更喜欢组合而不是继承,并且任务只是返回一个可运行的列表而不是它们本身就是一个。

【讨论】:

  • 我并没有强迫 SingleTask 实现者也扩展 AbstractList,但看起来你是。此外,您将 SingleTask 设为一个类,这意味着如果我想使用它,我无法扩展其他类。你甚至没有尝试回答我的第一个问题。
  • @aditsu I wasn't forcing SingleTask implementors to also extend AbstractList 是的,你这样做了,这就是导致问题的原因。我也会编辑我的答案以涵盖这一点。
  • 错了,我的代码,SingleTask的实现者只需要实现List的方法。 SingleTask 中没有关于 AbstractList 的内容!
  • @aditsu 没错,他们也可以手动实现所有List 方法。 (从好的方面来说,至少他们的代码可以编译。)
  • @aditsu 是的,如果你想要可插入的行为,组合是你在 Java 中的最佳选择。我个人觉得这很烦人,因为我真的很喜欢特质并且喜欢你,我认为默认方法可以完成这项工作。但很快就证明这不是一个好主意。
【解决方案2】:
  1. 为什么会出现这些错误?我期待它能够识别默认实现。

我认为@biziclop 在his answer 中正确地涵盖了这一点。简而言之,由于AbstractListget(int)size() 方法声明为抽象方法,这些方法优先于SingleTask 中的默认实现。

  1. 如果不应该这样工作,那么在不复制整个代码的情况下,在MyTask 中使用这两种方法的最简单方法是什么?

最简单的方法是覆盖MyTask 中的get(int)size() 方法,以便它们委托给SingleTask 接口中的默认方法:

public class MyTask extends AbstractList<Runnable> implements SingleTask {

    @Override
    public void run() {
        System.out.println("hello");
    }

    @Override
    public Runnable get(int index) {
        return SingleTask.super.get(index);
    }

    @Override
    public int size() {
        return SingleTask.super.size();
    }
}

使用这种方法,您可以委派SingleTask 中的默认方法。我不认为这是一件坏事(至少,您不需要使用属性)。此外,编写这些方法是有意义的,以便您可以选择提供默认实现的接口。

【讨论】:

    猜你喜欢
    • 2015-07-31
    • 2014-12-06
    • 2021-05-04
    • 1970-01-01
    • 1970-01-01
    • 2014-10-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多