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

问题

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

Some claim it may make pandas sad如果你只是为了它而使用它们,因为它很酷,但这不是我的意图。还经常提醒的是,引入了默认方法来支持API演变和向后兼容性,这是事实,但这并不会使它们错误或扭曲使用它们作为特征本身。

我有the following practical use case

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

或许,定义aPeriodTrait

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

可以肯定的是,可以使用组合(甚至是辅助类),但它看起来更冗长,更混乱,并且不允许从多态性中受益。

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

Several questions在SO中与Java vs Scala特征有关;这不是重点。我也不仅仅是在征求意见。相反,我正在寻找一个权威的答案或者至少是现场洞察力:如果你在公司项目中使用默认方法作为特征,那么它是否真的是一个时间炸弹?


#1 热门回答(103 赞)

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

讽刺的回答:告诉我whatyoumean的特点,也许我会给你一个更好的答案:)

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

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

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

将默认方法添加到接口的近似原因是支持接口演变,但我们很清楚我们已经超越了这一点。你是否认为这是"界面进化"或"特征 - "是一个个人解释的问题。所以,回答你关于安全性的问题......只要你坚持机制实际支持的东西,而不是试图将它扩展到它不支持的东西,你应该没问题。

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

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

  • 界面演变。在这里,我们向现有接口添加一个新方法,该接口在该接口上的现有方法方面具有合理的默认实现。一个例子是将forEach方法添加到Collection,其中默认实现是根据iterator()方法编写的。
  • "可选"方法。在这里,接口的设计者说"如果他们愿意接受需要的功能限制,那么实现者就不需要实现这种方法"。例如,Iterator.remove被赋予一个默认值,抛出UnsupportedOperationException;由于绝大多数Iterator实现都有这种行为,因此默认情况下这个方法基本上是可选的。 (如果AbstractCollection的行为在Collection上表示为默认值,我们可能会对mutative方法执行相同的操作。)
  • 便利方法。这些方法严格来说是为了方便,通常也是根据类中的非默认方法实现的。第一个示例中的logger()方法是对此的合理说明。
  • 组合器。这些是基于当前实例实例化接口的新实例的组合方法。例如,方法Predicate.and()或Comparator.thenComparing()是组合器的示例。

如果提供默认实现,则还应提供一些默认实现(在JDK中,我们使用@implSpecjavadoc标记)以帮助实现者了解是否要覆盖该方法。一些默认值,如方便方法和组合器,几乎从不被覆盖;其他人,如可选方法,经常被覆盖。你需要提供有关默认承诺要做的足够的规范(而不仅仅是文档),因此实现者可以做出关于是否需要覆盖它的合理决策。