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

2022-08-31 09:02:33

在Java 8中使用默认方法作为穷人版本的特征是一种安全的做法吗?

有些人声称,如果你只是为了它而使用它们,它可能会让熊猫伤心,因为它很酷,但这不是我的本意。人们还经常提醒人们,引入默认方法是为了支持API演进和向后兼容性,这是事实,但这并不能使它们本身成为错误或扭曲。

我考虑了以下实际用例

public interface Loggable {
    default Logger logger() {
        return LoggerFactory.getLogger(this.getClass());
    }
}

或者,定义一个:PeriodTrait

public interface PeriodeTrait {
    Date getStartDate();
    Date getEndDate();
    default isValid(Date atDate) {
        ...
    }
}

诚然,可以使用组合(甚至是辅助类),但它似乎更加冗长和混乱,不允许从多态性中受益。

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

关于SO的几个问题与Java与Scala特征有关;这不是重点。我也不仅仅是征求意见。相反,我正在寻找一个权威的答案,或者至少是现场见解:如果你使用默认方法作为你公司项目的特征,它是否被证明是一个定时炸弹?


答案 1

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

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

严肃地说,“特质”一词的定义并不明确。许多Java开发人员最熟悉特征,因为它们在Scala中表示,但Scala远非第一个具有特征的语言,无论是在名称上还是在效果上。

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

同样,在Scala中,特征是通过线性化组成的;如果类扩展了 特征 和 ,则 和 的混合顺序决定了 和 之间的冲突是如何解决的。在Java中,这种线性化机制并不存在(它被拒绝了,部分原因是它太“不像Java”。AXYXYXY

向接口添加默认方法的近因是支持接口演进,但我们很清楚我们正在超越这一点。无论你认为这是“接口进化++”还是“特征--”都是个人解释的问题。因此,要回答您关于安全性的问题...只要你坚持机制实际支持的东西,而不是一厢情愿地把它拉伸到它不支持的东西,你应该没问题。

一个关键的设计目标是,从接口客户端的角度来看,默认方法应该与“常规”接口方法没有区别。因此,方法的默认性只有接口的设计人员实现者才感兴趣。

以下是一些完全符合设计目标的用例:

  • 接口演进。在这里,我们将一个新方法添加到现有接口中,该接口相对于该接口上的现有方法具有合理的默认实现。例如,将方法添加到 中,其中默认实现是根据方法编写的。forEachCollectioniterator()

  • “可选”方法。在这里,接口的设计者说:“如果实现者愿意忍受功能的限制,他们就不需要实现这种方法”。例如,被赋予了一个默认值,它抛出 ;由于 的绝大多数实现都具有此行为,因此默认值使此方法本质上是可选的。(如果 的行为在 上表示为默认值,我们可能会对突变方法执行相同的操作。Iterator.removeUnsupportedOperationExceptionIteratorAbstractCollectionCollection

  • 方便的方法。这些方法严格是为了方便起见,通常也是在类上的非默认方法方面实现的。第一个示例中的方法是对此的合理说明。logger()

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

如果您提供默认实现,则还应该为默认实现提供一些规范(在JDK中,我们使用javadoc标签),以帮助实现者了解他们是否要重写该方法。一些默认值,如方便方法和组合器,几乎从不被覆盖;其他方法(如可选方法)通常会被覆盖。您需要提供有关默认承诺执行的操作的足够规范(而不仅仅是文档),以便实现者可以明智地决定是否需要覆盖它。@implSpec


答案 2

推荐