[Kotlin Pearls] Unit, Nothing, Any (And Null)
The Type Hierarchy in Kotlin is unparalleled in terms of interoperability.
Join the DZone community and get the full member experience.
Join For FreeHow to Take Advantage of Kotlin Special Types
It should be apparent, even to the casual reader of this blog, that I really like Kotlin as a language. One of the reasons is its Type Hierarchy, which is both very easy to work with and extremely powerful. It is also quite close to the Java Type System, so the interoperability is excellent.
To take full advantage of the Kotlin Type Hierarchy, it is essential to understand how these three special types work. We will look in detail at Unit
, Nothing
, and Any
, and compare against their Java corresponding classes.
Finally, we also consider null
and how types ending with ?
fit into these categories.
Unit
The Unit type in Kotlin is the equivalent to the void type in Java.
Or, if you prefer, is the result value of any statement (for example println()
).
fun whatIsLove(){
println("Baby don't hurt me!")
}
In Kotlin, we have two way to define a function or a method: as a statement, with the above syntax similar to Java or as an expression.
We can rewrite any statement without return type as an expression returning Unit
:
fun whatIsLove(): Unit = println("Baby don't hurt me!")
Expressions are usually easier to read than statements, at least if we can keep them short. Long methods are not a good idea anyway.
In the Kotlin standard library, Unit
is simply defined as an object, implicitly inheriting from Any
, with a single method override for toString()
.
public object Unit {
override fun toString() = "kotlin.Unit"
}
Object
s in Kotlin are classes with only one instance, which is created in the class static initializer.
For example:
object JustAnObject {
override fun toString(): String {
return "JustAnObject"
}
}
It is translated in ByteCode as a class JustAnObject
with a static method INSTANCE
to retrieve the singleton instance.
GETSTATIC com/ubertob/unitnothingany/JustAnObject.INSTANCE : Lcom/ubertob/unitnothingany/JustAnObject;
public final static Lcom/ubertob/unitnothingany/JustAnObject; INSTANCE
When operating with Java code, the Kotlin compiler is smart enough to translate any return void into Unit
and the same for special function types.
For example, we can pass a Java lambda of type Consumer<Integer>
to a function expecting a parameter of type (Int) -> Unit
.
//Java
public static Consumer<Integer> printNum = x -> System.out.println("Num " + x);
//Kotlin
fun callMe(block: (Int) -> Unit): Unit = (1..100).forEach(block)
fun main(){
callMe { Lambdas.printNum } //it works fine
}
Note also that in Java, there is a class called Void
, which, confusedly, it is only indirectly related to the keyword void
and cannot be instantiated, so null
is the only valid return value for a function with the result of type Void
.
In case you are wondering, Void
is needed to represent in Java reflection and Generics the concept of methods returning void
, but differently from Unit
is not the actual result values of those methods.
Nothing
Brace yourself here because this is the trickiest type here.
Nothing
is a class (not an object) which is a subclass of any other class, including the final ones. There is a catch, though: it is impossible to instantiate a Nothing
object because there is only a private constructor.
Its declaration is pretty simple:
public class Nothing private constructor()
Now, you may wonder what is the point of having such a class if we cannot instantiate it. Actually, it is pretty useful, as we are going to show.
Since it's not possible to pass a value of type Nothing
, it represents the result of "a function that never returns." For example, because it throws an exception or because it has an infinite loop.
In mathematics and functional programming, Nothing
is called the Bottom Type and represented with the symbol⊥.
In a computer program, any function, regardless of its return type, may not return a value, because of errors or infinite computations. Kotlin makes this property explicit with the Nothing
type.
Let's see some useful use cases now.
The first is the TODO()
function that you can use everywhere when you don't want to specify an implementation yet.
public inline fun TODO(): Nothing = throw NotImplementedError()
Did you ever wonder how it can replace any possible expected return type? It works only because Nothing
is a subtype of any class.
fun determineWinner(): Player = TODO()
//It compiles because Nothing is a subclass of Player
Note that in Java, you cannot write a similar function.
static Void todo(){
throw new RuntimeException("Not Implemented");
}
String myMethod(){
return todo(); //it doesnt' compile because Void is not a String
}
The second example is represented by all empty collections:
fun findAllPlayers(): List<Player> = emptyList()
How can the EmptyList
of Kotlin standard library be a valid List<Player>
? Look at the implementation solves the mystery:
public fun <T> emptyList(): List<T> = EmptyList
//generic function using type inference
object EmptyList : List<Nothing> ... //ultimate covariancefun readUser(id: UserId): User? = …
As you probably guessed, it works because of Nothing
type. Similarly, there are also emptyMap()
, emptySet()
, etc.
The third example is maybe less typical of the previous two, but it is still quite useful.
Let's say you have a function that can fail, for example, reading a user from database.
A simple and sensible choice can be returning a null when the user cannot be found. The signature can be like this:
fun readUser(id: UserId): User? = ...
Sometimes, we need more information about what exactly went wrong (connection can be down, table not exist ecc.) and let the called decide how to handle the failure.
Here, Nothing
came to the rescue! A brilliant solution is to provide a callback in case of an error that returns Nothing
.
inline fun readUser(id: UserId, onError: (DbError) -> Nothing): User = ...
How does it work? Let's see a full example:
fun createUserPage(id: UserId): HtmlPage {
val user = readUser(id) { err ->
when (err) {
is InvalidStatement ->
return@createUserPage throw Exception(err.parseError)
is UserNotFound ->
return@createUserPage HtmlPage("No such user!")
}
}
return HtmlPage("User ${user.name}") //happy case
}
As you can see, both throwing an Exception
and a non-local return is a valid Nothing
type.
To be able to call a non-local return from a lambda, you need to inline the calling function, readUser
in this case.
A possible further evolution of this error handling pattern is wrapping the outcome on a functional Either
with onError
method. This will give us even more flexibility and composition possibilities at the cost of slightly higher code complexity. It is a matter for a future blog post anyway. Let me know if you would be interested.
It is simple to have a private constructor but how can Nothing
be a subclass of anything else? How does it work "under the hood"?
It's all compiler magic, in the ByteCode Nothing
is translated as Void
, while Unit
is translated as void.
//Kotlin
fun neverReturn(): Nothing {
throw Exception("never!")
}
//ByteCode
public final static neverReturn()Ljava/lang/Void;
Finally, it can be useful to remember the differences between Nothing
and Unit
:
fun fooOne(): Unit { while (true) {} }
fun fooZero(): Nothing { while (true) {} }
//both ok
fun barOne(): Unit { println("hi")}
fun barZero(): Nothing { println("hi") } //error
//barZero not compiling
Note that Nothing
is a subtype of Unit
, that's why the first compile but not the last one.
Any
Any in Kotlin is on the top of the hierarchy. All other types descend from Any.
From the class documentation:
" The root of the Kotlin class hierarchy. Every Kotlin class has [Any] as a superclass."
It works like Java Object, but it has a much leaner signature:
public open class Any {
public open operator fun equals(other: Any?): Boolean
public open fun hashCode(): Int
public open fun toString(): String
}
As a comparison java.lang.Object
has 11 methods, of which five are about synchronization ( wait
, notify
, notifyAll
).
This is clearly an example of the advantages of coming later; it might seem a good idea at the time, but now having synchronization primitives on any possible object now seems unnecessary. There is also a reference on Valhalla project mailing list about it.
Looking at the code for Any
, we can also see that is one of the few classes in the Kotlin stdlib that is open, meaning that is possible to inherit directly from it. This is necessary because every Kotlin class automatically inherits from Any
, even if you don't have to write it explicitly, you may do.
class MyClass: Any() {
fun bye(): String = "bye bye"
}
Under the hood, at the JVM level, the Any
type doesn't exist and it gets compiled to java.lang.Object
For example:
fun whatIcanDoWithAny(obj: Any){
obj.toString()
}
Generate this bytecode where even if you are not familiar with the format. You can easily spot the actual type of the parameter (in bold).
public final static whatIcanDoWithAny(Ljava/lang/Object;)V
// annotable parameter count: 1 (visible)
// annotable parameter count: 1 (invisible)
@Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0
L0
ALOAD 0
LDC "obj"
INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull (Ljava/lang/Object;Ljava/lang/String;)V
L1
LINENUMBER 5 L1
ALOAD 0
INVOKEVIRTUAL java/lang/Object.toString ()Ljava/lang/String;
POP
Even if they are not easily accessible from Kotlin, all Kotlin objects do have all the methods missing in Any
, like notify
, wait
, etc.
This also makes possible to pass any Kotlin object to a Java method which is expecting an Object.
Finally, note that the Any
class implements the equals operator, which is mapped on the Java equals method of Object
class. As a consequence, equals==
is the only operator in Kotlin that needs to be overridden and you cannot change its signature, for example, to return some kind of Difference
class instead of a Boolean
.
Null
The last protagonist of this post is not a type but it unique because it has no type. If you want, you can consider null the opposite of Nothing
: a value without a Type, while Nothing
is a Type that cannot be instantiated.
In Kotlin, null
can form special union types with any other type, making them "nullable" and denoting them with the question mark. So String?
is the union of the type String
plus the null
. In this way, Kotlin with the help of smart casts and special operators make easy to handle null
safely.
What about our special types?
Unit?
allows you to return a statement or null
. I don't know of any interesting use of it.
Nothing?
Is particular because it is the only way to force a function to have null
as the only possible result. Still no practical use as far as I know.
Any?
is important because it is the equivalent of Java Object since in Java you can return null
.
Also when declaring a generic class MyClass<T>
the implicit bound of T
is Any?
, like in Java. If you want to restrict your generic class to non-nullable types, you have to state it explicitly: MyClass<T: Any>
Another way to look at null is syntax sugar for the Optional/Maybe type, that can represent a value or the lack of.
Just an exercise let's rewrite map and flatmap as extension functions for any nullable in Kotlin.
fun <A:Any, B:Any> A?.map(f: (A) -> B): B? = when(this) {
null -> null
else -> f(this)
}
fun <A:Any, B:Any> A?.flatMap(f: (A) -> B?): B? = when(this) {
null -> null
else -> f(this)
}
The full code for these examples and more is on my GitHub project.
I hope you liked this post, if so please applaud it and follow me on Twitter and Medium.
The feedback so far on this series of posts has been very positive; thanks to everybody who recommended and shared my posts!
Published at DZone with permission of Uberto Barbini, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments