11

Lets say I have the following structure:

abstract class A {
     abstract boolean foo();
}

interface B {
     default boolean foo() { return doBlah(); }
}

class C extends A implements B {
    //function foo
}

Java will now complain that class C must implement abstract method foo from A. I can work around this problem relatively easy by redefining the function in C and simply calling B.super.foo();.

however I do not understand why the default function from interface B does not forfill this requirement on its own, and I would like to have a better understanding of the underlying mechanics of java.

L0laapk3
  • 890
  • 10
  • 26
  • Can you give some application context to explain this structure? For example, why doesn't class `A` implement interface `B`? Then class `C` should just need to extend class `A` to benefit from both super types. – Bobulous May 12 '17 at 21:54
  • This is right question and I don't see correct answer yet. When we inherit, public method in super class is as good as method defined in sub class. So we can imagine foo() method is implemented in class C only. So why compiler is complaining ? – Chirag Apr 23 '21 at 20:38

3 Answers3

6

Default methods in Interfaces are used to prevent breaking programs that depend on your Interface when you add a method to your Interface.

In the case you describe, it is unclear if the default method in the interface (which by design must have been added later on) actually fulfills the contract as originally envisioned by the Abstract Class.

In this case it is safer for Java to complain.

Above text is my interpretation of the paragraph in 9.4.1.3 of the JLS SE8 spec, and I quote:

Similarly, when an abstract and a default method with matching signatures are inherited, we produce an error. In this case, it would be possible to give priority to one or the other - perhaps we would assume that the default method provides a reasonable implementation for the abstract method, too. But this is risky, since other than the coincidental name and signature, we have no reason to believe that the default method behaves consistently with the abstract method's contract - the default method may not have even existed when the subinterface was originally developed. It is safer in this situation to ask the user to actively assert that the default implementation is appropriate (via an overriding declaration).

4

At your program's current state, if you were to override A#foo in C.java (where the default method returns true and the overridden method returns false, printing C#foo would result in false, completely ignoring the default method.

Any abstract methods defined in an abstract class are required to be overridden by the first concrete class that extends the abstract class. For this reason, it is required that C.java overrides A#foo.

Default methods within interfaces are not required to be overridden.

However, both methods share identical signatures, meaning that one is required to be overridden and the other may be overridden.

This is extremely poor design and should not be used, as methods that share an identical signature cannot both be overridden. If you want to be required to override the abstract method, then simply change the name of either the abstract method, or the default method to something other than foo.

abstract class A {
    abstract boolean bar();
}

interface B {
    default boolean foo() { return doBlah(); }
}

class C extends A implements B {
    @Override
    public boolean foo() {
        ...
    }   

    @Override
    public boolean bar() {
        ...
    }
}

If you're looking to only override A#foo in some cases, then you can simply remove the abstract method entirely and retain the default method within B.java, and override it in C.java:

abstract class A {

}

interface B {
    default boolean foo() { return doBlah(); }
}

class C extends A implements B {
    @Override
    public boolean foo() {
        ...
    }   
}

If removing A#foo is not an option, then rename the default method, B#foo, to something else.

abstract class A {
    abstract boolean foo();
}

interface B {
    default boolean bar() { return doBlah(); }
}

class C extends A implements B {
    @Override
    public boolean foo() {
        ...
    }   

    @Override
    public boolean bar() {
        ...
    }
}
Jacob G.
  • 28,856
  • 5
  • 62
  • 116
  • Good explenation, but completely removing A#foo is not an option since it obviously implies that you can no longer call foo on your collection of A's. However, I think I found a possible solution for that aswell, I'll write it in an answer so it is more clear. – L0laapk3 May 12 '17 at 18:00
  • 1
    If removing `A#foo` is not an option, then rename the default method, `B#foo`, to something else. – Jacob G. May 12 '17 at 18:01
2

The answer of Jacob G. inspired me to come up with this solution:

interface Z {
     abstract boolean foo();
}

abstract class A implements Z {

}

interface B extends Z {
     default boolean foo() { return doBlah(); }
}

class C extends A implements B {

}

This way all subclasses of class A are required to define a method foo(), Without requiring every class that implements B to re-implement it.

Community
  • 1
  • 1
L0laapk3
  • 890
  • 10
  • 26
  • The class `A` still exists because it contains other methods and variables that can not be implemented in an interface. – L0laapk3 May 12 '17 at 18:17