Null Unions
FILE : NullUnions.kt
- How kotlin represents nullable values
- How compiler uses null information
- Operators for null unions
- An example of custom union implementation
Null or Type
In Java Nullpointer exception is a common thing because both :
String s1="string1";
String s2=null;
Have the same type String.
In Kotlin situation is different. We have two different types String and String? where
String? = String or null
or in general
Type? = Type or null
We have an alternative of two technically different types so we can call this an union of null and some type
Type Safety
What is an advantage of such union? Because technically String and String? are different types so you can not pass null when only non null types are expected
fun naiveUpper(input: String) = input.toUpperCase()
// naiveUpper(sOrNull) //compilation error
// val s:String=sOrNull //compilation error
// val s:String=null //compilation error
Union operators
- Safe Traversal '?.'
You can call methods on potentially nullable reference without triggering nullpointer exception. The result is still null union.
val sOrNull: String? = ...
val unionResult2: String? = sOrNull?.toUpperCase()?.trim()?.decapitalize()
- folding '?:'
If you want to move from union level to simple value level you just need to provide an alternative for null
val foldResult1: String =sOrNull?.toUpperCase() ?: ""
- unsafe get '!!'
in those 0,00001 % cases when you may have better context knowledge that type system
Displayer.section("nuclear option",naiveUpper(sOrNull!!))
From casting to null union
Kotlin has richer casting mechanism than Java. Beside standard Any as Type we also have Any as? Type .
Second option allow us to continue computation on null unions level which is very similar to how we would write program with Option in more functional style
variable as? String?.toUpper() ?: "ALTERNATIVE"
Modeling Union
By using Kotlin mechanism of sealed trait we can create our own model of union types.
sealed abstract class NullUnion{
abstract fun safeCall(call:(String) -> String): NullUnion
companion object {
fun of(input:String?):NullUnion = if(input==null) NullValue else NonNullValue(input)
}
}
class NonNullValue (private val v:String) : NullUnion(){
override fun safeCall(call:(String) -> String): NullUnion = NonNullValue(call(v))
override fun toString(): String {
return "NonNullValue(v='$v')"
}
}
object NullValue : NullUnion(){
override fun safeCall(call: (String) -> String): NullUnion = this
override fun toString(): String ="NullValue"
}
Here we are using standard subtype polimorphism to implement proper operators for specific union part. Because of sealed modificator we can easily control number of subtypes and set of operations between them.
Questions
At the bottom of the file you can find some questions to practice gained knowledge.
1) Which line displays description if it is present or text "NO DESCRIPTION" ?
class Product(val name:String,val price:Int,val description:String?)
val p = Product("PC",200,null)
a) p :? "NO DESCRIPTION"
b) p?.description?.or("NO DESCRIPTION")
c) p?.description ?: "NO DESCRIPTION"
2) what needs to be here nistead of '???'
val n:Any = "123"
???{ //what needs to be here istead of '???'
println(n.toInt())
}
3) which type is more similar to lang.java.object ?
a) Any
b) Any?
EXERCISES
FILE : NullUnionsExercises.kt
There are two short exercises in this module . In first one you need to implement methods which uses only NullStringUnion types
fun join(prefix: String?, middle: String?, suffix: String?): String
use tests as a specification.
In the second exercise you will work with custom made union types. You need to finish implementation of couple operators for this type.