Java 8 - Call interface's default method with double colon syntax

889 Views Asked by At

I'm delving into the Java 8 innovations, and I'm trying to call a default method which I implement in a high-level interface, even when a subclass overrides it. I would have no problem going back to implementing a Comparator in my BusinessLogic class, but I was wondering if there's some magic that lets me use the nifty new ::.

Code:

public interface IdEntity extends Comparable<IdEntity> {

    int getId();

    @Override
    default int compareTo(IdEntity other) {
        return getId() - other.getId();
    }
}

public class Game implements IdEntity {
    @Override
    public int compareTo(IdEntity o) {
        if (o instanceof Game) {
            Game other = (Game) o;
            int something;
            // logic
            return something;
        }
        return super.compareTo(o);
    }
}

public class BusinessLogic {
    private void sortList(List<? extends IdEntity> list) {
        // for Game, Game::compareTo gets called but I want, in this call only, the default method
        Collections.sort(list, IdEntity::compareTo);
    }
}
3

There are 3 best solutions below

3
On BEST ANSWER

One way would be to put the compare method in a static method:

public static interface IdEntity extends Comparable<IdEntity> {
  int getId();
  @Override default int compareTo(IdEntity other) {
    return defaultCompare(this, other);
  }
  static int defaultCompare(IdEntity first, IdEntity second) {
    return first.getId() - second.getId();
  }
}

Then your method would be:

Collections.sort(list, IdEntity::defaultCompare);
0
On

Simpler approach: static import Comparator.comparingInt and use

Collections.sort(list, comparingInt(IdEntity::getId));

I don't believe you can re-extract the default compareTo implementation: it's been overridden; you usually can't run an overridden method implementation. But this will just work straightforwardly.

0
On

Some general notes:

If a class overrides a superclass method, there is no way for other classes to deliberately invoke the superclass method, i.e. ignore the fact that this method has been overridden. Forbidding such bypassing is a fundamental design decision of the Java programming language.

The only class which may explicitly invoke the overridden method is the class which does the override and, of course, only on an instance of itself. Thus, within Game.compareTo you may invoke the overridden IdEntity.compareTo using IdEntity.super.compareTo which will invoke the method on the this instance.

You may also create a method reference to this super invocation but as it’s only allowed on the this instance, the this instance must be bound and can’t be a parameter of the functional signature. E.g. within an instance method of Game you can write:

ToIntFunction<IdEntity> f=IdEntity.super::compareTo;

This function will invoke the default method IdEntity.compareTo on the current Game instance, passing through the IdEntity argument.


But the entire design should be questioned. You should never create a class hierarchy in which non-abstract compareTo methods fulfilling the Comparable contract are overridden. This is an anti-pattern which will lead to bugs and surprising behavior. As Louis Wasserman has pointed out, there is an easy way for creating a Comparator for the desired behavior. If the natural order for Game instances differs from the general IdEntity order, at least one of them isn’t a “natural order” and therefore should be expressed as Comparator only, maybe even both.

import java.util.Comparator;

public interface IdEntity /* not Comparable<IdEntity> */ {
    int getId();
    static Comparator<IdEntity> defaultOrder() {
        return Comparator.comparingInt(IdEntity::getId);
    }
}

If you consider Game having a natural order:

public class Game implements IdEntity,Comparable<Game> {

    public int compareTo(Game o) {
        int something;
        // logic
        return something;
    }
    // …
}

or if the default Game order is just an alternative to the IdEntity.defaultOrder():

import java.util.Comparator;

public class Game implements IdEntity {

    public static Comparator<Game> defaultGameOrder() {
        return (a,b) -> {
            int something;
            // logic
            return something;
        };
    }
    // …
}