119

在 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 特征有关;这不是重点。我也不仅仅是征求意见。相反,我正在寻找一个权威的答案,或者至少是现场洞察力:如果你在你的公司项目中使用默认方法作为特征,那它是不是一个定时炸弹?

4

1 回答 1

127

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

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

严肃地说,“特质”一词的定义并不明确。许多 Java 开发人员最熟悉 Scala 中表达的特征,但 Scala 远非第一种在名称或效果上具有特征的语言。

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

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

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

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

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

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

  • “可选”方法。在这里,接口的设计者说“如果实现者愿意忍受所需要的功能限制,他们就不需要实现这个方法”。例如,Iterator.remove给定一个默认值 throws UnsupportedOperationException; 由于绝大多数的实现Iterator无论如何都有这种行为,默认使这个方法本质上是可选的。(如果 from 的行为AbstractCollection被表示为默认值Collection,我们可以对可变方法执行相同的操作。)

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

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

如果您提供默认实现,您还应该为默认提供一些规范(在 JDK 中,我们@implSpec为此使用 javadoc 标签)以帮助实现者了解他们是否要覆盖该方法。一些默认值,比如便捷方法和组合器,几乎从不被覆盖;其他的,比如可选方法,经常被覆盖。您需要提供足够的规范(不仅仅是文档)来说明默认承诺做什么,以便实现者可以就是否需要覆盖它做出明智的决定。

于 2015-02-23T22:38:19.240 回答