Chapter 3. Object-Oriented Programming in Kotlin

Like Java, Kotlin is an object-oriented programming (OOP) language. As such, it uses classes, both abstract and concrete, and interfaces in a way that is familiar to Java developers.

Some aspects of OOP in Kotlin are worth spending additional time on, and this chapter does so. It includes recipes that involve initializing objects, providing custom getters and setters, performing late and lazy initialization, creating singletons, understanding the Nothing class, and more.

3.1 Understanding the Difference Between const and val

Problem

You need to indicate that a value is a compile-time rather than a runtime constant.

Solution

Use the modifier const for compile-time constants. The keyword val indicates that a variable cannot be changed once it is assigned, but that assignment can occur at runtime.

Discussion

The Kotlin keyword val indicates a variable that cannot be changed. In Java, the keyword final is used for the same purpose. Given that, why does Kotlin also support the modifier const?

Compile-time constants must be top-level properties or members of an object declaration or a companion object. They must be of type String or a primitive type wrapper class (Byte, Short, Int, Long, Float, Double, Char, or Boolean), and they cannot have a custom getter function. They must be assigned outside any function, including main, because their values must be known at compile time.

As an example, consider defining a min and max priority for a task, as in Example 3-1.

Example 3-1. Defining compile-time constants
class Task(val name: String, _priority: Int = DEFAULT_PRIORITY) {

    companion object {
        const val MIN_PRIORITY = 1                1
        const val MAX_PRIORITY = 5                1
        const val DEFAULT_PRIORITY = 3            1
    }

    var priority = validPriority(_priority)       2
        set(value) {
            field = validPriority(value)
        }

    private fun validPriority(p: Int) =           3
        p.coerceIn(MIN_PRIORITY, MAX_PRIORITY)
}
1

Compile-time constants

2

Property with custom setter

3

Private validation function

In this example, three constants are defined using the normal Kotlin (and Java) idiom that suggests writing them in all uppercase letters. This example also takes advantage of a custom setter operation to map any provided priority into the given range.

Note that val is a Kotlin keyword, but const is a modifier, like private, inline, and so on. That’s why const must be used along with the keyword val rather than replacing it.

See also

Custom setter methods like the one shown in this recipe are covered in Recipe 3.2.

3.2 Creating Custom Getters and Setters

Problem

You want to customize how a value is processed when assigned or returned.

Solution

Add get and set functions to properties in a Kotlin class.

Discussion

As in other object-oriented languages, Kotlin classes combine data with functions that operate on that data, in a technique commonly known as encapsulation. Kotlin is unusual in that everything is public by default, because this seems to violate the principle of data hiding, wherein the data structure associated with information is assumed to be an implementation detail.

Kotlin resolves this dilemma in an unusual way: fields cannot be declared directly in Kotlin classes. That sounds strange when you can define properties in a class that look just like fields, as in Example 3-2.

Example 3-2. A class presenting a task
class Task(val name: String) {
    var priority = 3

    // ...
}

The Task class defines two properties, name and priority. One is declared in the primary constructor, while the other is a top-level member of the class. Both could have been defined in the constructor, of course, but this shows that you can use the alternative syntax shown. The downside to declaring priority in this way is that you won’t be able to assign it when instantiating the class, though you could still use an apply block:

var myTask = Task().apply { priority = 4 }

The advantage to defining a property in this way is that you can easily add a custom getter and setter. The full syntax for defining a property is shown here:

var <propertyName>[: <PropertyType>] [= <property_initializer]
    [<getter>]
    [<setter>]

The initializer, getter, and setter are optional. The type is optional if it can be inferred from the initialized value or the getter return type, though this is not true of properties declared in the constructor.

Caution

Properties declared in constructors must include a type, even when they are assigned default values.

Example 3-3 shows a custom getter being used to compute isLowPriority.

Example 3-3. A custom getter for a derived property
val isLowPriority
    get() = priority < 3

As stated, the type of isLowPriority is inferred from the return type of the get function, which in this case is a boolean.

A custom setter is used every time a value is assigned to a property. To make sure that priority is between 1 and 5, for example, a custom setter can be used as in Example 3-4.

Example 3-4. A custom setter for priority
var priority = 3
    set(value) {
        field = value.coerceIn(1..5)
    }

