Image created using photopea
Let’s look at a simple function with a functional type parameter in Kotlin. For simplicity, I have created this method which accepts two parameters:
- eventName of Type String
- completionListener of Type function which returns Unit.
fun insertEvent(eventName: String, completionListener: () -> Unit) { eventRepository.insert(eventName) // Consider this a delayed event completionListener.invoke() }
Let’s convert this to Kotlin byte code and decompile it to the Java version.
Tools -> Kotlin -> Show Byte code -> Decompile (How To Link)
public final void insertEvent(@NotNull String eventName, @NotNull Function0 completionListener) { Intrinsics.checkNotNullParameter(eventName, "eventName"); Intrinsics.checkNotNullParameter(completionListener, "completionListener"); this.eventRepository.insert(eventName); completionListener.invoke(); }
Let’s break down each parameter:
- eventName – the data type is still a String and it’s annotated with @NonNull annotation in java — this param looks straightforward.
- completionListener — the data type looks weird its called Function0 ?? 🤔
What is Function0 if you do a control-click on Function0 it will navigate to Functions.kt
Let’s look at what Functions.kt contains:
public interface Function0<out R> : Function<R> { public operator fun invoke(): R } public interface Function1<in P1, out R> : Function<R> { public operator fun invoke(p1: P1): R } public interface Function2<in P1, in P2, out R> : Function<R> { public operator fun invoke(p1: P1, p2: P2): R } . . .
Function0 is nothing but an interface with invoke method that has 0 params.
Function1 is another interface with invoke method that has 1 param
Function2 is another interface with invoke method that has 2 params
and it goes on up until Function22
So under that hood, our higher-order function completionListener has been replaced by Function0 by Kotlin. If you know generics a little you should be able to picture what P1, P2, … and R are in Functions.kt
P1, P2, … are the generic types for the parameters of completionListener
R is the return generic type for the return type of the function in our case completionListener’s return type
If you still can’t picture what’s happening try calling the same function that you created in Kotlin from a Java file you can picture how it works.
Since Kotlin is designed with Java interoperability in mind, existing Java code can be called from Kotlin in a natural way, and Kotlin code can be used from Java rather smoothly as well.
//Calling the same method from Java world public static void main(String[] args){ // Calling the same insertEvent from java world eventHelper.insertEvent("test", new Function0<Unit>() { @Override public Unit invoke() { return null; } }); }
Job Offers
As you can see above, we have to create an anonymous inner class for the Function0 interface to get the callback in the Java world
Ok, let’s alter completionListener and add a param:
// removed the eventName param fun insertEvent(completionListener: (test: String) -> Unit) { completionListener.invoke("success") }
This is the generated code:
public final void insertEvent(@NotNull Function1 completionListener) { Intrinsics.checkNotNullParameter(completionListener, "completionListener"); completionListener.invoke("success"); }
Function0 is now replaced with Function1 to support the String param.
Hope this blog helped you understand how higher-order functions work under the hood.
Some Bonus Knowledge:
Ok, will Kotlin support any number of parameters in our higher-order functions? I got this question as I noticed something weird in Functions.kt, there are Function interfaces up to Function22. What will happen if I have more than 22 parameters? Will Kotlin throw a compile error? Let’s see…
Let’s alter our completionListener to have 23 parameters. 😅
// I have removed the string param and replaced invoke method with a println. fun insertEvent( completionListener: ( test1: String, test2: String, test3: String, test4: String, test5: String, test6: String, test7: String, test8: String, test9: String, test10: String, test11: String, test12: String, test13: String, test14: String, test15: String, test16: String, test17: String, test18: String, test19: String, test20: String, test21: String, test22: String, test23: String, ) -> Unit ) { println("Just Printing dont want to invoke it :sweat:") }
Lets decompile …
public final void insertEvent(@NotNull FunctionN completionListener) { Intrinsics.checkNotNullParameter(completionListener, "completionListener"); String var2 = "Just Printing dont want to invoke it :sweat:"; System.out.println(var2); }
Wow, now there is something called FunctionN. I’ll let you explore what is that.
This article was originally published on proandroiddev.com on January 01, 2023