在Kotlin中,具有至少一个参数的函数可以定义为常规非成员函数,也可以定义为extension function,其中一个参数是接收者 .
对于范围界定,似乎没有区别:两者都可以在类和其他函数内部或外部声明,并且两者都可以或不可以具有相同的可见性修饰符 .
语言参考似乎不建议在不同情况下使用常规函数或扩展函数 .
所以,我的问题是: when do extension functions give advantage over regular non-member ones? 当常规的超过扩展时?
foo.bar(baz, baq)
vs bar(foo, baz, baq)
.
它只是一个函数语义的提示(接收器肯定是焦点)或是否有使用扩展函数使代码更清洁/开辟机会的情况?
4 回答
有些情况下你 have 使用扩展方法 . 例如 . 如果你有一些列表实现
MyList<T>
,你可以写一个扩展方法,如将其写成“正常”方法是不可能的 .
扩展功能在少数情况下很有用,在其他情况下是强制性的:
Idiomatic Cases:
当您想要增强,扩展或更改现有API时 . 扩展函数是通过添加新功能来更改类的惯用方法 . 您可以添加extension functions和extension properties . 请参阅Jackson-Kotlin Module中的示例,以便向
ObjectMapper
类添加方法,从而简化TypeReference
和泛型的处理 .为无法在
null
上调用的新方法或现有方法添加空安全性 . 例如,String?.isNullOrBlank()
字符串的扩展函数允许您甚至在null
字符串上使用该函数,而无需先进行自己的null
检查 . 函数本身在调用内部函数之前进行检查 . 见documentation for extensions with Nullable ReceiverMandatory Cases:
如果需要接口的内联默认函数,则必须使用扩展函数将其添加到接口,因为在接口声明中不能这样做(内联函数必须是
final
,当前在接口中不允许) . 当您需要内联实现的函数时,这非常有用,for example this code from Injekt如果要将
for (item in collection) { ... }
支持添加到当前不支持该用法的类 . 您可以添加遵循for loops documentation中描述的规则的iterator()
扩展方法 - 即使返回的类迭代器对象也可以使用扩展来满足提供next()
和hasNext()
的规则 .将运算符添加到现有类,例如
+
和*
(#1的特化,但不能以任何其他方式执行此操作,因此是必需的) . 见documentation for operator overloadingOptional 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中描述为:
扩展功能与安全呼叫操作员
?.
非常适合 . 如果您希望函数的参数有时为null
,而不是提前返回,则将其作为扩展函数的接收者 .普通功能:
扩展功能:
通话网站:
正如您所看到的,两者都做同样的事情,但是扩展功能更短,并且在呼叫站点上,立即清楚结果将是可空的 .
至少有一种情况是扩展函数是必须调用的链接,也称为“流畅的样式”:
假设您希望使用自己的操作从Java 8扩展Stream接口 . 当然,你可以使用普通的功能,但它看起来很难看地狱:
显然,您希望使用扩展功能 . 此外,您不能使普通函数中缀,但您可以使用扩展函数: