Kotlin extension functions - Difference between An

2019-02-25 11:02发布

问题:

I was doing some coding for fun and I was wonder which one should I use. Tried both and they gave me the same result. So, what's the difference between the two?

Examples:

fun Any?.foo() = this != null

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

The actual function is a bit more complicated and it actually does something based on the real type of the object (like a when with some options)

回答1:

The second function gives you an opportunity that is not used in this particular case: it captures the type of the receiver into the type parameter T, so that you may use it somewhere else in the signature, like in the parameter types or the return value type, or in the function body.

As a quite synthetic example, a listOf(this, this) inside the second function would be typed as List<T?>, retaining the knowledge that the items type is the same as the receiver type, while the same expression in the first function would be a List<Any?>.

The first function does not allow you to use the receiver type generically for storing items of this type, accepting additional items of the same type as parameters or using the receiver type in the return value type of the function, while the second function allows all of these.

These functions are equivalent from the runtime perspective, as generics are erased from the JVM bytecode when the code is compiled, so you won't be able to determine the type T at runtime and act depending on it, unless you convert the function to an inline function with a reified type parameter.


As a very important special case, capturing the type from a call site into a type parameter allows a higher-order function to accept another function using T in its signature. The standard library has a set of scoping functions (run, apply, let, also) which show the difference.

Suppose that the signature of also did not use generics and looked like this:

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

This function can be called on any object, but its signature doesn't show that it's the receiver object that is passed to the block and returned from the function – the compiler won't be able to ensure type safety and, for example, allow a call to a member of the receiver object without a type check:

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 

Now, capturing the type parameter is exactly the way to show that it is the same type in all the three places. We modify the signature as follows:

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

And the compiler can now infer the types, knowing that it's the same type T everywhere it appears:

val s: String = "abc"

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


回答2:

If you run this on the JVM you will get the following

java.lang.ClassFormatError: Duplicate method name&signature in class file ...

This is interesting, so from a signature standpoint they are identical.

It strongly depends on your use case, but in most cases you may want to use the generic variant, because the type may be variable but fixed at compile-time. This advantage becomes obvious here:

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

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

All properties and functions available on Int? won't be usable for x until I explicitely cast it (to and Int?). y on the other hand "knows" that it returned an Int?.


In your example it wouldn't make a difference since you will always return a Boolean and as already shown, the signature are the same.



标签: kotlin