1. 程式人生 > >Calling Kotlin from Java: start using Kotlin today

Calling Kotlin from Java: start using Kotlin today

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.

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:

1234567 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:

12 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:

Want to learn Kotlin?

Check my free guide to create your first project in 15 minutes!

123 fun ViewGroup.inflate(resId:Int,attachToRoot:Boolean=false):View{returnLayoutInflater.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?

1 Viewv=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:

1234 @JvmOverloadsfun ViewGroup.inflate(resId:Int,attachToRoot:Boolean=false):View{returnLayoutInflater.from(context).inflate(resId,this,attachToRoot)}
1 Viewv=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:

1 @file:JvmName("AndroidUtils")

And now the class in Java will be named:

123 AndroidUtils.logD("Debug");AndroidUtils.logE("Error");Viewv=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:

1234567891011121314 classApp:Application(){val appHelper=AppHelper()companionobject{lateinit varinstance: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:

1 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:

1234 companionobject{lateinit varinstance:Appval CONSTANT=27}

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

1 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:

1 @JvmField val CONSTANT=27

And now you can use it from Java code:

1 intc=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:

1 data classMediaItem(val id:Int,val title:String,val url:String)

We are able to create instances of this class:

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

But are we missing something?

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

123456 MediaItem mediaItem=newMediaItem(1,"Title","https://antonioleiva.com");MediaItem mediaItem2=newMediaItem(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:

1 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:

12345 sealedclassFilter{objectNone:Filter()data classByType(val type:Type):Filter()data classByFormat(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:

12345 publicvoidfilter(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:

123456789 if(filter instanceofFilter.None){Log.d(TAG,"Nothing to filter");}elseif(filter instanceofFilter.ByType){Filter.ByType type=(Filter.ByType)filter;Log.d(TAG,"Type is: "+type.getType().toString());}elseif(filter instanceofFilter.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?