Kotlin Calling Java
Particularly for new projects, most likely you will be focused on Kotlin code calling into Java code. While Kotlin libraries are growing in number, they pale in comparison to the vast array of Java libraries. In particular, many major frameworks are implemented in Java, such as the Android SDK and Spring. While they may get some Kotlin wrappers, many developers will wind up working with them directly.
On the whole, things tend to work fairly smoothly. There are a variety of occasions where you will need to think about interoperability, but for the most part, “it just works”. This is one of the reasons why Google was comfortable in endorsing Kotlin — if having Kotlin code call into the Android SDK was going to be some huge problem, Google might have been more cautious.
In this section, we will explore some things that you will need to consider as you have your Kotlin code call out to Java classes and methods.
Java Methods and Property Syntax
If you spend time on a Kotlin/JVM project in an IDE that offers Kotlin auto-complete (e.g., Android Studio, IntelliJ IDEA), you will rapidly discover that Kotlin allows you to invoke certain Java methods as if they were Kotlin properties or other simpler forms of Kotlin syntax.
getXXX()
and setXXX()
Pairs
The so-called “JavaBeans” method structure in Java involves paired methods, prefixed with get
and set()
, with a common base and using a common type:
public class Something {
private String foo;
public String getFoo() {
return foo;
}
public void setFoo(String newFoo) {
foo = newFoo;
}
}
Here, we have JavaBeans-style accessor methods for a foo
field, with getFoo()
returning the foo
value and setFoo()
changing it.
If you access this Java code from Kotlin, you can treat the getFoo()
and setFoo()
methods as if you were accessing a foo
property that was public
:
class Bar {
fun doFoo() {
val thingy = Something()
val oldFoo = thingy.foo
thingy.foo = "this is the replacement"
}
}
There is nothing stopping you from calling the Java methods directly, so this works too:
class Bar {
fun doFoo() {
val thingy = Something()
val oldFoo = thingy.getFoo()
thingy.setFoo("this is the replacement")
}
}
However, your IDE might hint to you to switch to “property access syntax”:
So, while calling the methods directly works, your IDE would prefer that you use the shorter syntax.
Note that property access syntax only works when there is a matching getter and setter with the same base name and operating on the same type. For example, you might have a Java class with a single getter but overloaded setters, such as with Android’s TextView
and its text “property”. There are several setText()
variants, accepting different parameters. If there is a matching pair — such as the CharSequence
versions of getText()
and setText()
on TextView
— property access syntax works:
class Goo {
fun doText(tv: TextView) {
val oldText = tv.text
tv.text = "This is new!"
}
}
However, other setters that work with other types require you to call the setter method directly, not use property assignment syntax:
class Goo {
fun doText(tv: TextView) {
val oldText = tv.text
tv.setText(R.string.app_name)
}
}
If you try tv.text = R.string.app_name
, you get a type mismatch error, as the property assignment is expecting a CharSequence
, and R.string.app_name
(in the world of Android) is an Int
.
The isXXX()
Variant
Sometimes, with boolean
or Boolean
values, the Java getter method uses is...()
instead of get...()
as a naming convention:
public class Something {
private boolean foo;
public boolean isFoo() {
return foo;
}
public void setFoo(boolean newFoo) {
foo = newFoo;
}
}
This too maps to a property in Kotlin, though the property name will be the same as the getter method name — isFoo
in this case:
class Bar {
fun doFoo() {
val thingy = Something()
val oldFoo = thingy.isFoo
thingy.isFoo = !oldFoo
}
}
Technically, this works for any data type, not just Boolean
. In practice, though, you will rarely see Java code use is...()
for a getter that does not return a boolean
or Boolean
.
get()
as Square Brackets
Kotlin uses square bracket notation for accessing the contents of things like a List
or Map
. In the case of a List
, the value in the brackets is the 0-based index, while for a Map
, the value in the brackets is the Map
key.
Kotlin also extends that notation to any Java class that has appropriate method signatures.
So, if the Java class has get()
and set()
methods that work off of an int
or Integer
index:
public class Something {
private ArrayList<String> stuff = new ArrayList<>(100);
public String get(int i) {
return stuff.get(i);
}
public void set(int i, String value) {
stuff.set(i, value);
}
}
…then Kotlin allows square-bracket notation:
class Bar {
fun doFoo() {
val thingy = Something()
thingy[0] = "hello"
thingy[1] = "world"
println("${thingy[0], ${thingy[1]}")
}
}
In fact, the get()
and set()
methods can use any type for their “key”, not just int
:
public class Something {
private HashMap<String, String> stuff = new HashMap<>();
public String get(String key) {
return stuff.get(key);
}
public void set(String key, String value) {
stuff.put(key, value);
}
}
class Bar {
fun doFoo() {
val thingy = Something()
thingy["first key"] = "hello"
thingy["second key"] = "world"
println("${thingy["first key"]}, ${thingy["second key"]}")
}
}
It is relatively unlikely that you will have a Java class that conforms to this system, but if it does, you are welcome to use this Kotlin notation to work with it.
Dealing with null
Kotlin has a deep relationship with null
:
- Nullable types (
String?
) - Safe calls (
?.
) - Elvis operator (
?:
) - And so on
Most languages do not treat null
with the same reverence. In particular, Java does not.
This causes some problems when Kotlin needs to work with Java objects. For example, suppose that we have:
public class JavaStuff {
public String getSomething() {
return null;
}
}
…and we try using this from Kotlin:
val thingy = JavaStuff().something
println(thingy.length)
The Kotlin compiler will tend to assume that getSomething()
is returning String
, not String?
, and so it will treat thingy
as a String
… and we will crash at runtime with a NullPointerException
.
If the Java code has annotations that indicate nullability, though, Kotlin will attempt to use them.
import androidx.annotation.Nullable;
public class JavaStuff {
public @Nullable String getSomething() {
return null;
}
}
Here, we use an Android SDK annotation, @Nullable
, to indicate that the return value of getSomething()
might be null
. Kotlin, in turn, will then treat getSomething()
as returning String?
instead of String
.
Many libraries, such as the Android SDK, use these annotations to help Java and Kotlin developers. However, not all libraries offer them. As a result, be careful when calling Java code from Kotlin, and consider introducing the nullable type yourself. For example, even in the earlier Java example without @Nullable
, we could write our Kotlin as:
val thingy: String? = JavaStuff().something
println(thingy?.length)
Here, we are saying that we do not trust getSomething()
to always return a non-null
value, so we force thingy
to be String?
for safety’s sake.
Java Class Objects
Sometimes in Java, we need to refer to Java Class
objects. In Android development, one prominent scenario for this is in creating explicit Intent
objects:
startActivity(new Intent(this, TheOtherActivity.class));
In Java, .class
as a suffix on the class name returns the Class
object associated with that class, and we can pass that Class
object to methods that need one.
In Kotlin, ::class
as a suffix on the class name also returns an object representing the class. However, this is a KClass
— a Kotlin class object. We need to use ::class.java
to get at the Java Class
object instead. So this will not compile:
startActivity(Intent(this, MainActivity::class))
…but this will:
startActivity(Intent(this, MainActivity::class.java))
There will be cases in Kotlin code where you just use ::class
. That means the function that you are calling takes a KClass
parameter, probably because that function was written in Kotlin and is designed for use with Kotlin classes.
The SAM Scenario
Every now and then, Java winds up being easier to use from Kotlin than is Kotlin itself.
For example, take this simple Java interface:
public interface SomeJavaInterface {
void beUseful(String value);
}
Implementations need to override beUseful()
and… well… be useful.
You can have the equivalent interface in Kotlin, of course:
interface SomeKotlinInterface {
fun beUseful(value: String)
}
Other than the name, the interfaces seem identical.
However, using them from Kotlin is significantly different.
To use the Kotlin interface, we need to create an object that overrides beUseful()
:
val thingy = object : SomeKotlinInterface {
override fun beUseful(value: String) {
println(value)
}
}
To use the Java interface, we could do the same basic thing:
val thingy = object : SomeJavaInterface {
override fun beUseful(value: String) {
println(value)
}
}
In practice, though, for Java we can get away with just:
val thingy = SomeJavaInterface { println(it) }
SomeJavaInterface
implements the SAM pattern: Single Abstract Method. Kotlin knows how to convert a lambda expression into the implementation of that single abstract method, which in turn allows for the succinct syntax that we see here.
But if you try using that syntax with SomeKotlinInterface
, you fail with a compile error.
Historically, Kotlin did not support the use of SAM for Kotlin interfaces. The argument boils down to: you should not create SomeKotlinInterface
. Instead, use a function type, possibly with a typealias
:
typealias SomeKotlinInterface = (String) -> Unit
Now, you can get similar syntax as you get with SAM conversion:
val thingy: SomeKotlinInterface = { println(it) }
However, Kotlin 1.4 added functional interfaces which allow for SAM syntax for specially-constructed interfaces.
void
and Unit
At this point, though, you may be wondering what Unit
is.
All functions in Kotlin return something. If you do not specify otherwise, the function returns Unit
. This is a singleton: there is exactly one Unit
in the environment.
You do not see Unit
mentioned that much because much of the time it can be safely dropped from Kotlin syntax. For example, this function:
fun ofMeasure() {
println("hello, world!")
}
…is really:
fun ofMeasure(): Unit {
println("hello, world!")
return Unit
}
If your function body does not have an explicit return
, it returns Unit
. And, if your function declaration does not have a return type, it returns Unit
. Kotlin just lets us skip the Unit
references.
While we can skip Unit
from an actual function declaration and the return type, a function type cannot skip it. We cannot replace:
typealias SomeKotlinInterface = (String) -> Unit
with:
typealias SomeKotlinInterface = (String) ->
or:
typealias SomeKotlinInterface = (String)
(the latter compiles but is no longer a function type)
Java methods that “return” void
, when referenced in Kotlin, really return Unit
.
Type Mapping
If you have a Java class named org.yourapp.Restaurant
, and you create an instance of it in Kotlin, you get a org.yourapp.Restaurant
object.
This may seem obvious.
However, not all types work that way. There is a long list of Java types that get “mapped” to Kotlin native types.
For example:
- Java primitives, like
int
, are turned into instances of Kotlin classes, likeInt
- “Boxed” Java primitives, like
Integer
, are turned into instances of the corresponding nullable Kotlin class, likeInteger?
- Certain core Java classes, like
Object
andString
, get turned into Kotlin equivalents (Any
forObject
, and Kotlin’s ownString
class instead ofjava.lang.String
)
The Kotlin documentation has the full roster of mapped types.
Usually, we do not think about this much. However, it may raise some challenges when using reflection or similar techniques that inspect class details at runtime.
Keyword Differences
There is a fair amount of overlap in keywords between Kotlin and Java, such as class
and return
. However, there are some Kotlin keywords that are not Java keywords, such as object
and when
.
As a result, from time to time, you will see function names wrapped in backticks — this is to allow you to call, say, a Java when()
function, despite the fact that when
is a Kotlin keyword.
Prev Table of Contents Next
This book is licensed under the Creative Commons Attribution-ShareAlike 4.0 International license.