我正在做一些有趣的编码,我想知道应该使用哪个编码。尝试了两个,他们给了我相同的结果。那么,两者有什么区别?
示例:
fun Any?.foo() = this != null
fun <T> T?.foo() = this != null
实际功能要复杂一些,它实际上根据对象的实际类型(例如带有某些选项的when
)执行某项操作
答案 0 :(得分:6)
第二个函数为您提供了在这种特殊情况下不使用的机会:它将捕获的接收器的类型捕获到类型参数#temp
中,以便您可以在某个地方使用它其他在签名中,例如在参数类型或返回值类型中,或在函数主体中。
作为一个非常综合的示例,第二个函数内的T
将被键入为listOf(this, this)
,从而保留了以下知识:项目类型与接收者类型相同,而第一个功能是List<T?>
。
第一个函数不允许您通常使用接收器类型来存储该类型的项目,不能接受与参数相同类型的其他项目,也不能在函数的返回值类型中使用接收器类型,而第二个函数允许所有这些。
从运行时的角度来看,这些函数与generics are erased from the JVM bytecode等效,因此在编译代码时,它们将无法在运行时确定类型List<Any?>
并依赖于它执行操作,除非您将函数转换为inline
function with a reified
type parameter。
作为一个非常重要的特殊情况,将来自呼叫站点的类型捕获到类型参数中可以使高阶函数在其签名中使用T
来接受另一个函数。标准库具有一组作用域函数(T
,run
,apply
,let
),它们显示出不同之处。
假设also
的签名未使用泛型,并且看起来像这样:
also
可以在任何对象上调用此函数,但是其签名并不表示它是传递给fun Any?.also(block: (Any?) -> Unit): Any? { ... }
并从函数返回的接收者对象–编译器将无法确保类型安全,例如,允许在不进行类型检查的情况下调用接收方对象的成员:
block
现在,捕获type参数正是显示这三个地方都是相同类型的方式。我们对签名进行如下修改:
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
编译器现在可以推断出类型,知道它出现的地方都是相同的类型fun <T : Any?> T.also(block: (T) -> Unit): T { ... }
:
T
答案 1 :(得分:1)
如果您在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
,除非我明确地将其强制转换为(to和Int?
)。另一方面,y
“知道”它返回了Int?
。
在您的示例中,这没有什么区别,因为您将始终返回Boolean
,并且如前所示,签名是相同的。