One of the things that makes Kotlin so attractive as a language for Android development is its interoperability with Java, but this is tricky business! Fortunately for us, the developers of Kotlin have implemented some nifty tricks to ease the pain of working with two languages at the same time.
One of these nifty tricks is the way that Kotlin deals with nullability of types coming from Java code. Kotlin refers to these as "platform types."
Nullability in Kotlin
In Kotlin, nullable types are denoted with a question mark after the type name. For example, a House?
can either reference an instance of House
or just be null.
Nullability being encoded into the type system forces us to be super careful about calling methods on instances that could possibly be null. This helps avoid nasty null pointer exceptions and gives us some peace of mind that our programs and apps work as expected.
Nullability in Java
Nullability in Java doesn't have the same language level support as Kotlin. We do have annotations like @Nullable
and @NotNull
/@Nonnull
to give us clues about nullability, but there's no concept of nullability built into the language itself.
Without these annotations (that aren't required and probably aren't present in 99% of Java code in the world), we're left to our own devices to deal with nullability in Java.
How does Kotlin deal with Java's nullable ambiguity?
Kotlin denotes types without any nullability information with a single exclamation mark. You'll see this in Android Studio hints for Java method return types, for example. The single exclamation mark, which might look like House!
for a Java method that returns a House
object, means that the house reference might be nullable and might be not nullable.
This is different from "might be null and might not be null," which is described by the single question mark as in House?
. To put it in Kotlin terms, House!
means it might be House?
and it might be House
.
What are we to do?
We have a couple options to make this platform type ambiguity a little less ambiguous.
Option 1
If we have control over the Java code that provides the platform type we can use nullability annotations to turn a House!
into either a House
or a House?
.
For example, the Kotlin compiler would know that the method @NotNull House getHouse()
returns a House
and not a House?
. Similarly, Kotlin would be able to infer that the method @Nullable House getHouse()
returns a House?
.
Option 2
If we don't have control over the Java code, we can explicitly specify a type as nullable or non-null to leverage Kotlin's null safety and avoid an unexpected null pointer exception at runtime.
For example, if we're dealing with a Java method House getHouse()
and we don't know if this method can return null or not, it's probably safest to just assume that it might and specify its type as House?
. Our Kotlin code would then look like:
val house: House? = javaObject.getHouse()
If we know for sure that getHouse()
won't return null, or if getHouse()
returning null indicates a programmer error that we'd rather fix, we can specify the type as non-null by omitting the question mark:
val house: House = javaObject.getHouse()
If we don't specify a type explicitly, the Kotlin compiler will issue us a warning that should be familiar to most of us:
"Declaration has type inferred from a platform call, which can lead to unchecked nullability issues. Specify type explicitly as nullable or non-nullable."
When you see this warning, either add a nullability annotation to the java code that provides the platform type or specify the type explicitly in Kotlin.