Java 8 - 使用双冒号语法调用接口的默认方法

4
我正在深入研究Java 8的创新,我试图调用一个高级接口中实现的默认方法,即使子类覆盖了它。如果我回到在我的BusinessLogic类中实现Comparator,我不会有任何问题,但我想知道是否有一些魔法让我使用新的::操作符。
代码:
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);
    }
}

1
你的建议有什么问题吗? - assylias
你的意思是实现一个 Comparator 吗?正如我所说,没有什么问题,但这看起来像是使用 :: 的理想情况,直到我发现 lambda 表达式会转换为运行时类型。因此,我想知道是否可以仍然使用更改进的语法。 - blagae
1
确实如此 - 我没想到。 - assylias
我不相信按照所述的方式编写IdEntityGame可以实现这一点。 - Louis Wasserman
3个回答

6

一种方法是将比较方法放在静态方法中:

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

那么,您的方法应该是:
Collections.sort(list, IdEntity::defaultCompare);

1
太棒了,这个解决了问题。我没有想到在我的接口中使用静态方法,因为它也是Java 8中的新功能。 - blagae
3
我还会考虑使用Collections.sort(list, comparingInt(IdEntity::getId)),并静态导入java.util.Comparator.comparingInt。这样一来,就能在一开始就清楚看到比较实际上正在做什么,并且代码也几乎同样简洁。 - Louis Wasserman
这实际上更好,@LouisWasserman,因为我不必更改IdEntity接口。如果您将其发布为答案,我肯定会点赞。 - blagae

5
更简单的方法:静态导入 Comparator.comparingInt 并使用。
Collections.sort(list, comparingInt(IdEntity::getId));

我认为你无法重新提取默认的compareTo实现:它已经被覆盖;通常你不能运行一个被覆盖的方法实现。但这只是直截了当地工作。


2
一些通用的注释:
如果一个类覆盖了一个超类方法,那么其他类没有办法故意调用超类方法,即忽略该方法已被覆盖的事实。禁止这种绕过是Java编程语言的一个基本设计决策。
唯一可以显式调用被覆盖方法的类是进行覆盖的类本身,当然,只能在自己的实例上调用。因此,在Game.compareTo中,您可以使用IdEntity.super.compareTo调用被覆盖的IdEntity.compareTo,并在this实例上调用该方法。
您还可以创建对此super调用的方法引用,但是由于它仅允许在此实例上使用,因此必须绑定此实例,不能将其作为函数签名的参数。例如,在Game的实例方法中,您可以编写:
ToIntFunction<IdEntity> f=IdEntity.super::compareTo;

这个函数将在当前的 Game 实例上调用 default 方法 IdEntity.compareTo,并通过 IdEntity 参数传递。
整个设计都应该被质疑。您不应该创建一个类层次结构,在其中非抽象的实现了Comparable接口的compareTo方法被覆盖。这是一种反模式,会导致错误和令人惊讶的行为。正如Louis Wasserman指出的那样,有一种简单的方法可以创建所需行为的Comparator。如果Game实例的自然顺序与一般的IdEntity顺序不同,那么它们中至少有一个不是“自然顺序”,因此应该仅表示为Comparator,甚至可能是两者都应该如此。
import java.util.Comparator;

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

如果您认为“游戏”具有“自然顺序”:
public class Game implements IdEntity,Comparable<Game> {

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

或者如果默认的Game顺序只是IdEntity.defaultOrder()的替代选择:
import java.util.Comparator;

public class Game implements IdEntity {

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接