Here, at last, we see the resolution of the public property / private field dilemma listed previously. Normally, when a property needs a backing field, Kotlin provides it automatically. Here, however, in the custom setter, the field identifier is used to reference the generated backing field. The field identifier can be used only in a custom getter or setter.

A backing field is generated for a property if it uses the default generated getter or setter or if a custom getter or setter references it through the field property. That means that the derived property lowPriority will not have one.

Note

In the literature, the terms getters and setters are formally referred to as accessors and mutators, presumably because it’s hard to charge large consulting fees for get and set.

To complete this example, imagine that you want to be able to assign a priority by using a constructor. One way to do that is to introduce a constructor parameter that isn’t a property, by leaving out the var or val keyword. This then leads to the implementation of Task, shown in Example 3-1 and repeated here for reference:

class Task(val name: String, _priority: Int = DEFAULT_PRIORITY) {

    companion object {
        const val MIN_PRIORITY = 1
        const val MAX_PRIORITY = 5
        const val DEFAULT_PRIORITY = 3
    }

    var priority = validPriority(_priority)
        set(value) {
            field = validPriority(value)
        }

    private fun validPriority(p: Int) =
        p.coerceIn(MIN_PRIORITY, MAX_PRIORITY)
}

The parameter _priority is not a property, but rather just an argument to the constructor. It is used to initialize the actual priority property, and the custom setter is evaluated to coerce it into the desired range every time it changes. Note the term value here is just a dummy name; you can change it to anything you like, as with any function parameter.

See Also

The constants used in the Task example are discussed in Recipe 3.1.

3.3 Defining Data Classes

Problem

You want to create a class representing an entity, complete with implementations of equals, hashCode, toString, and more.

Solution

Use the keyword data when defining your class.

Discussion

Kotlin provides the keyword data to indicate that the purpose of a particular class is to hold data. In Java, when such a class represents information from a database table, it is known as an entity, and the concept of a data class is similar.

Adding the word data to a class definition causes the compiler to generate a whole series of functions, including consistent equals and hashCode functions, a toString function that shows the class and the property values, a copy function, and component functions used for destructuring.

For example, consider the Product class:

data class Product(
    val name: String,
    var price: Double,
    var onSale: Boolean = false
)

The compiler generates equals and hashCode functions based on the properties declared in the primary constructor. The algorithm used is the same one described by Joshua Bloch years ago in Effective Java (Addison-Wesley Professional). The tests in Example 3-5 show that they work properly.

Example 3-5. Using the generated equals and hashCode implementations
@Test
fun `check equivalence`() {
    val p1 = Product("baseball", 10.0)
    val p2 = Product("baseball", 10.0, false)

    assertEquals(p1, p2)
    assertEquals(p1.hashCode(), p2.hashCode())
}

@Test
fun `create set to check equals and hashcode`() {
    val p1 = Product("baseball", 10.0)
    val p2 = Product(price = 10.0, onSale = false, name = "baseball")

    val products = setOf(p1, p2)
    assertEquals(1, products.size)  1
}
1

Duplicate not added

Since p1 and p2 are equivalent, when both are included in the setOf function, only one is added to the actual result.

A toString implementation converts the product into a string:

Product(name=baseball, price=10.0, onSale=false)

The copy method is an instance method that creates a new object that starts with the property values from the original and modifies only the supplied values, as the test in Example 3-6 shows.

Example 3-6. Testing the copy function
@Test
fun `change price using copy`() {
    val p1 = Product("baseball", 10.0)
    val p2 = p1.copy(price = 12.0)       1
    assertAll(
        { assertEquals("baseball", p2.name) },
        { assertThat(p2.price, `is`(closeTo(12.0, 0.01))) },
        { assertFalse(p2.onSale) }
    )
}
1

Changes only the price

The test verifies that using copy with the price parameter changes only that value. Note that the Hamcrest matcher closeTo is used to compare prices, because using equality checks with floating-point values is not considered a good idea.

Note that the copy function performs only a shallow copy, not a deep one. To demonstrate this, consider an additional data class called OrderItem, as shown in Example 3-7.

Example 3-7. A class containing a Product
data class OrderItem(val product: Product, val quantity: Int)

The test shown in Example 3-8 instantiates an OrderItem and then makes a copy by using the copy function.

Example 3-8. Test demonstrating shallow copy
@Test
fun `data copy function is shallow`() {
    val item1 = OrderItem(Product("baseball", 10.0), 5)
    val item2 = item1.copy()

    assertAll(
        { assertTrue(item1 == item2) },
        { assertFalse(item1 === item2) },                1

        { assertTrue(item1.product == item2.product) },
        { assertTrue(item1.product === item2.product) }  2
    )
}
1

