article banner (priority)

Effective Kotlin Item 48: Consider using object declarations

This is a chapter from the book Effective Kotlin. You can find it on LeanPub or Amazon.

When you have a class without any instance-specific state, you can turn it into an object declaration to define a singleton. This means not defining a constructor and using the object keyword instead of class. We reference the singleton object using the name of the object declaration.

object Singleton { fun doSomething() { // ... } } fun main() { val obj = Singleton obj.doSomething() Singleton.doSomething() }

Object declarations are useful to limit the number of created objects. This is especially useful for classes that are created many times in your projects, like events or markers. Thanks to object declarations, we can be sure that they have only one instance.

sealed class ValueChange<out T> data object Keep : ValueChange<Nothing>() data object SetDefault : ValueChange<Nothing>() data object SetEmpty : ValueChange<Nothing>() data class Set<out T>(val value: T) : ValueChange<T>() sealed class ManagerMessage data class CodeProduced(val code: String) : ManagerMessage() data object ProductionStopped : ManagerMessage() sealed interface AdView data object FacebookAd : AdView data object GoogleAd : AdView data class OwnAd(val text: String,val imgUrl: String):AdView

It’s a bit more challenging when you want to turn a class that has some generic parameter types, like DeleteAll in the following example, into an object declaration:

sealed interface StoreMessage<T> data class Save<T>(val data: T) : StoreMessage<T> data class DeleteAll<T> : StoreMessage<T>

In such cases, it is popular to use the pattern called Covariant Nothing Object36. To use it, we need to make the type parameter of the supertype class covariant (so, use the out modifier next to T in the StoreMessage declaration), then use Nothing as a type argument for the object declaration:

sealed interface StoreMessage<out T> data class Save<T>(val data: T) : StoreMessage<T> data object DeleteAll : StoreMessage<Nothing>

Since Nothing is a subtype of all types in Kotlin, and T in StoreMessage is covariant, StoreMessage<Nothing> is a subtype of all StoreMessage<T> types. This means that DeleteAll is a subtype of StoreMessage<T> for all T types.

val deleteAllInt: StoreMessage<Int> = DeleteAll val deleteAllString: StoreMessage<String> = DeleteAll

This pattern is used in many projects and libraries, including Kotlin stdlib. For instance, EmptyList is an object declaration that is a subtype of List<Nothing>; as a result, it is a subtype of all List<T> types. This way, there is only one instance of an empty list in the whole application.

internal object EmptyList : List<Nothing> { // ... } val emptyListInt: List<Int> = EmptyList val emptyListString: List<String> = EmptyList

Summary

  • To define singletons, turn classes without an instance-specific state into object declarations.
  • Use the covariant nothing object pattern to turn classes with generic type parameters into object declarations.
36:

I defined and described this in detail in the book Advanced Kotlin.