· 8 min read

Calling Kotlin from Java: start using Kotlin today (KAD 29)

One of the great wonders of Kotlin is that it’s fully integrated with Java. This means that although all your application code is written Java, you can create a class in Kotlin and use it from Java without any issues. Calling Kotlin from Java code can’t be easier.

This potentially gives you two advantages:

  • You can use Kotlin in a Java project: In any project you have already started, you can decide to start writing the new code in Kotlin. You can then call it from Java code.
  • If you have a mental block in Kotlin, you can do that part in Java: many people ask me if there is a case where Kotlin won’t be enough to do something on Android. In theory, everything can be done, but the fact is that it doesn’t matter. If you can’t do it in Kotlin, you can implement that part in Java.

Today we’re going to see how this compatibility works, and how Kotlin code looks when used from Java. You can also watch this video where I cover the topic:

https://www.youtube.com/watch?v=9PF3PnrXZs4

If you want to learn what Kotlin’s situation is in the market, why you should learn it and the first steps to see how easy it is, I have prepared a free hour and a half training that you can join by clicking here.

Action View

Calling Kotlin from Java: Package-level functions

In Kotlin, functions don’t need to be inside a class, but this isn’t the case in Java. How can we call a function then? Imagine that we have a file utils.kt that looks like this:

fun logD(message: String) {
    Log.d("", message)
}

fun logE(message: String) {
    Log.e("", message)
}

In Java we can access them through a class that will be called UtilsKt, with some static methods:

UtilsKt.logD("Debug");
UtilsKt.logE("Error");

Extension functions

You’ve already seen from previous articles that I love extension functions. But how do they look in Java? Imagine that we have the following:

fun ViewGroup.inflate(resId: Int, attachToRoot: Boolean = false): View {
    return LayoutInflater.from(context).inflate(resId, this, attachToRoot)
}

Note: although it may have appeared at some point, I haven’t explicitly talked about it. The arguments of a function may have default values. This means that if we don’t specify them, they’ll take the value specified in the declaration. This prevents us from using method overloading, as we tend to use in Java.

The function is applied over a ViewGroup. It receives a layout and inflates it using the parent view.

What would we get if we want to use it in Java?

View v = UtilsKt.inflate(parent, R.layout.view_item, false);

As you can see, the object that applies this function (the receiver) is added as an argument to the function. In addition, the optional argument becomes mandatory, because in Java we can’t use default values.

Function overloads

If you want to generate the corresponding overloads in Java, you can use @JvmOverloads annotation for that function. That way, you wouldn’t need to specify the false in Java:

@JvmOverloads
fun ViewGroup.inflate(resId: Int, attachToRoot: Boolean = false): View {
    return LayoutInflater.from(context).inflate(resId, this, attachToRoot)
}
View v = UtilsKt.inflate(parent, R.layout.view_item);

If you prefer to specify the name of the class when Kotlin from Java, you can use an annotation to modify it. In the file utils.kt, add this before the package:

@file:JvmName("AndroidUtils")

And now the class in Java will be named:

AndroidUtils.logD("Debug");
AndroidUtils.logE("Error");
View v = AndroidUtils.inflate(parent, R.layout.view_item, false);

Instance and static fields

In Java we use fields to store the state. They can be instance fields, which means that each object will have its own, or static (all instances of a class will share them).

If we try to find a mapping of this in Kotlin, it’d be the properties and the companion objects. If we have a class like this:

class App : Application() {

    val appHelper = AppHelper()

    companion object {
        lateinit var instance: App
    }

    override fun onCreate() {
        super.onCreate()
        instance = this
    }

}

How will this work in Java? You can simply access the companion objects as static fields, and properties using getters and setters:

AppHelper helper = App.instance.getAppHelper();

And you’ll see that the compiler doesn’t complain. Being val, it only generates the getter in Java. If it were var, we’d also have a setter.

Access to instance has worked automatically because it uses the lateinit annotation, which also exposes the field that Kotlin uses to store the state. But imagine we create a constant:

companion object {
    lateinit var instance: App
    val CONSTANT = 27
}

You’ll see that you can’t access to it directly. You will have to access through a Companion internal class:

KotlinTest.Companion.getCONSTANT();

Which doesn’t look very good. To expose the field on Java the same way a static field would look, you’ll need a new annotation:

@JvmField val CONSTANT = 27

And now you can use it from Java code:

int c = App.CONSTANT;

