Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Kotlin extension functions - Difference between Any? and Generic T?

Tags:

kotlin

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)

like image 552
SkiFire13 Avatar asked Mar 05 '23 18:03

SkiFire13


1 Answers

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) } 
like image 68
hotkey Avatar answered Apr 30 '23 14:04

hotkey