Kotlin Null Safety for the Optional Experience
Here, we'll go through several of Kotlin's null safety operators to see how you can bring your Optional experience over to Kotlin.
Join the DZone community and get the full member experience.
Join For FreeIn this article, I will try to map methods of Java’s Optional to Kotlin’s similar, scattered language features and built-in functions. The code in the examples is written in Kotlin because the language has all the JDK classes available.
Representation
Let’s start with the representation. Optional usage requires creating a new object for the wrapper every time some value is wrapped or transformed to another type — with the exclusion of when the Optional is empty (singleton empty Optional is used). In Kotlin, there is no additional overhead. The language uses plain old null. The code below shows both approaches:
val kotlinNullable: String? = "Kotlin way, this can be null"
val javaNullable: Optional<String> = Optional.ofNullable("Java way, can be null as well”)
Map Using The Method of The Inner Value’s Type
To transform the value inside Optional using the inner value’s method we can apply a method reference to map. To do the same in Kotlin, we can use safe call operator (?.) as demonstrated below:
val lengthKotlin: Int? = kotlinNullable?.length
val lengthJava: Optional<Int> = javaNullable.map(String::length)
Map Using an External Mapper
If the transformation cannot be performed by a simple method call, then Optional’s map method is happy to take a lambda as well. Kotlin provides the built-in method let, which we can invoke on any object. In our case, we also need to use the safe call operator to skip the calculation for null values:
val lengthKotlin: Int? = kotlinNullable?.let { external.computeLength(it) }
val lengthJava: Optional<Int> = javaNullable.map { external.computeLength(it) }
Filter
In Optional filter allows us to remove the value inside if the provided predicate test returns false. Kotlin offers two built-in functions with this behavior — takeIf and takeUntil. The first is similar to the Optional’s filter while the second one drops the value if the predicate returns true — the opposite to takeIf. Example code:
val kotlinFiltered: String? = kotlinNullable.takeIf(predicate)
val kotlinFiltered2: String? = kotlinNullable.takeUnless(predicate)
val javaFiltered: Optional<String> = javaNullable.filter(predicate)
FlatMap
There is no built-in Kotlin function with the flatMap behavior because it’s actually not necessary. Nullability is not represented by a type and simple mapping turns out to be working fine.
IsPresent
In Kotlin, this check can be performed by a simple null comparison:
val kotlinNotNull: Boolean = kotlinNullable != null
val javaNotNull: Boolean = javaNullable.isPresent()
It is worth to know that after we do this, the compiler is sure that the value is present and allows us to drop all required null checks in further code (String? becomes String).
if (kotlinNullable != null) { // String?
val notNullable: String = kotlinNullable // String from this line
}
Get
Both the Optional and Kotin approaches discourage users from getting the inside value with a straight call because it may cause an NPE. Kotlin introduces rude (!!) operator, which you should use only as a last resort:
val notSafeGetKotlin: String = kotlinNullable!!
val notSafeGetJava: String = javaNullable.get()
OrElse/OrElseThrow
Kotlin introduces the elvis operator (?:), which allows us to set the default value in case of null or when throwing an exception:
val kotlinValue: String = kotlinNullable?: ""
val javaValue: String = javaNullable.orElse("")
val kotlinExc: String = kotlinNullable ?: throw RuntimeException()
val javaExc: String = javaNullable.orElseThrow { RuntimeException() }
Conclusion
I hope reading this article will help you leverage your Optional experience to quickly learn Kotlin's null safety features. I think it is worth giving Kotlin a try if only to expand your programming horizons.
Opinions expressed by DZone contributors are their own.
Comments