Java 8默认方法作为特征:安全吗?

Java 8 default methods as traits : safe?

在Java 8中使用默认方法作为穷人的男性版本是安全的做法吗?

有人说,如果你只是为了熊猫而使用它们,可能会让熊猫伤心,因为这很酷,但这不是我的意图。还经常提醒人们,引入了默认方法来支持API的演进和向后兼容性,这是正确的,但这并不意味着将它们本身用作特性是错误的或扭曲的。

我考虑了以下实际用例:

1
2
3
4
5
public interface Loggable {
    default Logger logger() {
        return LoggerFactory.getLogger(this.getClass());
    }
}

或者,定义一个PeriodTrait

1
2
3
4
5
6
7
public interface PeriodeTrait {
    Date getStartDate();
    Date getEndDate();
    default isValid(Date atDate) {
        ...
    }
}

可以使用组合(甚至是助手类),但它看起来更加冗长和混乱,不允许从多态性中获益。

所以,使用默认方法作为基本特征是可以/安全的,还是我应该担心不可预见的副作用?

关于SO的几个问题与JavaS和Scala性状有关,这不是重点。我也不只是征求意见。相反,我在寻找一个权威性的答案,或者至少是实地观察:如果你在你的公司项目中使用了默认的方法作为特征,那么它是一个定时炸弹吗?


简短的回答是:如果安全使用它们是安全的。)

难听的回答:告诉我你所说的特质是什么意思,也许我会给你一个更好的答案:)

严肃地说,"特质"一词并没有很好地定义。许多Java开发人员最熟悉的是他们在Scala中表达的特性,但是斯卡拉远不是第一种具有特征的语言,无论是名字还是效果。

例如,在scala中,特征是有状态的(可以有var变量);在fortress中,它们是纯粹的行为。Java与默认方法的接口是无状态的,这是否意味着它们不是特性?(提示:这是一个技巧问题。)

同样,在scala中,特征是通过线性化组成的;如果类A扩展了特征XY,那么XY的混合顺序决定了XY之间的冲突是如何解决的。在爪哇,这种线性化机制是不存在的(部分被拒绝,因为它太"非Java类")。

向接口添加默认方法的直接原因是为了支持接口演进,但是我们很清楚我们已经超越了这一点。不管你认为这是"界面进化++"还是"特性——"都是个人的解释。所以,为了回答你关于安全的问题…只要你坚持这个机制实际支持的东西,而不是一厢情愿地把它拉伸到它不支持的东西上,你就应该没事了。

一个关键的设计目标是,从接口的客户机的角度来看,默认方法应该与"常规"接口方法不可区分。因此,方法的默认性只对接口的设计者和实现者有意义。

以下是一些在设计目标范围内的用例:

  • 界面演变。在这里,我们将向现有接口添加一个新方法,根据该接口上的现有方法,该方法具有合理的默认实现。一个例子是将forEach方法添加到Collection中,其中默认实现是用iterator()方法编写的。

  • "可选"方法。在这里,接口的设计者说,"如果实现者愿意接受所需功能的限制,他们就不需要实现这个方法"。例如,Iterator.remove被赋予了一个缺省值,它抛出了UnsupportedOperationException;由于Iterator的绝大多数实现无论如何都具有这种行为,因此缺省值使得该方法本质上是可选的。(如果AbstractCollection的行为表示为Collection的默认值,我们可能会对突变方法做同样的操作。)

  • 方便方法。这些方法都是为了方便起见而严格执行的,同样也通常是根据类上的非默认方法来实现的。第一个示例中的logger()方法就是对此的合理说明。

  • 组合器。这些是基于当前实例实例化接口新实例的组合方法。例如,方法Predicate.and()Comparator.thenComparing()是组合器的例子。

如果您提供了一个默认的实现,那么您还应该提供一些默认的规范(在JDK中,我们使用@implSpecjavadoc标记来实现),以帮助实现者理解他们是否想要覆盖该方法。一些默认值,如方便方法和组合器,几乎从不被重写;其他的,如可选方法,通常被重写。您需要提供足够的规范(不仅仅是文档)来说明默认情况下承诺要做什么,这样实现者就可以明智地决定是否需要覆盖它。