Kotlin's "inline" keyword

Kotlin has a bunch of new concepts for folks like me approaching it from a Java background. Among these is the inline keyword which can admittedly be pretty confusing if you don't already know what "inline" means in the context of a programming language.

What is inlining?

To "inline" a function basically means to copy a function's body and paste it at the function's call site. This happens at compile time.

When is this useful?

The inline keyword is useful for functions that accept other functions, or lambdas, as arguments.

Without the inline keyword on a function, that function's lambda argument gets converted at compile time to an instance of a Function interface with a single method called invoke(), and the code in the lambda is executed by calling invoke() on that Function instance inside the function body.

With the inline keyword on a function, that compile time conversion never happens. Instead, the body of the inline function gets inserted at its call site and its code is executed without the overhead of creating a function instance.

Hmm? Example please.

Let's say we have a function in an activity router class to start an activity and apply some extras (this is an Android blog after all).

fun startActivity(context: Context,
                  activity: Class<*>,
                  applyExtras: (intent: Intent) -> Unit) {
  val intent = Intent(context, activity)
  applyExtras(intent)
  context.startActivity(intent)
}

This function creates an intent, applies some extras by calling the applyExtras function argument, and starts the activity.

If we look at the compiled bytecode and decompile it to Java, this looks something like:

void startActivity(Context context,
                   Class activity,
                   Function1 applyExtras) {
  Intent intent = new Intent(context, activity);
  applyExtras.invoke(intent);
  context.startActivity(intent);
}

Let's say we call this from a click listener in an activity:

override fun onClick(v: View) {
  router.startActivity(this, SomeActivity::class.java) { intent ->
    intent.putExtra("key1", "value1")
    intent.putExtra("key2", 5)
  }
}

The decompiled bytecode for this click listener would then look like something like this:

@Override void onClick(View v) {
  router.startActivity(this, SomeActivity.class, new Function1() {
    @Override void invoke(Intent intent) {
      intent.putExtra("key1", "value1");
      intent.putExtra("key2", 5);
    }
  }
}

A new instance of Function1 gets created every time the click listener is triggered. This works fine, but it's not ideal!

Now let's just add inline to our activity router method:

inline fun startActivity(context: Context,
                         activity: Class<*>,
                         applyExtras: (intent: Intent) -> Unit) {
  val intent = Intent(context, activity)
  applyExtras(intent)
  context.startActivity(intent)
}

Without changing our click listener code at all, we're now able to avoid the creation of that Function1 instance. The Java equivalent of the click listener code would now look something like:

@Override void onClick(View v) {
  Intent intent = new Intent(context, SomeActivity.class);
  intent.putExtra("key1", "value1");
  intent.putExtra("key2", 5);
  context.startActivity(intent);
}

That's it!

Inline functions are not super complicated and can make our code more efficient without much extra work for us.

Of course, this is just the tip of the iceberg! Check out the Kotlin docs for more on inline functions.