javainterfacediamond-problemdefault-method

Selecting default implementation from indirectly inherited Interface not working


I have 4 Classes that look like this:

public interface Foo<T> {
...
default boolean isEmpty() {
    return false; //dummy value real implementation is not relevant for the problem
}
}

public interface CharSequence { //java.lang.CharSequence
...
default boolean isEmpty() {
    return true; //dummy value real implementation is not relevant for the problem
}

public abstract class Bar<T> implements Foo<T> {
...
}

public final BarImpl extends Bar<Character> implements CharSequence { //typical diamond problem
...
@Override
public boolean isEmpty() { //needed to resolve diamond problem
   return Foo.super.isEmpty() // Compile Error: No enclosing instance of the type Foo<T> is accessible in scope
   return Bar.super.isEmpty() // Compile Error: No enclosing instance of the type Bar<T> is accessible in scope
   return CharSequence.super.isEmpty() // compiles
}

Why can't I access the default implementation coming from extending Bar?


Solution

  • BarImpl can not invoke Foo’s default method explicitly, as BarImpl is not directly implementing Foo. It’s extending Bar which directly implements Foo, hence, it’s Bar’s decision to override Foo’s default method or not.

    BarImpl can only invoke Bar’s isEmpty() method via super.isEmpty(), which may end up at Foo’s default method if Bar decides not to override it or at a concrete method of Bar if it does override it.

    Note that T.super.method() can only be used if either, T is a directly implemented super interface (i.e. not already implemented by a super class or another super interface) or if T is an enclosing type of an inner class. The second use case is the reason for the “No enclosing instance of … is accessible in scope” error message.

    import java.util.Objects;
    
    class Test {
      public static void main(String... arg) {
        System.out.println(new BarImpl().isEmpty());
      }
    }
    
    public interface Foo<T> {
      default boolean isEmpty() {
          System.out.println("  Foo's default method");
          return false;
      }
    }
    
    public abstract class Bar<T> implements Foo<T> {
    }
    
    public final class BarImpl extends Bar<Character> implements CharSequence {
      @Override
      public boolean isEmpty() {
        System.out.println("calling (Bar) super.isEmpty();");
        super.isEmpty();
        System.out.println("calling CharSequence.super.isEmpty();");
        return CharSequence.super.isEmpty();
      }
    
      public char charAt(int index) {
        Objects.checkIndex(index, length());
        return (char)('A' + index);
      }
    
      public int length() {
        System.out.println("  length() [CharSequence's default method]");
        return 26;
      }
    
      public CharSequence subSequence(int start, int end) {
        Objects.checkFromToIndex(start, end, length());
        return new StringBuilder(end - start).append(this, start, end);
      }
    
      public String toString() {
        return new StringBuilder(length()).append(this, 0, length()).toString();
      }
    }
    
    calling (Bar) super.isEmpty();
      Foo's default method
    calling CharSequence.super.isEmpty();
      length() [CharSequence's default method]
    false