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


116

使用默认方法作为 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 vs Scala特性有关。这不是重点。我也不只是在征求意见。相反,我正在寻找一个权威的答案或至少是对领域的洞察力:如果您在公司项目中使用默认方法作为特质,那是否真是定时炸弹?


在我看来,继承抽象类可以使您获得相同的收益,而不必担心让熊猫哭泣...我可以看到在接口中使用默认方法的唯一原因是您需要功能并且不能修改一堆基于接口的旧代码。
迪文·菲利普斯

1
我同意@ infosec812关于扩展抽象类的定义,该抽象类定义了自己的静态记录器字段。您的logger()方法是否不会在每次调用新实例时实例化它?
Eidan Spiegel 2015年

对于日志记录,您可能需要查看Projectlombok.org及其@ Slf4j批注。
Deven Phillips

Answers:


120

简短的答案是:如果安全使用它们,这是安全的:)

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

严格地说,“特征”一词的定义不明确。许多Java开发人员最熟悉以Scala表示的特征,但是Scala并不是第一种具有特征或名称的特征的语言。

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

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

向接口添加默认方法的最直接的原因是为了支持接口的演变,但是我们深知我们将超越此范围。您是否将其视为“接口进化++”或“特征-”是个人解释的问题。因此,要回答有关安全性的问题……只要您坚持该机制真正支持的内容,而不是希望将其扩展到它不支持的内容,就可以了。

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

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

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

  • “可选”方法。在这里,接口的设计者说“如果实现者愿意忍受功能性的限制,则他们无需实现此方法”。例如,Iterator.remove给了一个默认的throws UnsupportedOperationException; 由于绝大多数实现都Iterator具有此行为,因此默认设置使该方法本质上是可选的。(如果from的行为AbstractCollection表示为on的默认值Collection,我们可能会对变异方法做同样的事情。)

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

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

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


9
谢谢Brian的全面回答。现在,我可以轻松使用默认方法。读者:例如,可以在NightHacking Worldwide Lambdas中找到Brian Goetz提供的有关接口演变和默认方法的更多信息。
youri

谢谢@ brian-goetz。从您所说的来看,我认为Java的默认方法更接近于Ducasse等人论文(scg.unibe.ch/archive/papers/Duca06bTOPLASTraits.pdf)中定义的传统特征的概念。Scala的“特征”在我看来根本不是特征,因为它们具有状态,在其成分中使用线性化,而且看起来它们还隐式地解决了方法冲突-而这些都是传统特征所不具备的有。实际上,我会说Scala特性更像是mixin而不是特性。你怎么看?PS:我从未在Scala中编写过代码。
adino

如何在充当侦听器的接口中为方法使用空的默认实现?侦听器实现可能只对侦听少数接口方法感兴趣,因此,通过将方法设置为默认值,实现者仅需实现需要侦听的方法。
Lahiru Chandima

1
@LahiruChandima喜欢MouseListener吗?在某种程度上讲,这种API样式有意义,因此适合“可选方法”存储桶。确保清楚记录可选性!
Brian Goetz

是。就像在MouseListener。感谢您的回复。
Lahiru Chandima
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.