首页 文章

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

提问于
浏览
39

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

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

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

所以,我的问题是: when do extension functions give advantage over regular non-member ones? 当常规的超过扩展时?

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

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

4 回答

  • 43

    有些情况下你 have 使用扩展方法 . 例如 . 如果你有一些列表实现 MyList<T> ,你可以写一个扩展方法,如

    fun Int MyList<Int>.sum() { ... }
    

    将其写成“正常”方法是不可能的 .

  • 0

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

    Idiomatic Cases:

    • 当您想要增强,扩展或更改现有API时 . 扩展函数是通过添加新功能来更改类的惯用方法 . 您可以添加extension functionsextension properties . 请参阅Jackson-Kotlin Module中的示例,以便向 ObjectMapper 类添加方法,从而简化 TypeReference 和泛型的处理 .

    • 为无法在 null 上调用的新方法或现有方法添加空安全性 . 例如, String?.isNullOrBlank() 字符串的扩展函数允许您甚至在 null 字符串上使用该函数,而无需先进行自己的 null 检查 . 函数本身在调用内部函数之前进行检查 . 见documentation for extensions with Nullable Receiver

    Mandatory Cases:

    • 如果需要接口的内联默认函数,则必须使用扩展函数将其添加到接口,因为在接口声明中不能这样做(内联函数必须是 final ,当前在接口中不允许) . 当您需要内联实现的函数时,这非常有用,for example this code from Injekt

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

    • 将运算符添加到现有类,例如 +* (#1的特化,但不能以任何其他方式执行此操作,因此是必需的) . 见documentation for operator overloading

    Optional Cases:

    • 您希望控制调用者可以看到某些内容的作用域,因此您只能在允许调用可见的上下文中扩展该类 . 这是可选的,因为您可以只允许一直看到扩展名 . see answer in other SO question for scoping extension functions

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

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

    Precedence Rules:

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

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

  • 8

    扩展功能与安全呼叫操作员 ?. 非常适合 . 如果您希望函数的参数有时为 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)
    

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

  • 4

    至少有一种情况是扩展函数是必须调用的链接,也称为“流畅的样式”:

    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))
    }
    

相关问题