什么时候应该喜欢Kotlin扩展功能?

在Kotlin中,一个至少有一个参数的函数可以被定义为一个常规的非成员函数,或者被定义为一个参数为接收者的扩展函数 。

对于范围界定,似乎没有区别:既可以在类内部也可以在其他功能之外进行声明,并且都可以或不可以具有可见性修饰符。

语言参考似乎不建议在不同的情况下使用常规函数或扩展函数。

所以,我的问题是: 什么时候扩展函数比普通的非成员函数更有优势? 而当普通的扩展?

foo.bar(baz, baq) vs bar(foo, baz, baq)

这只是一个函数语义的提示(接收器肯定是焦点),或者在使用扩展函数的情况下,代码更清洁/开放的机会吗?

扩展功能在少数情况下是有用的,在其他情况下是强制性的:

习惯用语:

  1. 当你想增强,扩展或改变现有的API。 扩展功能是通过添加新功能来改变类的惯用方法。 您可以添加扩展功能和扩展属性 。 在Jackson-Kotlin模块中查看一个示例,将方法添加到ObjectMapper类中,简化了TypeReference和泛型的处理。

  2. 将null安全性添加到无法在null上调用的新方法或现有方法。 例如String的扩展函数String?.isNullOrBlank()允许你使用那个函数,即使在一个null字符串中,也不必先做自己的null检查。 调用内部函数之前,函数本身会执行检查。 有关可扩展接收器的扩展参见文档

强制性案例:

  1. 当需要接口的内联缺省函数时,必须使用扩展函数将其添加到接口中,因为在接口声明中不能这样做(内联函数必须是final ,而在接口中当前不允许)。 当你需要内联的函数时,这是很有用的, 例如Injekt的代码

  2. 当你想添加for (item in collection) { ... }支持到当前不支持该用法的类。 您可以添加一个遵循for循环文档中描述的规则的iterator()扩展方法 – 即使返回的类似迭代器的对象也可以使用扩展来满足提供next()hasNext()的规则。

  3. 将运算符添加到现有的类(如+* (#1的专门化,但不能以其他任何方式执行此操作,因此是必需的)。 请参阅运营商超载的文档

可选案例:

  1. 您希望控制调用者可见的东西的范围,所以只能在允许调用可见的上下文中扩展该类。 这是可选的,因为你可以让扩展一直被看到。 在其他SO问题中查看范围扩展功能的答案

  2. 你有一个接口,你想简化所需的实现,同时仍然允许更多的帮助功能的用户。 您可以选择添加接口的默认方法来提供帮助,或者使用扩展函数来添加接口的不可预期的部分。 一个允许覆盖默认值,另一个不允许(扩展名和成员的优先级除外)。

  3. 当你想把功能与一类功能联系起来时, 扩展函数使用它们的接收器类作为从中找到它们的地方。 他们的名字空间成为他们可以触发的类(或类)。 而顶级函数将很难找到,并填充IDE代码完成对话框中的全局名称空间。 您还可以修复现有的库名称空间问题。 例如,在Java 7中,您有Path类,并且很难找到Files.exist(path)方法,因为它的名称间距很奇怪。 该函数可以直接放在Path.exists() 。 (@kirill)

优先规则:

在扩展现有类时,要考虑优先规则。 它们在KT-10806中被描述为:

对于当前上下文中的每个隐式接收者,我们尝试成员,然后尝试本地扩展函数(也是具有扩展函数类型的参数),然后是非本地扩展。

至少有一种情况下,扩展功能是一个必须调用链接,也被称为“流畅的风格”:

 foo.doX().doY().doZ() 

假设你想用你自己的操作从Java 8扩展Stream接口。 当然,你可以使用普通的函数,但它看起来很丑陋:

 doZ(doY(doX(someStream()))) 

显然,你想使用扩展功能。 另外,你不能使普通函数中缀,但你可以用扩展函数来完成它:

 infix fun <A, B, C> ((A) -> B).`|`(f: (B) -> C): (A) -> C = { a -> f(this(a)) } @Test fun pipe() { val mul2 = { x: Int -> x * 2 } val add1 = { x: Int -> x + 1 } assertEquals("7", (mul2 `|` add1 `|` Any::toString)(3)) } 

安全呼叫操作员的扩展功能真的很好?. 。 如果你期望函数的参数有时是null ,而不是提前返回,那么把它作为扩展函数的接收器。

普通功能:

 fun nullableSubstring(s: String?, from: Int, to: Int): String? { if (s == null) { return null } return s.substring(from, to) } 

扩展功能:

 fun String.extensionSubstring(from: Int, to: Int) = substring(from, to) 

呼叫地点:

 fun main(args: Array<String>) { val s: String? = null val maybeSubstring = nullableSubstring(s, 0, 1) val alsoMaybeSubstring = s?.extensionSubstring(0, 1) 

正如你所看到的,两者都做同样的事情,但扩展功能更短,并在呼叫网站,立即清楚,结果是可空的。 }