Kotlin扩展函数-任何函数之间的区别?和通用T



我正在做一些有趣的编码,我想知道我应该使用哪一个。两者都试过了,结果都一样。那么,两者之间有什么区别呢?

示例:

fun Any?.foo() = this != null
fun <T> T?.foo() = this != null

实际的函数有点复杂,它实际上是根据对象的真实类型来做一些事情的(比如有一些选项的when)

第二个函数为您提供了一个在这种特殊情况下不使用的机会:它将接收器的类型捕获到类型参数T中,这样您就可以在签名的其他地方使用它,比如在参数类型或返回值类型中,或者在函数体中。

作为一个相当综合的例子,第二个函数内的listOf(this, this)将被类型化为List<T?>,保留了项目类型与接收器类型相同的知识,而第一个函数中的相同表达式将是List<Any?>

第一个函数不允许您一般使用接收器类型来存储此类型的项,接受相同类型的附加项作为参数,或在函数的返回值类型中使用接收器类型,而第二个函数允许所有这些。

从运行时的角度来看,这些函数是等效的,因为在编译代码时,泛型会从JVM字节码中删除,因此,除非将函数转换为具有reified类型参数的inline函数,否则您将无法在运行时确定类型T并根据它采取行动。


作为一个非常重要的特殊情况,将类型从调用站点捕获到类型参数中允许高阶函数在其签名中使用T接受另一个函数。标准库有一组作用域函数(runapplyletalso),它们显示了差异。

假设also的签名没有使用泛型,看起来像这样:

fun Any?.also(block: (Any?) -> Unit): Any? { ... }

这个函数可以在任何对象上调用,但它的签名并不能表明它是传递给block并从函数返回的接收器对象——编译器将无法确保类型安全,例如,允许在没有类型检查的情况下调用接收器对象的成员:

val s: String = "abc"
// won't compile: `it` is typed as `Any?`, the returned value is `Any?`, too
val ss1: String = (s + s).also { println(it.length) } 
// this will work, but it's too noisy
val ss2: String = (s + s).also { println((it as String).length) } as String 

现在,捕获type参数正是显示它在所有三个地方都是相同类型的方法。我们修改签名如下:

fun <T : Any?> T.also(block: (T) -> Unit): T { ... }

编译器现在可以推断类型,因为它在任何地方都是相同的类型T

val s: String = "abc"
// OK!
val ss: String = (s + s).also { println(it.length) } 

如果在JVM上运行此程序,您将获得以下

java.lang.ClassFormatError:重复的方法名&课堂签名文件

这很有趣,所以从签名的角度来看,它们是相同的。

它在很大程度上取决于您的用例,但在大多数情况下,您可能希望使用泛型变体,因为类型可能是可变的,但在编译时是固定的。这种优势在这里变得显而易见:

fun Any?.foo() = this
fun <T> T?.bar() = this
fun main(args: Array<String>) {
val x = 5.foo() // Any?
val y = 5.bar() // Int?
}

Int?上可用的所有属性和函数都不能用于x,除非我明确地将其强制转换为(和Int?)。y则"知道"它返回了一个Int?


在您的示例中,这不会有什么不同,因为您将始终返回Boolean,并且如前所示,签名是相同的。

最新更新