【问题标题】:Why is there no @ConformsTo for @FunctionalInterface in Java?为什么Java中的@FunctionalInterface没有@ConformsTo?
【发布时间】:2018-01-25 15:32:33
【问题描述】:

Java (1.8+) 有一个 @FunctionalInterface 注释,它(基本上)建议您可以将方法引用而不是接口实现传递给另一个方法调用。我今天玩的有用的是:

DateTimeFormatter.parse(String, TemporalQuery<T>)

这很好,因为它可以让您告诉格式化程序将什么样的结果交还给您。 javadoc 甚至给了你一个很好的例子:

The query is typically a method reference to a from(TemporalAccessor) method. For example: 
  LocalDateTime dt = parser.parse(str, LocalDateTime::from);

当我了解@FunctionalInterface 是什么以及它的含义后,我开始想知道 API 的使用者是如何弄清楚他们可以实际使用什么来代替它的。上面的示例告诉您可以使用什么,如果您跟踪 java.time 包,您可以找到其他可以使用的方法引用。但是,API 的任何贡献者都需要通读整个 javadoc,以确保他们不会破坏其他地方提到的任何隐式契约(当然他们应该这样做,尤其是对于 JDK,但这不是 javadoc 的目的!)

所以.. 如果此 API 的贡献者要更改 LocalDateTime::from 的签名,则没有编译时检查表明此方法不再符合“TemporalQuery”的 FuncitonalInterface。这显然会破坏 API 的任何消费者,他们可以更改代码以使用显式 lambda。我确实理解它不需要,但如果有一个类似于可选的“@Override”注释的注释可用,那么它将提供一些编译时检查以及自省/反思以发现可用方法引用的可能性。

例如

@ConformsTo(TemporalQuery.class)
public static LocalDateTime from(TemporalAccessor temporal)

然后,还可以通过自省找到可用于功能接口的任何其他方法引用。

所以,需要明确的是,我理解这不是必需的,但确实认为不将其作为可选注释包含在内似乎是一种疏忽。这可能/不应该存在有什么特别的原因吗?

【问题讨论】:

  • 请注意,@FunctionalInterface允许你传递一个方法引用,它表明接口是意味着以这种方式使用。即使接口上缺少该注释,您也可以传递方法引用。
  • 是的,我确实希望使用“(基本上)”可以让我摆脱语义,但你说得对,我应该更清楚地说明:)
  • 您可以在代码中添加static { if(false) { TemporalQuery verify = LocalDateTime::from; } }...

标签: java lambda api-design functional-interface


【解决方案1】:

由于更改方法的签名或返回类型而引起的问题,例如LocalDateTime::from 不仅限于功能接口。甚至在 Java 8 更改这些东西之前就有可能破坏依赖这些东西的现有代码。这就是为什么设计 API 始终是一项挑战,因为更改现有代码可能意味着大量工作。

此外,假设功能接口和匹配方法是不同库的一部分,您真的希望它们紧密耦合,即当一个更改时两者都需要更改?如果它们由不同的组织(比如不同的开源项目或公司)维护,它们应该如何协调?

Comparator.comparing(Function&lt;? super T, ? extends U&gt; keyExtractor) 为例。这基本上接受对任何不带参数并返回可比较的方法的引用。有这么多库已经提供了这些方法,您是否希望它们都必须添加@ConformsTo

也就是说,@ConformsTo 充其量是不完整的,甚至可能会产生误导/过时。

编辑:

让我们从编译器的角度来处理这两个注解。

@FunctionalInterface 告诉编译器,当您定义多个抽象方法或在接口以外的其他东西上使用它时,它应该报错。

这意味着需求/合同定义(“这个接口是一个功能接口”)和实现(接口本身)包含在同一个文件中,因此无论如何都必须一起更改。

@ConformsTo 可以告诉编译器检查功能接口(甚至接口)的要求,并查看该方法是否满足它们。

到目前为止一切都很好,但是当接口更改时会出现问题:它会将方法和接口耦合在一起,这可能是不同且完全不相关的库的一部分。即使它们是同一个库的一部分,当方法本身不会被重新编译时,您也可能会遇到问题 - 编译器可能会错过这种不兼容性,从而违背该注释的目的(如果它仅适用于人类,那么一个简单的评论也足够了)。

【讨论】:

  • 我们一直在围绕咖啡机进行长时间的辩论。 LocalDateTime::from 不是也不应该仅限于该功能接口,但 JavaDoc 说它“可以”用于此目的。如果不引入显式依赖项,您将无法跨多个 API 编写此代码,这与仅实现其他人提供的接口没有什么不同。我的意思是,如果你开始编写 API,你可以提供一个明确的合同,而不仅仅是一条评论说“这将适用于现在”
  • @beirtipol 很好,考虑到方法和接口都是同一个库的一部分,可以提供一些关于如何使用它的提示。除此之外,函数式接口、lambda 等实际上与实现某些接口的旧方式并没有太大区别——它只是允许更好的语法。显式合约的问题在于,很难在多个库中真正执行它们,因此拥有它们可能会导致“错误的安全感”(因为没有更好的术语)。
  • 我只是在问它是否应该对单个库的维护者有用。我真的不是建议“必须”使用它。没有“必要”拥有这个。将“final”作为类级别关键字也没有“必要性”,但当您有多个 API 贡献者时,它确实很有用。
  • @beirtipol 实际上,final@ConformsTo 之间是有区别的:final 定义什么可以做,什么不能和你的用户api 不能改变这一点 - 你定义合同的规则。 @ConformsTo 会描述你打算履行一份合同,但你不是定义它的人,所以它可能会在你不知道或无法做些什么的情况下改变。
  • @beirtipol 我也不是说你说它必须被使用或不使用。您在问为什么它不存在,而我的回答是:它可能会造成更大的伤害(以误导或过时的信息形式)而不是好处(提供一组匹配方法)。事实上,无论如何您都需要使用 IDE,以便 IDE 也可以只扫描代码库并匹配符合所需签名和返回值的方法 - 无需开发人员添加和维护注释。 @FunctionalInterface 另一方面,当您声明多个抽象方法时,编译器会报错。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-01-10
  • 2012-02-02
  • 1970-01-01
相关资源
最近更新 更多