OrderItem produced by copy is a different object

2

Product inside both OrderItem instances is the same object

The test shows that although the two OrderItem instances are equivalent (by the equals function invoked via ==), they are still two separate objects because the referential equality operator === returns false. They both, however, share the same internal Product instance, because === on both contained references returns true.

Tip

Invoking copy on a data class performs a shallow copy, not a deep one.

In addition to the copy function, data classes add functions called component1, component2, and so on, that return the values of the properties. These functions are used for destructuring, as shown in the test in Example 3-9.

Example 3-9. Destructuring a Product instance
@Test
fun `destructure using component functions`() {
    val p = Product("baseball", 10.0)

    val (name, price, sale) = p       1
    assertAll(
        { assertEquals(p.name, name) },
        { assertThat(p.price, `is`(closeTo(price, 0.01))) },
        { assertFalse(sale) }
    )
}
1

Destructures the product

You are free to override any of these functions (equals, hashCode, toString, copy, or any of the _componentN_ functions) if you wish. You can add other functions as well.

Tip

If you don’t want a property to be included in the generated functions, add the property to the class body rather than the primary constructor.

Data classes are a convenient way of representing classes whose primary purpose is to hold data. The standard library includes two data classes, Pair and Triple, for holding two or three properties of any generic types. If you need more than that, create your own data class.

3.4 The Backing Property Technique

Problem

You have a property of a class that you want to expose to clients, but you need to control how it is initialized or read.

Solution

Define a second property of the same type and use a custom getter and/or setter to provide access to the property you care about.

Discussion

Say you have a class called Customer and you want to keep a list of messages or notes you’ve saved regarding them. You don’t necessarily want to load all the messages whenever you create an instance, however, so you create the class shown in Example 3-10.

Example 3-10. Customer class, version 1
class Customer(val name: String) {
    private var _messages: List<String>? = null         1

    val messages: List<String>                          2
        get() {                                         3
            if (_messages == null) {
                _messages = loadMessages()
            }
            return _messages!!
        }

    private fun loadMessages(): MutableList<String> =
        mutableListOf(
            "Initial contact",
            "Convinced them to use Kotlin",
            "Sold training class. Sweet."
        ).also { println("Loaded messages") }
}
1

Nullable private property used for initialization

2

Property to be loaded

3

Private function

In this class, the property messages will hold the list of messages about that client. To avoid initializing it immediately, the additional property _messages is added, which is of the same type but nullable. The custom getter is used to check whether the messages have been loaded yet, and if not, loads them. The test in Example 3-11 accesses the messages.

Example 3-11. Accessing the messages in the customer
@Test
fun `load messages`() {
    val customer = Customer("Fred").apply { messages }  1
    assertEquals(3, customer.messages.size)             2
}
1

Loads messages the first time

2

Accesses the messages again, but already loaded

You can’t load the messages by using a constructor property, because _messages is private. If you want the messages right away, as shown here, use the apply function. In this test, that invokes the getter method, which both loads the messages and prints the info message. The second time the property is accessed, the messages have already been loaded, and no print is seen.

While this is a useful illustration, it implements lazy loading the hard way. Much easier is the code in Example 3-12, which uses the built-in lazy delegate function.

Example 3-12. Lazy loading the messages by using lazy
class Customer(val name: String) {

    val messages: List<String> by lazy { loadMessages() }  1

    private fun loadMessages(): MutableList<String> =
        mutableListOf(
            "Initial contact",
            "Convinced them to use Kotlin",
            "Sold training class. Sweet."
        ).also { println("Loaded messages") }
}
1

Uses the lazy delegate

Still, using a private backing field to enforce initialization of a property is a useful technique.

A variation on this is to provide a constructor argument to set a value but still enforce constraints on the property, as done in Example 3-1 and repeated here for simplicity:

class Task(val name: String, _priority: Int = DEFAULT_PRIORITY) {

    companion object {
        const val MIN_PRIORITY = 1
        const val MAX_PRIORITY = 5
        const val DEFAULT_PRIORITY = 3
    }

    var priority = validPriority(_priority)
        set(value) {
            field = validPriority(value)
        }

    private fun validPriority(p: Int) =
        p.coerceIn(MIN_PRIORITY, MAX_PRIORITY)
}

Note that the _priority property is not marked with val, indicating it is only a constructor argument rather than an actual property of the class. The property you care about, priority, has a custom setter to assign its value based on the constructor argument.

The backing property technique shows up fairly often in Kotlin classes, so it’s worth understanding how it works.

See Also

The lazy delegate is discussed further in Recipe 8.2.

3.5 Overloading Operators

Problem

You want a client to be able to use operators such as + and * with classes defined in a library.

Solution

Use Kotlin’s operator-overloading mechanism to implement the associated functions.

Discussion

Many operators, including addition, subtraction, and multiplication, are implemented in Kotlin as functions. When you use the +, -, or * symbols, you are delegating to those functions. That means by supplying those functions, you allow a client to use operators.

The classic example, given in the reference docs, is to provide a member function unaryMinus for a Point class, as in Example 3-13.

Example 3-13. Overriding the unaryMinus operator on Point (from reference docs)
data class Point(val x: Int, val y: Int)

operator fun Point.unaryMinus() = Point(-x, -y)

val point = Point(10, 20)

fun main() {
   println(-point)  // prints "Point(x=-10, y=-20)"
}
Note

The operator keyword is necessary when overriding all operator functions other than equals.

What if you want to add the relevant functions to a class that you didn’t write? You can use extension functions to do the job.

For example, consider the Complex class in the (Java) library Apache Commons Math, which represents a complex number (one with real and imaginary parts). If you browse the Javadocs, you’ll see that the class includes methods like add, subtract, and multiply. In Kotlin, the +, -, and * operators correspond to the functions plus, minus, and times. If you add extension functions to Complex to delegate to the existing functions, as in Example 3-14, you can then use the operators instead.

Example 3-14. Extension functions on Complex
import org.apache.commons.math3.complex.Complex

operator fun Complex.plus(c: Complex) = this.add(c)
operator fun Complex.plus(d: Double) = this.add(d)
operator fun Complex.minus(c: Complex) = this.subtract(c)
operator fun Complex.minus(d: Double) = this.subtract(d)
operator fun Complex.div(c: Complex) = this.divide(c)
operator fun Complex.div(d: Double) = this.divide(d)
operator fun Complex.times(c: Complex) = this.multiply(c)
operator fun Complex.times(d: Double) = this.multiply(d)
operator fun Complex.times(i: Int) = this.multiply(i)
operator fun Double.times(c: Complex) = c.multiply(this)
operator fun Complex.unaryMinus() = this.negate()

In each case, the extension function delegates to the existing method in the Java class. The test in Example 3-15 illustrates how to use the delegated operator functions.

Example 3-15. Using the operators with Complex instances
import org.apache.commons.math3.complex.Complex
import org.apache.commons.math3.complex.Complex.* 1

import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.`is`
import org.hamcrest.Matchers.closeTo
import org.junit.jupiter.api.Test

import org.junit.jupiter.api.Assertions.*
import java.lang.Math.*                           2

internal class ComplexOverloadOperatorsKtTest {
    private val first = Complex(1.0, 3.0)
    private val second = Complex(2.0, 5.0)

    @Test
    internal fun plus() {
        val sum = first + second
        assertThat(sum, `is`(Complex(3.0, 8.0)))
    }

    @Test
    internal fun minus() {
        val diff = second - first
        assertThat(diff, `is`(Complex(1.0, 2.0)))
    }

    @Test
    internal fun negate() {
        val minus1 = -ONE 1

        assertThat(minus1.real, closeTo(-1.0, 0.000001))
        assertThat(minus1.imaginary, closeTo(0.0, 0.000001))
    }

    @Test
    internal fun `Euler's formula`() {
        val iPI = I * PI  2

        assertTrue(Complex.equals(iPI.exp(), -ONE, 0.000001))
    }
}
1

Import of Complex.* allows ONE instead of Complex.ONE

2

Can use I and PI for Complex.I and Math.PI

In the last test, the exp function from Complex returns the value of e^{arg}, so the test demonstrates Euler’s formula, e^{i * PI} == –1.

The tests illustrate many of the overloaded operators. If you are writing in Kotlin and using the Complex class, a little bit of operator overloading with extension functions lets you use the same operators you’ve used with regular numbers.

3.6 Using lateinit for Delayed Initialization

Problem

You don’t have enough information to initialize a property in a constructor, but you don’t want to have to make the property nullable as a result.

