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

hot*_*key 52 code-structure kotlin

在Kotlin中,具有至少一个参数的函数可以定义为常规非成员函数,也可以定义为扩展函数,其中一个参数是接收者.

对于范围界定,似乎没有区别:两者都可以在类和其他函数内部或外部声明,并且两者都可以或不可以具有相同的可见性修饰符.

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

所以,我的问题是:扩展功能何时优于常规非成员功能?当常规的扩展?

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

它只是一个函数语义的提示(接收器肯定是焦点)或是否有使用扩展函数使代码更清洁/开辟机会的情况?

Jay*_*ard 57

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

惯用案例:

  1. 当您想要增强,扩展或更改现有API时.扩展函数是通过添加新功能来更改类的惯用方法.您可以添加扩展功能扩展属性.请参阅Jackson-Kotlin模块中的示例,以便在ObjectMapper类中添加简化处理TypeReference和泛型的方法.

  2. 为无法在a上调用的新方法或现有方法添加null安全性null.例如,String of的扩展函数String?.isNullOrBlank()允许您甚至在nullString 上使用该函数,而无需先进行自己的null检查.函数本身在调用内部函数之前进行检查.请参阅Nullable Receiver的扩展文档

强制性案件:

  1. 如果需要接口的内联默认函数,则必须使用扩展函数将其添加到接口,因为在接口声明中不能这样做(内联函数必须final在接口中当前不允许).当您需要内联的已知函数时,这非常有用,例如来自Injekt的此代码

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

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

可选案例:

  1. 您希望控制调用者可以看到某些内容的范围,因此您只能在允许调用可见的上下文中扩展该类.这是可选的,因为您可以只允许一直看到扩展名. 对于范围扩展函数,请参阅其他SO问题中的答案

  2. 您有一个界面,您希望简化所需的实现,同时仍然为用户提供更简单的帮助程序功能.您可以选择为接口添加默认方法以提供帮助,或使用扩展功能添加接口的非预期实现部分.一个允许覆盖默认值,另一个不允许(扩展与成员的优先级除外).

  3. 当您想要将功能与功能类别相关联时; 扩展函数使用它们的接收器类作为查找它们的位置.它们的名称空间成为可以触发它们的类(或类).而顶级函数将更难找到,并将填充IDE代码完成对话框中的全局名称空间.您还可以修复现有的库名称空间问题.例如,在Java 7中,您拥有Path该类,并且很难找到该Files.exist(path)方法,因为它奇怪地名称间隔.该功能可以直接放在上面Path.exists().(@kirill)

优先规则:

扩展现有类时,请记住优先级规则.它们在KT-10806中描述为:

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

  • 由于您拥有最完整的答案,因此您可以添加以下额外好处:扩展功能不会丢弃全局命名空间.如果您在任何代码块内并调用Ctrl + Space,则将列出所有顶级函数.扩展函数不是这样,只会在正确键入的接收器上列出方法调用. (3认同)

Kir*_*man 8

扩展功能与安全呼叫操作员配合得非常好?..如果您希望函数的参数有时是null,而不是提前返回,则使其成为扩展函数的接收者.

普通功能:

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

    return s.substring(from, to)
}
Run Code Online (Sandbox Code Playgroud)

扩展功能:

fun String.extensionSubstring(from: Int, to: Int) = substring(from, to)
Run Code Online (Sandbox Code Playgroud)

通话网站:

fun main(args: Array<String>) {
    val s: String? = null

    val maybeSubstring = nullableSubstring(s, 0, 1)
    val alsoMaybeSubstring = s?.extensionSubstring(0, 1)
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,两者都做同样的事情,但是扩展功能更短,并且在呼叫站点上,立即清楚结果将是可空的.


byt*_*efu 5

至少有一种情况必须使用扩展函数 - 调用链,也称为“流畅风格”:

foo.doX().doY().doZ()
Run Code Online (Sandbox Code Playgroud)

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

doZ(doY(doX(someStream())))
Run Code Online (Sandbox Code Playgroud)

显然,您想为此使用扩展函数。此外,您不能使普通函数中缀,但您可以使用扩展函数来做到这一点:

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))
}
Run Code Online (Sandbox Code Playgroud)

  • 这真的是“当您想要更改无法控制的现有 API 时”的一般情况吗? (2认同)