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)
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) }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With