Solution

Use the lateinit modifier on your property.

Discussion

Tip

Use this technique sparingly, only when necessary. Cases such as the dependency injection described here are useful, but in general, consider alternatives like the lazy evaluation in Recipe 8.2 where possible.

Properties of a class that are declared as non-null are supposed to be initialized in a constructor. Sometimes, however, you don’t have enough information at that time to give the property a value. This occurs in dependency injection frameworks, in which the injection doesn’t happen until after all objects have been constructed, or in setup methods in unit tests. For such cases, use the lateinit modifier on the property.

For example, the Spring framework uses the annotation @Autowired to assign values to dependencies from the so-called application context. Again, since the value is set after the instances have already been created, mark it as lateinit, as in the test case shown in Example 3-16.

Example 3-16. Testing a Spring controller
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class OfficerControllerTests {
    @Autowired
    lateinit var client: WebTestClient           1

    @Autowired
    lateinit var repository: OfficerRepository   1

    @Before
    fun setUp() {
        repository.addTestData()                 2
    }

    @Test
    fun `GET to route returns all officers in db`() {
        client.get().uri("/route")               3
        // ... get data and check values ...
    }

    // ... other tests ...
}
1

Initialized by autowiring

2

Uses repository in the setup function

3

Uses client in tests

The lateinit modifier can be used only on var properties declared inside the body of a class, and only when the property does not have a custom getter or setter. Since Kotlin 1.2, you can also use lateinit on top-level properties and even local variables. The type must be non-null, and it cannot be a primitive type.

By adding lateinit, you are promising to initialize the variable before it is first used. Otherwise, it throws an exception, as shown in Example 3-17.

Example 3-17. Behavior of lateinit properties
class LateInitDemo {
    lateinit var name: String
}

class LateInitDemoTest {
    @Test
    fun `unitialized lateinit property throws exception`() {
        assertThrows<UninitializedPropertyAccessException> {
            LateInitDemo().name
        }
    }

    @Test
    fun `set the lateinit property and no exception is thrown`() {
        assertDoesNotThrow { LateInitDemo().apply { name = "Dolly" } }
    }
}

Accessing the name property before it has been initialized throws an UninitializedPropertyAccessException, as the test shows.

Inside the class, you can check whether one of its properties has been initialized by using isInitialized on the property reference, as in Example 3-18.

Example 3-18. Using isInitialized on a property reference
class LateInitDemo {
    lateinit var name: String

    fun initializeName() {
        println("Before assignment: ${::name.isInitialized}")
        name = "World"
        println("After assignment: ${::name.isInitialized}")
    }
}

fun main() {
    LateInitDemo().initializeName()
}

The output from executing the initializeName function is as follows:

Before assignment: false
After assignment: true

See Also

The lazy delegate is discussed in Recipe 8.2.

3.7 Using Safe Casting, Reference Equality, and Elvis to Override equals

Problem

You want to provide a good implementation of the equals method in a class, so that instances can be checked for equivalence.

Solution

Use the reference equality operator (===), the safe casting function (as?), and the Elvis operator (?:) together.

Discussion

All object-oriented languages have the concept of object equivalence versus object equality. In Java, the double equals operator (==), is used to check whether two references are assigned to the same object. By contrast, the equals method, as part of the Object class, is intended to be overridden to check that two objects are equivalent.

In Kotlin, the == operator automatically invokes the equals function. The open class Any declares the equals function, as shown in Example 3-19, along with hashCode and toString.

Example 3-19. The declarations of equals, hashCode, and toString in Any
open class Any {
    open operator fun equals(other: Any?): Boolean

    open fun hashCode(): Int

    open fun toString(): String
}

The contract for equals requires that the implementation be reflexive, symmetric, and transitive, as well as consistent, and handle nulls appropriately. The contract for hashCode is that if two objects are equal by the equals function, they should have the same hashCode as well. The hashCode function should be overridden whenever the equals function is.

That said, how do you go about implementing a good equals function? One excellent example from the library is provided by the KotlinVersion class, whose equals function is shown in Example 3-20.

Example 3-20. The equals function in KotlinVersion
override fun equals(other: Any?): Boolean {
    if (this === other) return true
    val otherVersion = (other as? KotlinVersion) ?: return false
    return this.version == otherVersion.version
}

