必须实现默认接口方法吗?

5
这是一个简化的示例,展示了我的问题:

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时,仍然会出现以下错误:

The type MyTask must implement the inherited abstract method AbstractCollection.size()

或者

MyTask.java:3: error: MyTask is not abstract and does not override abstract method get(int) in AbstractList

(取决于编译器)。当然,我使用的是Java 8。
那么我有两个问题:
  1. 为什么会出现这些错误?我原本期望它能够识别默认实现。
  2. 如果它不能像预期那样工作,那么如何在不复制整个代码的情况下在MyTask中使用这两种方法最简单呢?

1
值得注意的是,Eclipse在size()未实现时出现了问题:这可能是Eclipse的一个bug。而javac 1.8.0_51在未实现get(int)时出现问题,这是正确的:它确实没有被实现。 - Tunaki
1
@Tunaki sizeget都被同等地实现/未实现。 - aditsu quit because SE is EVIL
1
@aditsu SingleTask是单例模式的实现,你刚刚说了它。如果它是一个实现,那么它应该是一个类,而不是一个接口。你在这里尝试使用默认方法看起来有点像特征,但它们并不是为此设计的。 - biziclop
2
如果这样做成功了,你将得到一个List实现,即使是像equalshashCode这样关键的方法也会出现问题,因为它们可能会导致StackOverflowError。这个递归数据结构有什么意义呢? - Holger
1
请查看 https://dev59.com/OmAg5IYBdhLWcg3wDHfS。 - Holger
显示剩余3条评论
2个回答

6
强制实现SingleTask接口的类也要实现List接口中的所有方法并不是非常优雅,而且默认方法并不适合用于定义类似特征的实体,而你的SingleTask接口看起来就像一个特征。
默认方法作为特征是个不好的想法,有几个原因,最明显的一点是任何一个实现者都可以简单地重写你的默认方法,破坏你的特征。
而这正是在这里发生的事情:由于AbstractList显式地将get()size()声明为abstract,这意味着SingleTask将继承它们,而不是你在超级接口中可能拥有的默认实现。JLS 8.4.8

一个类C从其直接超类和直接超接口继承所有m抽象方法和默认(§9.4)方法,如果以下所有内容都是真的:

...

  • 没有通过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 quit because SE is EVIL
1
@aditsu “我并没有强制SingleTask实现者也扩展AbstractList”,但事实上你确实这样做了,这就是导致问题的原因。我会编辑我的答案来解决这个问题。 - biziclop
我的代码有问题,SingleTask 实现者只需要实现 List 的方法。SingleTask 中没有关于 AbstractList 的内容! - aditsu quit because SE is EVIL
1
@aditsu 你说得对,他们也可以手动实现所有的“List”方法。(好处是,至少他们的代码会编译通过。) - biziclop
我认为你对组合与继承的观点很有道理,我猜你会用它来实现特性? - aditsu quit because SE is EVIL
1
@aditsu 如果你想要可插拔的行为,Java中的组合是最好的选择。个人而言,我觉得这很烦人,因为我很喜欢traits,和你一样,我认为默认方法可以胜任这项工作。但很快就发现这不是一个好主意。 - biziclop

2
为什么会出现这些错误?我原以为它能够识别默认实现。
我认为@biziclop在他的回答中正确地解决了这个问题。简而言之,由于AbstractList声明了get(int)size()方法是抽象的,因此它们优先于SingleTask中默认实现中的方法。
如果不希望这样工作,那么在不复制整个代码的情况下,使用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中委托默认方法。我认为这并不是一件坏事(至少,您不需要使用属性)。此外,编写这些方法是有意义的,这样您就可以选择哪个接口提供默认实现。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接