If you have functions in a companion object, they’re converted to static methods using the @JvmStatic annotation.

There are several ways to define constants that, when we use Kotlin from Java, generate different bytecode. I recommend you to take a look at this article from Egor Andreevici, where all this is explained in deep.

Data classes

Some things are kind of clear, but some others are a little more hard to predict how they’re going to work when calling Kotlin from Java. So let’s take a look to those features that Kotlin has but Java doesn’t. One example is data classes.

Let’s say we have a data class like this:

data class MediaItem(val id: Int, val title: String, val url: String)

We are able to create instances of this class:

MediaItem mediaItem = new MediaItem(1, "Title", "https://antonioleiva.com");

But are we missing something?

First, let’s check if equals works as expected:

MediaItem mediaItem = new MediaItem(1, "Title", "https://antonioleiva.com");
MediaItem mediaItem2 = new MediaItem(1, "Title", "https://antonioleiva.com");

if (mediaItem.equals(mediaItem2)) {
    Toast.makeText(this, "Items are equals", Toast.LENGTH_SHORT).show();
}

Of course, the Toast is shown. The bytecode the class generates has everything it needs to compare the two items and, if the state is the same, then the items are also the same.

But there are other things that are more difficult to replicate. Remember the copy feature data classes have? The method is there, but you can only use it passing all arguments:

mediaItem.copy(1, "Title2", "http://google.com");

So it’s not better than just using the constructor. Also, we don’t have destructuring, as Java doesn’t allow constructs like those.

Sealed classes

Another thing that you may wonder is how sealed classes work when used from Java. Let’s try it:

sealed class Filter {
    object None : Filter()
    data class ByType(val type: Type) : Filter()
    data class ByFormat(val format: Format) : Filter()
}

We have a class Filter that represents a filter that can be applied to items. Of course, in Java we can’t do:

public void filter(Filter filter) {
    switch (filter) {
        ...
    }
}

The switch in Java only accepts a small amount of types, and Java is seeing sealed classes as regular classes. So you can imagine how they’ll work:

if (filter instanceof Filter.None) {
    Log.d(TAG, "Nothing to filter");
} else if (filter instanceof Filter.ByType) {
    Filter.ByType type = (Filter.ByType) filter;
    Log.d(TAG, "Type is: " + type.getType().toString());
} else if (filter instanceof Filter.ByFormat) {
    Filter.ByFormat format = ((Filter.ByFormat) filter);
    Log.d(TAG, "Format is: " + format.getFormat());
}

As mentioned above, from Java sealed classes are just regular classes. So that’s the closest you can do to a Kotlin when. Missing auto-casting, right? 😅

Inline functions and reified types

As you may know, in Kotlin you can make generic functions use reified types. That way, you can use the generic type inside the function. But you may also remember that, in order to do this, it needs to use the reserved word inline, which will substitute the calls to the function by the body of the function when compiling. Can we use that from Java?

Let’s start with the inline functions, which are easier to test. If we have a toast function that receives a lambda for the message:

inline fun Context.toast(message: () -> CharSequence) {
    Toast.makeText(this, message(), Toast.LENGTH_SHORT).show()
}

We can use it without issues like this from Java:

ExtensionsKt.toast(this, () -> "Hello World");

So inline works! But there’s an interesting thing here. When used from Kotlin, the decompiled code looks like this:

Toast.makeText(this, "Hello", 0).show();

The function is being inlined as expected. But what happens when used from Java?

ExtensionsKt.toast(this, DetailActivity$$Lambda$0.$instance);

So, though you can use inline functions from Java, they are not really inlined. It’s calling the function and creating an object for the lambda. That’s an important thing to take into account.

Now what happens to reified types? This is a function that navigates to an activity specified in the generic type:

inline fun <reified T : Activity> Context.startActivity() {
    startActivity(Intent(this, T::class.java))
}

Then let’s try to call this function written in Kotlin from Java:

Oh! So we finally found something that can’t be used from Java. As you see, from Java this method appears to be private, so we cannot call it.

Conclusion

You see that it’s very simple to use the code we write in Kotlin from Java. Most things can still be used, though obviously we can’t take advantage of some Kotlin features from Java.

Still not convinced about Kotlin for Android? Start using it as soon as possible! Thanks to the previous articles you can learn more about Kotlin, or I recommend that you sign up to my free training here.

Are you missing any Kotlin feature in the article? Let me know in the comments and I’ll investigate it and update the content.

    Share:
    Back to Blog