Note the simple elegance of this implementation, which takes advantage of several Kotlin features:

  • First, it checks reference equality by using ===.

  • Then it uses the safe casting operator, as?, which either casts the argument as the desired type or returns null.

  • If the safe cast returns null, the Elvis operator (?:) then returns false because if the instances aren’t of the same class, they can’t be equal.

  • Finally, the last line checks whether the version property of the current instance (not shown) is equivalent (using the == operator) to the same property in the other object, and returns the result.

In three lines, this covers all the required cases. For completeness, the implementation of hashCode is simply as follows:

override fun hashCode(): Int = version

This is interesting, but not as directly relevant if you’re trying to understand how to write your own equals function. Say, for example, that you have a simple class called Customer with a string property called name. A consistent implementation of both equals and hashCode is shown in Example 3-21.

Example 3-21. Implementing equals and hashCode in Customer
class Customer(val name: String) {

    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        val otherCustomer = (other as? Customer) ?: return false
        return this.name == otherCustomer.name
    }

    override fun hashCode() = name.hashCode()
}

Incidentally, if you let IntelliJ IDEA generate an equals and hashCode implementation for you, the result (using the Ultimate Edition version 2019.2) is shown in Example 3-22.

Example 3-22. Generated equals and hashCode functions by IntelliJ IDEA
class Customer(val name: String) {
    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (javaClass != other?.javaClass) return false

        other as Customer

        if (name != other.name) return false

        return true
    }

    override fun hashCode(): Int {
        return name.hashCode()
    }
}

The difference is that the generated equals function checks that the javaClass properties on the KClass are equivalent before casting, using the as operator, and then relies on the resulting smart cast to check the name properties. This is essentially equivalent to the procedure described previously, only a bit more verbose.

Data classes have their own autogenerated implementations of equals and hashCode (as well as toString, copy, and component methods). Here, however, you can see how easy it is to implement your own versions.

See Also

Data classes are discussed in Recipe 3.3. The KotlinVersion class is shown in Recipe 11.1.

3.8 Creating a Singleton

Problem

You want to ensure that only one instance of a class is available.

Solution

Use the object keyword instead of class.

Discussion

The Singleton design pattern defines a mechanism for guaranteeing there is only one instance for a particular class. To define a singleton:

  1. Declare all constructors of a class to be private.

  2. Provide a static factory method that returns a reference to the class, instantiating it if necessary.

The Singleton pattern is controversial, because it is sometimes used in cases where a small number of instances could be used rather than one. Nevertheless, it is one of the fundamental design patterns defined in Design Patterns by Erich Gamma et al. (Addison-Wesley Professional), and it can be quite helpful in certain cases.

An example of a singleton in the Java standard library is given by the Runtime class. Say you want to know how many processors are available on a given platform. In Java, you can find out with the code in Example 3-23.

Example 3-23. Finding the number of processors
fun main() {
    val processors = Runtime.getRuntime().availableProcessors()
    println(processors)
}

The getRuntime method is the static method used to return the singleton instance of the class. Example 3-24 shows the relevant portion of the java.lang.Runtime class.

Example 3-24. Runtime implemented to use the Singleton pattern
public class Runtime {
    private static final Runtime currentRuntime = new Runtime();

    public static Runtime getRuntime() {
        return currentRuntime;
    }

    /** Don't let anyone else instantiate this class */
    private Runtime() {}

// ...
}

The Runtime class contains a private, static, final instance of the class, which is eagerly instantiated where the currentRuntime attribute is declared. The only constructor is declared to be private, and the static factory method is getRuntime, as used in Example 3-24.

To implement a singleton in Kotlin, simply use the object keyword instead of class, as in Example 3-25. This is known as an object declaration.

Example 3-25. Defining a singleton in Kotlin
object MySingleton {
    val myProperty = 3

    fun myFunction() = "Hello"
}

If you decompile the generated bytecodes, you get a result similar to Example 3-26.

Example 3-26. Decompiled code for a singleton from object
public final class MySingleton {
   private static final int myProperty = 3;
   public static final MySingleton INSTANCE; 1

   private MySingleton() {                   2
   }

   public final int getMyProperty() {
      return myProperty;
   }

   public final void myFunction() {
      return "Hello";
   }


   static {
      MySingleton var0 = new MySingleton();  3
      INSTANCE = var0;
      myProperty = 3;
   }
}
1

Generated INSTANCE property

2

Private constructor

3

Eager instantiation of singleton

