5

Here is a simplified example showing my problem:

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");
    }
}

In SingleTask I provide implementations for the methods get and size, which are the only abstract methods from AbstractList. However, when I compile MyTask, I still get errors like:

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

or

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

(depending on the compiler). I am, of course, using java 8.

So I have two questions:

  1. Why am I getting these errors? I was expecting it to recognize the default implementations.
  2. If it's not supposed to work like that, then what's the simplest way to use those two methods in MyTask without copying the whole code?
  • 1
    Don't make your tasks extend `List` (what does that mean semantically anyway?), instead create an interface `Task` with a `public List getRunnables();` method. Failing that, simply make `SingleTask` an abstract class instead of an interface. – biziclop Mar 03 '16 at 10:53
  • 1
    Worth noting that Eclipse is choking on the `size()` not implemented: this is probably an Eclipse bug. `javac` 1.8.0_51 is choking on `get(int)` not being implemented and it's right: it is not implemented. – Tunaki Mar 03 '16 at 10:55
  • @biziclop it's supposed to be a list of tasks; SingleTask is a singleton implementation – aditsu quit because SE is EVIL Mar 03 '16 at 10:55
  • 1
    @Tunaki both `size` and `get` are equally implemented/not implemented – aditsu quit because SE is EVIL Mar 03 '16 at 10:56
  • 1
    @aditsu `SingleTask is a singleton implementation` There, you've just said it. If it's an implementation, it should be a class, not an interface. What you're trying to use default methods here for looks a bit like traits, which they aren't designed for. – biziclop Mar 03 '16 at 10:57
  • @biziclop it implements the singleton "feature" (or trait, I guess?), not the actual task; anyway, regardless if this is how *you think* I should be doing this, the question is about why it's not working this way. – aditsu quit because SE is EVIL Mar 03 '16 at 11:00
  • 2
    If that worked, you got a `List` implementation where even crucial methods like `equals` or `hashCode` are broken as they might end up with a `StackOverflowError`. What’s the point of this recursive data structure? – Holger Mar 03 '16 at 14:11
  • 1
    See http://stackoverflow.com/q/24016962/2711488 – Holger Mar 03 '16 at 14:12

2 Answers2

6

Forcing SingleTask implementors to also implement all the methods of List isn't very elegant, and default methods aren't meant to be used to define trait-like entities, which your SingleTask interface looks like.

There are several reasons why default methods-as-traits is a bad idea, the most obvious one being that any implementor can simply override your default method, ruining your trait.

And this is exactly what is happening here: since AbstractList explicitly declares get() and size() as abstract, it means SingleTask will inherit them, rather than the default implementations you may have had in a superinterface.

JLS 8.4.8:

A class C inherits from its direct superclass and direct superinterfaces all abstract and default (§9.4) methods m for which all of the following are true:

...

  • No concrete method inherited by C from its direct superclass has a signature that is a subsignature of the signature of m.

Bearing all that in mind the simplest solution is probably this:

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();
}

Its drawback is that your tasks must extend SingleTask and thus can't extend anything else, on the plus side though they don't need to deal with the task also being a List, they only need to implement run().

In the long run though, I would prefer composition over inheritance, and tasks simply returning a list of runnables rather than themselves being one.

biziclop
  • 48,926
  • 12
  • 77
  • 104
  • I wasn't forcing SingleTask implementors to also extend AbstractList, but it looks like you are. Also, you made SingleTask a class, which means I can't extend a different class if I want to use this. And you didn't even attempt to answer my first question. – aditsu quit because SE is EVIL Mar 03 '16 at 11:06
  • 1
    @aditsu `I wasn't forcing SingleTask implementors to also extend AbstractList` Yes, you do, and that's what is causing the problems. I'll edit my answer to cover that too. – biziclop Mar 03 '16 at 11:14
  • Wrong, with my code, SingleTask implementors only need to implement List's methods. There's nothing in SingleTask about AbstractList! – aditsu quit because SE is EVIL Mar 03 '16 at 11:15
  • 1
    @aditsu You're right, they can also implement all the `List` methods by hand. (On the plus side, at least their code would compile then.) – biziclop Mar 03 '16 at 11:20
  • I think you have a good point about composition vs inheritance, I guess you would use that for implementing traits? – aditsu quit because SE is EVIL Mar 03 '16 at 11:35
  • 1
    @aditsu Yes, if you want pluggable behaviour, composition is your best bet in Java. I find it personally quite annoying as I really like traits and like you, I thought default methods could do the job. But it turned out quite quickly that it isn't a good idea. – biziclop Mar 03 '16 at 11:45
2
  1. Why am I getting these errors? I was expecting it to recognize the default implementations.

I think @biziclop correctly covered that in his answer. In short, as AbstractList declares get(int) and size() methods as abstract, these take precedence over your default implementations in SingleTask.

  1. If it's not supposed to work like that, then what's the simplest way to use those two methods in MyTask without copying the whole code?

The easiest would be to override get(int) and size() methods in MyTask, so that they delegate to your default methods in SingleTask interface:

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();
    }
}

With this approach, you would be kind of delegating to your default methods in SingleTask. I don't think this is a bad thing (at least, you don't need to use an attribute). Besides, it makes sense to write these methods, so that you can choose what interface provides the default implementations.

Community
  • 1
  • 1
fps
  • 33,623
  • 8
  • 55
  • 110