我正在做一些有趣的编码,我想知道我应该使用哪一个。两者都试过了,结果都一样。那么,两者之间有什么区别呢?
示例:
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
接受另一个函数。标准库有一组作用域函数(run
、apply
、let
、also
),它们显示了差异。
假设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
,并且如前所示,签名是相同的。