When invoking code in the singleton, you can access the members from the object name, as you would for static members in Java. The member function and property become static final methods and attributes in the decompiled Java class, along with any required getter methods, and the properties are initialized in a static block, along with the class itself. Kotlin code to access the members is in Example 3-27.

Example 3-27. Accessing members of a singleton from Kotlin
MySingleton.myFunction()
MySingleton.myProperty

Accessing the singleton from Java uses the generated INSTANCE property and is shown in Example 3-28.

Example 3-28. Accessing members of a singleton from Java
MySingleton.INSTANCE.myFunction();
MySingleton.INSTANCE.getMyProperty();

A complication arises if you want your singleton to be instantiated with an argument. Say, for example, you’re writing a database connection pool, which would be a natural singleton. The initial size of the pool would be a reasonable input parameter when generating the singleton. Unfortunately, a Kotlin object can’t have a constructor, so there’s no easy way to pass an argument to it.

Note

The blog post “Kotlin Singletons with Argument” by Christophe Beyls discusses ways to handle arguments based on the implementation of the lazy delegate in the Kotlin library.

The referenced article gets into the complexities associated with making the singleton instantiation thread-safe, based on double-checked locking and @Volatile. See the article for details.

3.9 Much Ado About Nothing

Problem

You want to use the Nothing class idiomatically.

Solution

Use Nothing when a function never returns.

Discussion

You know Nothing, Jon Snow.

Ygritte in Game of Thrones, praising Jon Snow for reading this recipe

There is a class in Kotlin called Nothing, whose entire implementation is given in Example 3-29.

Example 3-29. The Nothing implementation
package kotlin

public class Nothing private constructor()

The private constructor means the class cannot be instantiated outside the class, and as you can see, it isn’t instantiated inside the class either. Therefore, there are no instances of Nothing. The documentation states that “you can use Nothing to represent a value that never exists.”

The Nothing class arises naturally in two circumstances. The first occurs when a function body consists entirely of throwing an exception, as in Example 3-30.

Example 3-30. Throwing an exception in Kotlin
fun doNothing(): Nothing = throw Exception("Nothing at all")

The return type must be stated explicitly, and since the method never returns (it throws an exception instead), the return type is Nothing.

That is virtually guaranteed to be a source of confusion for existing Java developers. In Java, if a method throws an exception of any type, the return type on the method doesn’t change. Exception handling is completely outside the normal flow of execution, but you don’t have to change the return type on a method to account for it. The type system in Kotlin, however, has different requirements.

The other context in which Nothing arises occurs when you assign a variable to null and don’t give it an explicit type, as in Example 3-31.

Example 3-31. A variable assigned to null without an explicit type
val x = null

The type of x is inferred to be Nothing?, because it’s obviously nullable (it was assigned null, after all) and the compiler has no other information about it.

To really make matters interesting, consider this fact: in Kotlin, the Nothing class is actually a subtype of every other type.

To see why that is necessary, consider an if statement that can throw an exception, as in Example 3-32.

Example 3-32. An if statement that can throw an exception
val x = if (Random.nextBoolean()) "true" else throw Exception("nope")

The type of x is inferred to be either String, Comparable<String>, CharSequence, Serializable, or even Any, based on the string assigned when a true boolean is generated by the Random.nextBoolean function. The else clause returns a value of type Nothing, and since Nothing is a subtype of every type, performing a Boolean “and” with it and any other type is the other type.

Perhaps the following example will be clearer. The remainder of any number when divided by 3 must be either 0, 1, or 2. Therefore, the when statement in Example 3-33 shouldn’t need an else clause, but the compiler doesn’t know that.

Example 3-33. Remainder modulo 3
for (n in 1..10) {
    val x = when (n % 3) {
        0 -> "$n % 3 == 0"
        1 -> "$n % 3 == 1"
        2 -> "$n % 3 == 2"
        else -> throw Exception("Houston, we have a problem...")
    }
    assertTrue(x is string)
}

The when construct returns a value, so the compiler requires it to be exhaustive. The else condition should never happen, so it makes sense to throw an exception in that case. The return type on throwing an exception is Nothing, and since String is String, the compiler knows that the type of x is String.

Note

The TODO function (discussed in Recipe 11.9) returns Nothing, which makes sense because its implementation is to throw a NotImplementedError.

The Nothing class can be confusing, but once you know the use cases for it, it makes sense in context.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.117.189.228