【问题标题】:When should one prefer Kotlin extension functions?什么时候应该更喜欢 Kotlin 扩展函数?
【发布时间】:2016-05-20 23:11:42
【问题描述】:

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

至于作用域,似乎没有区别:两者都可以在类和其他函数内部或外部声明,并且两者都可以或不能具有同等的可见性修饰符。

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

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

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

这只是函数语义的暗示(接收器绝对是焦点)还是存在使用扩展函数使代码更清晰或开辟机会的情况?

【问题讨论】:

  • 这可能是见仁见智,但经典的例子是 list.sort() 而不是 Collections.sort(list):静态实用函数可以成为扩展函数。
  • @nhaarman 关于这个话题的有趣观点——并不是每个实用函数都应该成为扩展:medium.com/@dimsuz/…
  • @Ilya,感谢您的链接,提示似乎足以让您在心里记下。 :)

标签: kotlin code-structure


【解决方案1】:

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

惯用案例:

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

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

强制案例:

  1. 当你想要一个接口的内联默认函数时,你必须使用扩展函数将它添加到接口,因为你不能在接口声明中这样做(内联函数必须是final,这目前不是允许在接口内)。这在您需要内联具体化函数时很有用,for example this code from Injekt

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

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

可选案例:

  1. 您希望控制调用者何时可以看到某些内容的范围,因此您只能在允许调用可见的上下文中扩展类。这是可选的,因为您可以只允许始终看到扩展名。 see answer in other SO question for scoping extension functions

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

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

优先规则:

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

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

【讨论】:

  • 既然您有最完整的答案,那么您可以添加一个额外的好处:扩展函数不会乱扔全局命名空间。如果您在任何代码块中并调用 Ctrl + Space,则将列出所有顶级函数。扩展函数并非如此,它只会在正确类型的接收器上的方法调用中列出。
【解决方案2】:

扩展功能与安全呼叫运算符?. 配合得非常好。如果您希望函数的参数有时会是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)

如您所见,两者都做同样的事情,但扩展功能更短,在调用站点上,结果可以立即为空。

【讨论】:

  • 请添加一个例子,我不确定你在描述什么。
【解决方案3】:

至少有一种情况是扩展函数是必须的——调用链,也称为“流利的风格”:

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

【讨论】:

  • 这真的是“当您想要更改您无法控制的现有 API 时”的一般情况吗?
  • 虽然很酷,但实际上并不能编译。在 1.0.3 我得到:Error:Kotlin: [Internal Error] java.lang.IllegalStateException: java.io.FileNotFoundException: D:\...\ExampleKt$|$1.class (The filename, directory name, or volume label syntax is incorrect) at org.jetbrains.kotlin.codegen.CompilationErrorHandler$1.reportException(CompilationErrorHandler.java:27) at org.jetbrains.kotlin.codegen.PackageCodegen.generate(PackageCodegen.java:69)
  • 给以后的读者:代码没有问题,消息明确说Kotlin编译器找不到源文件:java.io.FileNotFoundException: D:\...\ExampleKt$|$1.class (The filename, directory name, or volume label syntax is incorrect)
【解决方案4】:

在某些情况下,您必须使用扩展方法。例如。如果你有一些列表实现MyList&lt;T&gt;,你可以写一个扩展方法,比如

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

不可能把它写成“正常”的方法。

【讨论】:

  • 基本上,问题更多的是关于何时以及为什么我们应该更喜欢fun MyList&lt;Int&gt;.sum() 而不是fun sum(list: MyList&lt;Int&gt;),而不是扩展与成员函数。
猜你喜欢
  • 1970-01-01
  • 2011-05-10
  • 2018-02-09
  • 2022-01-08
  • 1970-01-01
  • 2017-12-21
  • 1970-01-01
  • 2020-07-19
  • 2013-03-25
相关资源
最近更新 更多