In this tutorial, we’ll be looking into Kotlin inline function. We’ll follow that with Reified Type Parameters.
Kotlin inline functions
We’ve discussed Kotlin Higher Order Functions and Lambda Expressions before.
They’re super useful in passing functions as parameters. But these functions are objects which have there own callbacks and subsequently memory allocations. Let’s understand how these functions passed as parameters internally work through an example.
1 2 3 4 5 6 7 8 9 10 |
fun main(args: Array<String>) { println("Hey how are you doing") sampleFunction("JournalDev.com", ::println) } fun sampleFunction(str: String, expression: (String) -> Unit) { println("This is Kotlin Inline Functions Tutorial") expression(str) } |
sampleFunction
when run passes println
as the parameter. Now Kotlin is a JVM based language so everything is converted into bytecode. Let’s look at the bytecode of the above code by going to Tools | Kotlin | Show Bytecode.
The main part expression.invoke()
. Invoking the lambda expression (println) would create an additional call and hence memory. The invoke method looks like this in Java:
1 2 3 4 5 6 7 8 |
expression(new Function() { @Override public void invoke() { //println statement is called here. } }); |
Now if we call multiple functions as parameters each of them would add up to the method count and have a HUGE impact on the memory and the performance.
Inline Functions to the rescue!
Inline functions flatten the function calls by providing the function body and everything to the calling function at runtime. We need to add the inline
modifier to do so.
So the sampleFunction
above would look like the below code when the sampleFunction is called.
1 2 3 4 5 6 |
inline fun sampleFunction(str: String, expression: (String) -> Unit) { print("This is Kotlin Inline Functions Tutorial") expression(str) } |
inline
keyword copies the function to the call site. This saves the additional object creation to invoke the parameter function thus saving memory for you.
Let’s look at the bytecode now by decompiling.
Take note: The println
lambda expression is expanded in the main function itself in the form of System.out.println. NO more additional calls needed.
Why not make every function inlined?
- Inlining a function copies the code into one place thereby increasing the generated code. It should be avoided when the function to be invoked through parameter has a big code already.
- Also, inlined functions can’t access
private
members of the enclosed class. You’ll have to set them asinternal
An example using inline functions is given below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
fun main(args: Array<String>) { normalFunction() } fun normalFunction() { println("This is normal function.") inlineFunctionExample({ println("Inlined Functions")},{ println("Instead of object creation it copies the code.")} ) } inline fun inlineFunctionExample(myFunction: () -> Unit, another: () -> Unit ) { myFunction() another() print("Finally it's working fine!") } |
In the above code, we’ve passed many lambda expressions.
All of these would be copied at runtime.
Following is the generated bytecode in our IntelliJ:
All the println
lambda calls are flattened in the normalFunction
itself. The output takes lesser memory.
Inline Allows Non-Local Control Flow
With inline functions, you can return from the lambda expression itself and it’ll exit the function in which inline function was called. The below snippet demonstrates the same.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
fun main(args: Array<String>) { normalFunction() } fun normalFunction() { println("This is normal function.") inlineFunctionExample({ println("Inlined Functions") return},{ println("Instead of object creation it copies the code.")} ) println("This is normal function closing") } inline fun inlineFunctionExample(myFunction: () -> Unit, another: () -> Unit ) { myFunction() another() print("Finally it's working fine!") } |
The output that gets printed is :
As you can see, the inlined function is exited as well as its enclosing function.
We cannot return from lambda expressions that are a part of normal functions(non inline functions).
To prevent this, we can mark the lambda expression as crossinline
. It’ll throw a compiler error if it sees a return statement inside that lambda expression.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
fun normalFunction() { println("This is normal function.") inlineFunctionExample( { println("Inlined Functions") return //compiler error here }, { println("Instead of object creation it copies the code.")} ) println("This is normal function closing") } inline fun inlineFunctionExample(crossinline myFunction: () -> Unit, another: () -> Unit ) { myFunction() another() print("Finally it's working fine!") } |
noinline
noinline
modifier is used to set expressions not to be inlined
in the call.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
fun main(args: Array<String>) { normalFunction() } fun normalFunction() { println("This is normal function.") inlineFunctionExample({ println("Inlined Functions")}, { println("Instead of object creation it copies the code.")} ) println("This is normal function closing") } inline fun inlineFunctionExample(myFunction: () -> Unit, noinline another: () -> Unit ) { myFunction() another() print("Finally it's working fine!") } |
anotherlambda expression won't be inlined.
noinline
expressions don’t support non-local control flow. Setting a return statement in them would throw a compiler error.
Inline Properties
Kotlin inline keyword is allowed on properties too. Just like inline functions, it’ll copy the inline properties accessor methods to the call site.
Inline properties cannot have a backing field
.
1 2 3 4 5 6 7 8 9 |
fun main(args: Array<String>) { print(x) } var i = 10 inline var x: Boolean get() = i == 11 set(x) { x} |
We can set inline separately on the get and set methods too.
Reified Type Parameters
The type of a parameter cannot be retrieved in code since it’s erased at runtime. For inline functions though it is possible using Reified.
To retrieve the type of a parameter, we can set a reified
modifier against it.
Why does this work?
Inline functions copy the complete function at runtime, hence the type of the parameters is also available provided we’ve set reified against it.
1 2 3 4 5 6 7 8 |
fun main(args: Array<String>) { getT<Double>() } inline fun <reified T> getT() { print(T::class) } |
The following output gets printed:
That’s all for kotlin inline functions tutorial, we will look into more Kotlin features in future tutorials.