Top 10 Kotlin Stack Overflow questions, pt 3 - nulls and such
2018-05-08 • Márton Braun
This is the written version of a talk I gave recently, and since it was quite long, this is the third of three articles about it. If you’ve missed it, here’s the first and second articles.
Nullability
6. Smart casts on mutable properties
Why doesn’t smart cast work on mutable properties?
Here’s what the problematic code looks like:
class Dog(var toy: Toy? = null) {
fun play() {
if (toy != null) {
toy.chew()
}
}
}
toy
is expected to be smart cast inside the if
block from Toy?
to Toy
, as it has been checked for nullability. However, this error appears:
Kotlin: Smart cast to ‘Toy’ is impossible, because ‘toy’ is a mutable property that could have been changed by this time
The issue is that this Dog
instance could be modified from another thread between the time the toy != null
check is made, and the toy.chew()
method is called. This could potentially cause a NullPointerException
.
What can I do to make it work?
The issue doesn’t exist if the property is not mutable. It’s a good idea to make everything (both properties and variables) a val
by default, and only make them mutable if you must.
If they really have to be mutable, using a local immutable copy solves the problem - smart cast works as expected on an immutable variable:
class Dog(var toy: Toy? = null) {
fun play() {
val _toy = toy
if (_toy != null) {
_toy.chew()
}
}
}
But of course, there’s a nicer way to make a temporary copy:
class Dog(var toy: Toy? = null) {
fun play() {
toy?.let {
it.chew()
}
}
}
At this point, you should probably have noticed the obvious solution for this simple case of calling just a single function on the property in question:
class Dog(var toy: Toy? = null) {
fun play() {
toy?.chew()
}
}
But the solution using let
works for the cases where you do more complex things after your null
check.
7. null!!
Why exactly does null!!
crash?
Here’s some example code of a property which is meant to be non-null, but for some reason can’t be initialized when the class is constructed. Putting !!
after null
lets the code compile, but at what cost? Any time an Episode
is constructed, it will crash with a KotlinNullPointerException
.
class Episode {
var airdate: Date = null!!
}
The problem here is that the !!
operator is often perceived to be similar to the safe call operator ?.
, and therefore thought to not do anything in the code above, since there’s nothing on its right hand side anyway. It’s also sometimes thought to be a compile time signal to the compiler to “just let your code compile”, which it is not.
Let’s take a look at another code sample:
fun scheduleEpisode(airdate: Date?) {
airdate!!.getWeekday()
}
The most important point here is that the !!
operator can exist and does its job without anything following it. While airdate
is the first expression of the line, the next expression is not airdate!!.getWeekday()
, but instead just airdate!!
. The way this is evaluated is the following:
- If
airdate
is non-null, it returnsairdate
, but now as a non-nullable type, in this case,Date
, which is why thegetWeekday
method can be then called without a safe call operator. - If
airdate
isnull
, it throws aKotlinNullPointerException
.
You can think of !!
as having the same functionality as an extension function like this would:
fun <T> T?.forceNonNull(): T {
if (this == null) {
throw KotlinNullPointerException("Oops, found null")
} else {
return this as T
}
}
So the previous usage of !!
would be equivalent to this:
fun scheduleEpisode(airdate: Date?) {
airdate.forceNonNull().getWeekday()
}
Finally, let’s get back to our original problem:
class Episode {
var airdate: Date = null!!
}
The issue should now be obvious. Before the value of the expression null!!
is assigned to the property, it has to be evaluated, with throws the exception.
8. Platform types
How do I decide the nullability of a parameter when overriding a Java function? What are the consequences of each choice?
Let’s return to our OnClickListener
class defined in Java, with a single callback method.
public interface OnClickListener {
void onClick(Button button);
}
We’ll assume that sometimes a valid Button
instance isn’t provided for the click event (perhaps the Button
is disabled by the time the listener is called). We’ll also forget to annotate these parameters in the Java code accordingly.
Let’s override this interface in Kotlin - we’ll end up with the following method signature if we let IDEA generate it:
class KtListener: OnClickListener {
override fun onClick(button: Button?): Unit {
val name = button?.name ?: "Unknown button"
println("Clicked $name")
}
}
Since the Java method has no nullability annotations, the parameters of the Kotlin function will have a platform type, in this case, Button!
. This means that it’s up to us to decide (based on experience, or hopefully, documentation) whether the parameter received by the method can be null
.
By default, it’s safer to use the nullable types for all parameters, and suffer the restrictions that the compiler forces on us to handle these safely. This results in some extra code, but is always safe.
For parameters which are known never to be null
, the non-nullable type can be used instead - both will compile. If the method does get called with a null
value for a parameter we marked to be non-null, an injected null
check will produce an IllegalArgumentException
before any of the code in the method body would run, making this a slightly riskier choice. Of course, having a non-nullable parameter simplifies our code:
class KtListener: OnClickListener {
override fun onClick(button: Button): Unit {
val name = button.name
println("Clicked $name")
}
}
Small bonus tip
9. Import aliases
How can I use multiple extension functions with the same name in a file?
Let’s say you implement two different indent
extension functions on the String
class in two different packages. If this was an ordinary function, you could call it with a fully qualified package name, however, the syntax doesn’t allow for it with an extension function - there’s quite simply nowhere to put the package name:
"hello world".indent()
However, you can use the as
keyword at the import
statement to rename one (or both) of them, like so:
import com.example.code.indent as indent4
import com.example.square.indent as indent2
Which then lets you use each of them by their new names:
"hello world".indent4()
Another use case is that you could be using two classes with the same name from different packages in the same file (e.g. java.util.Date
and java.sql.Date
), and you don’t want to use either of them by their fully qualified name. You could do the following here:
import java.util.Date as UtilDate
import java.sql.Date as SqlDate
Within this file, you can now refer to these classes by their aliases.
Wrap-up
10. Kotlin vs Java
Which one should I learn if I’m just starting to learn Android development?
❓ ❓ ❓ ❓ ❓ ❓ ❓ ❓ ❓ ❓ ❓
I’d consider nothing other than Xamarin. Don’t waste your time on native development, it’s 2018. /s
Anyhow, that’s a wrap, thank you for reading this series, and see you on StackOverflow :)
You might also like...
Top 10 Kotlin Stack Overflow questions, pt 2 - the big ones
In the second part of the series, I'm covering various possible issues with using SAM conversions, as well as how to create "static" things in Kotlin.
How I Finally Memorized Modifier Ordering in Compose
For the longest time, I proudly had no idea of how Modifier ordering works, and would just guess and then guess again when something didn't look quite right. Here's how I finally ended up remembering how the ordering works.
Top 10 Kotlin Stack Overflow questions, pt 1 - decisions, decisions
I've been hanging out around the Kotlin tag on Stack Overflow a lot this last year or so. This is the first part of a series covering the most frequently asked questions there. For a start, we'll be taking a look at various decisions around using collections in Kotlin. I'm hoping there's something new for everyone in here.
The Other Side of Stack Overflow
My experiences and thoughts after using Stack Overflow from the other side for over a year now, answering questions mostly under the Kotlin tag.