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:
MyTask
without copying the whole code?Default interface methods are an efficient way to deal with this issue. They allow us to add new methods to an interface that are automatically available in the implementations. Therefore, we don't need to modify the implementing classes.
A class must implement all methods of the interface in its implementation part, with the following exceptions: Interface methods declared as optional using the addition DEFAULT.
Members with bodies permit the interface to provide a "default" implementation for the method in classes and structs that do not provide their own implementation. Interfaces may not contain instance state.
The Java Tutorials Documentation defines default interface methods: Default methods enable you to add new functionality to the interfaces of your libraries and ensure binary compatibility with code written for older versions of those interfaces. and then proceeds to supply a convoluted, 50+ line example.
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.
- 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
.
- 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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With