In Kotlin, nullability is encoded in the type system. It means that one has to use a different type if something may accept null (for instance String?
) than if it cannot be null (for instance String
).
This is very useful, because it allows the compiler to detect potential null-pointer exception at compile-time.
Kotlin also provide a set of convenient language construct in order to deal with nullability:
- Safe call:
nullable?.doSomething()
will only calldoSomething
ifnullable
is not null. And thus never throw any NPE - Elvis operator:
val x: String = input ?: "default"
allow to choose what to do if something return null. It can also be used to return the function or throw an exception. - Smart-cast:
if (x != null) x.length
compiles, because the Kotlin compiler knows that it cannot be null whenlength
is called. - Contracts:
requireNotNull(x) ; x.lenght
compiles, becauserequireNotNull
defines a contract stating that if the function does not throw, it means the argument is not null. Therefore the Kotlin compiler is able to smart-cast. - lateinit: In case of late-initialization (like in a test), there is a specific language keyworkd
lateinit
which allow to declare a non-null member without initializing it in the constructor.
All of that makes dealing with nullability really great in Kotlin. However this is true until we use !!
.
The !!
operator in Kotlin allow the programmer to tell the compiler “Ok, I know what I do, and I promise, it won’t be null”. It is nothing more than a way to stop using all the benefits of Kotlin type system.
It is very error-prone as plain old NPE may appear at run-time again. It kills one major benefit of using Kotlin.
And if we think about it, it is kind of a Kotlin equivalent of squid:S2637:
Java Noncompliant snippet for squid:S2637
@Nonnull
public String indirectMix() {
String mix = null;
return mix; // <-- squid:S2637 Noncompliant; return value is Nonnull, but null is returned.}}
}
The equivalent of the above snippet in Kotlin is:
fun indirectMix(): String { // <-- this declaration is equivalent as using `@NotNull` in Java.
val mix: String? = null;
return mix!!; // <-- This should not be compliant in Kotlin
}
Snippet of non-compliant Code
fun example1(x: Interface?) {
x!!.callMethod()
}
fun example2(x: Interface?) {
callFunction(x!!)
}
fun example3(x: Interface?): List<String> {
return x!!.names
}
Snippet of compilant Code (fixing the above noncompliant code)
Option 1: Use non-null type declaration
fun example1(x: Interface) { // <-- Doesn't accept null. If called from Kotlin, the compiler will enforce it at compile-time.
x.callMethod()
}
fun example2(x: Interface) { // <-- Doesn't accept null. If called from Kotlin, the compiler will enforce it at compile-time.
callFunction(x)
}
fun example3(x: Interface): List<String> { // <-- Doesn't accept null. If called from Kotlin, the compiler will enforce it at compile-time.
return x.names.orEmpty()
}
Option 2: Use safe calls, elvis operator and extension funcions defined on nullable type
fun example1(x: Interface?) {
x?.callMethod() // <-- call `callMethod` only if x is not null
}
fun example2(x: Interface?) {
callFunction(x ?: return) // <-- terminate the function immediately if x is null (and thus don't execute `callFunction`)
}
fun example3(x: Interface?): List<String> {
return x?.names.orEmpty() // <-- return an empty list if x is null
}
Option 3: Throw explicit exception
fun example1(x: Interface?) {
requireNotNull(x) { "null is not a valid argument because..." }
x.callMethod() // <-- Smart cast by Kotlin compiler
}
fun example2(x: Interface?) {
requireNotNull(x) { "null is not a valid argument because..." }
callFunction(x) // <-- Smart cast by Kotlin compiler
}
fun example3(x: Interface?): List<String> {
return x?.names ?: throw IllegalArgumentException("null is not a valid argument because...")
}
External references and/or language specifications
Kotlin null-safety: https://kotlinlang.org/docs/reference/null-safety.html#null-safety
Combating non-null assertions: https://medium.com/@igorwojda/kotlin-combating-non-null-assertions-5282d7b97205
Type : Bug
May cause null pointer exception
I’d personally put the default severity to “minor”
Tags
suspicious, bad-practice