Java 8 个默认方法作为特征:安全吗?
Java 8 default methods as traits : safe?
在 Java 8 中使用 默认方法作为 traits 的穷人版本是否安全?
Some claim it may make pandas sad 如果你只是为了它而使用它们,因为它很酷,但这不是我的意图。还经常提醒,引入默认方法是为了支持 API 进化和向后兼容,这是事实,但这并不意味着将它们本身用作特征是错误的或扭曲的。
我想 the following practical use case:
public interface Loggable {
default Logger logger() {
return LoggerFactory.getLogger(this.getClass());
}
}
或者,定义一个 PeriodTrait
:
public interface PeriodeTrait {
Date getStartDate();
Date getEndDate();
default isValid(Date atDate) {
...
}
}
诚然,可以使用组合(甚至是助手 类),但它看起来更冗长和混乱,并且无法从多态性中获益。
所以,是ok/safe使用默认方法作为基本特征,还是我应该担心不可预见的副作用?
SO 上的 Several questions 与 Java vs Scala 特征相关;这不是重点。我也不只是征求意见。相反,我正在寻找一个权威的答案或至少是现场洞察力:如果你在你的公司项目中使用默认方法作为特征,它会变成一颗定时炸弹吗?
简短的回答是:如果你安全地使用它们是安全的:)
尖刻的回答:告诉我你的特征是什么意思,也许我会给你一个更好的答案:)
严肃地说,"trait" 一词的定义并不明确。许多 Java 开发人员最熟悉 Scala 中表达的特征,但 Scala 远不是第一种具有特征的语言,无论是在名称上还是在效果上。
例如,在 Scala 中,特征是有状态的(可以有 var
个变量);在堡垒中,它们是纯粹的行为。 Java 带有默认方法的接口是无状态的;这是否意味着它们不是特征? (提示:这是一个技巧问题。)
同样,在 Scala 中,特征是通过线性化组成的;如果 class A
扩展了特征 X
和 Y
,那么 X
和 Y
的混合顺序决定了 [=] 之间的冲突12=] 和 Y
已解决。在 Java 中不存在这种线性化机制(它被拒绝了,部分原因是它太 "un-Java-like"。)
向接口添加默认方法的最直接原因是支持接口演化,但我们很清楚我们要超越这个范围。您认为它是 "interface evolution++" 还是 "traits--" 是个人解释的问题。所以,回答你关于安全的问题......只要你坚持机制实际支持的东西,而不是试图一厢情愿地将它扩展到它不支持的东西,你应该没问题。
一个关键的设计目标是,从接口的 客户端 的角度来看,默认方法应该与 "regular" 接口方法没有区别。因此,方法的默认性仅对接口的设计者和实现者感兴趣。
以下是一些完全符合设计目标的用例:
界面进化。在这里,我们向现有接口添加一个新方法,根据该接口上的现有方法,它具有合理的默认实现。一个例子是将 forEach
方法添加到 Collection
,其中默认实现是根据 iterator()
方法编写的。
"Optional" 方法。在这里,界面的设计者说 "Implementors need not implement this method if they are willing to live with the limitations in functionality that entails"。例如,Iterator.remove
被赋予了一个默认值,它会抛出 UnsupportedOperationException
;由于 Iterator
的绝大多数实现无论如何都具有此行为,默认情况下使此方法本质上是可选的。 (如果 AbstractCollection
中的行为在 Collection
中表示为默认值,我们可能会对可变方法执行相同的操作。)
方便的方法。这些方法完全是为了方便起见,通常也是根据 class 上的非默认方法实现的。您第一个示例中的 logger()
方法是对此的合理说明。
组合器。这些是基于当前实例实例化接口的新实例的组合方法。例如,方法 Predicate.and()
或 Comparator.thenComparing()
是组合器的示例。
如果您提供默认实现,您还应该为默认提供一些规范(在 JDK 中,我们为此使用 @implSpec
javadoc 标记)以帮助实现者了解他们是否是否要覆盖该方法。一些默认值,如便捷方法和组合器,几乎从不被覆盖;其他的,比如可选方法,经常被覆盖。您需要提供足够的规范(不仅仅是文档)来说明默认承诺做什么,以便实施者可以就是否需要覆盖它做出明智的决定。
在 Java 8 中使用 默认方法作为 traits 的穷人版本是否安全?
Some claim it may make pandas sad 如果你只是为了它而使用它们,因为它很酷,但这不是我的意图。还经常提醒,引入默认方法是为了支持 API 进化和向后兼容,这是事实,但这并不意味着将它们本身用作特征是错误的或扭曲的。
我想 the following practical use case:
public interface Loggable {
default Logger logger() {
return LoggerFactory.getLogger(this.getClass());
}
}
或者,定义一个 PeriodTrait
:
public interface PeriodeTrait {
Date getStartDate();
Date getEndDate();
default isValid(Date atDate) {
...
}
}
诚然,可以使用组合(甚至是助手 类),但它看起来更冗长和混乱,并且无法从多态性中获益。
所以,是ok/safe使用默认方法作为基本特征,还是我应该担心不可预见的副作用?
SO 上的Several questions 与 Java vs Scala 特征相关;这不是重点。我也不只是征求意见。相反,我正在寻找一个权威的答案或至少是现场洞察力:如果你在你的公司项目中使用默认方法作为特征,它会变成一颗定时炸弹吗?
简短的回答是:如果你安全地使用它们是安全的:)
尖刻的回答:告诉我你的特征是什么意思,也许我会给你一个更好的答案:)
严肃地说,"trait" 一词的定义并不明确。许多 Java 开发人员最熟悉 Scala 中表达的特征,但 Scala 远不是第一种具有特征的语言,无论是在名称上还是在效果上。
例如,在 Scala 中,特征是有状态的(可以有 var
个变量);在堡垒中,它们是纯粹的行为。 Java 带有默认方法的接口是无状态的;这是否意味着它们不是特征? (提示:这是一个技巧问题。)
同样,在 Scala 中,特征是通过线性化组成的;如果 class A
扩展了特征 X
和 Y
,那么 X
和 Y
的混合顺序决定了 [=] 之间的冲突12=] 和 Y
已解决。在 Java 中不存在这种线性化机制(它被拒绝了,部分原因是它太 "un-Java-like"。)
向接口添加默认方法的最直接原因是支持接口演化,但我们很清楚我们要超越这个范围。您认为它是 "interface evolution++" 还是 "traits--" 是个人解释的问题。所以,回答你关于安全的问题......只要你坚持机制实际支持的东西,而不是试图一厢情愿地将它扩展到它不支持的东西,你应该没问题。
一个关键的设计目标是,从接口的 客户端 的角度来看,默认方法应该与 "regular" 接口方法没有区别。因此,方法的默认性仅对接口的设计者和实现者感兴趣。
以下是一些完全符合设计目标的用例:
界面进化。在这里,我们向现有接口添加一个新方法,根据该接口上的现有方法,它具有合理的默认实现。一个例子是将
forEach
方法添加到Collection
,其中默认实现是根据iterator()
方法编写的。"Optional" 方法。在这里,界面的设计者说 "Implementors need not implement this method if they are willing to live with the limitations in functionality that entails"。例如,
Iterator.remove
被赋予了一个默认值,它会抛出UnsupportedOperationException
;由于Iterator
的绝大多数实现无论如何都具有此行为,默认情况下使此方法本质上是可选的。 (如果AbstractCollection
中的行为在Collection
中表示为默认值,我们可能会对可变方法执行相同的操作。)方便的方法。这些方法完全是为了方便起见,通常也是根据 class 上的非默认方法实现的。您第一个示例中的
logger()
方法是对此的合理说明。组合器。这些是基于当前实例实例化接口的新实例的组合方法。例如,方法
Predicate.and()
或Comparator.thenComparing()
是组合器的示例。
如果您提供默认实现,您还应该为默认提供一些规范(在 JDK 中,我们为此使用 @implSpec
javadoc 标记)以帮助实现者了解他们是否是否要覆盖该方法。一些默认值,如便捷方法和组合器,几乎从不被覆盖;其他的,比如可选方法,经常被覆盖。您需要提供足够的规范(不仅仅是文档)来说明默认承诺做什么,以便实施者可以就是否需要覆盖它做出明智的决定。