Kotlin Comparable and Comparator

Sorting objects by a certain property or field is a common task. However, as we begin to define our own classes, we need a framework that allows us to support sorting our objects. The Comparable and Comparator interfaces provide such a framework.

Both interfaces define a comparTo(t : T) : Int method. When one object is greater than another object, compareTo should return a positive number (1 or greator). When two objects are equal, compareTo should return zero. Finally, when one object is less than another object, compareTo should return a negative number (-1 or less).

Since Kotlin supports operator overloading, class that implement the Comparable interface have the added bonus of being able to support comparison operators such as <, <=, >, >=. Operator overloading was added to Kotlin to improve code readability and conciseness. After all, it’s much easier to read str1 < str2 as opposed to str1.compareTo(str2) == 1. Nevertheless, it’s still on the developer to decide how two classes are compared.

It’s typical to use field by field comparison. For example, if we are sorting people, we may choose to sort by last name followed by first name. In other cases, we may wish to sort by employee id number. When our classes implement Comparable, we are specifying a default or natural ordering for our classes. To help us define a compareTo method, we may wish to turn to 3rd party libraries such as Apache Commons or Google Guava which provide excellent tools to implement compareTo. Some IDES can even generate code to implement the compareTo method.

It’s not very difficult to implement compareTo in the absence of tools. All Kotlin primitives support compareTo, so implemeting compareTo can be a straightforward class. Let’s look at this example.

data class FamilyMember(val firstName : String,
                        val lastName : String,
                        val age : Int) : Comparable<FamilyMember> {

    /**
     * Having a compareTo method also overloads the comparison operators
     * >, >=, <, <=
     */
    override fun compareTo(other: FamilyMember): Int {
        val fName = firstName.compareTo(other.firstName)
        val lName = lastName.compareTo(other.lastName)
        return fName.compareTo(lName)
    }
}

In our compareTo example, we compare first names by calling compareTo on both names and store the result in a variable. We do the same for last names. Finally, we call compareTo on the fName and lName variables and return the result. At this point our family class has a natural ordering and may use the comparison operators.

When we wish to sort a collection class, we can use the sort() method. The sort() method calls compareTo on each object held in the collection and sorts the items.

val belchers = mutableListOf(
            FamilyMember("Bob", "Belcher", 45),
            FamilyMember("Linda", "Belcher", 44),
            FamilyMember("Tina", "Belcher", 13),
            FamilyMember("Gene", "Belcher", 11),
            FamilyMember("Louise", "Belcher", 9))
belchers.sort()
println(belchers)

After calling belchers.sort(), all family members are sorted by last name, first name. However, what if we want to sort by a different property, say age? That’s when we use Comparator.

Since Kotlin has both object expressions and lambda expressions, we don’t tend to define classes that implement Comparator. Instead, since Comparator is a single abstract method (SAM) interface, we typically use Comparator in a lambda expression. Let’s see how we can sort the belchers by their age.

belchers.sortBy { it.age } // This creates a Comparator that compares by age
println(belchers)

The FamilyMember.age property is an Int, which already implements Comparable. So when the compiler sees it.age, it has all the information it needs to create a Comparator that compares by age. Since it’s not completely known in advance how we are expected to sort objects, Comparators give developers extra flexibility when we need to sort objects differently from the natural order.

Putting it Together

This is an example program that shows how to use both Comparable and Comparator.

data class FamilyMember(val firstName : String,
                        val lastName : String,
                        val age : Int) : Comparable<FamilyMember> {

    /**
     * Having a compareTo method also overloads the comparison operators
     * >, >=, <, <=
     */
    override fun compareTo(other: FamilyMember): Int {
        val fName = firstName.compareTo(other.firstName)
        val lName = lastName.compareTo(other.lastName)
        return fName.compareTo(lName)
    }
}

fun main(args : Array<String>){
    val belchers = mutableListOf(
            FamilyMember("Bob", "Belcher", 45),
            FamilyMember("Linda", "Belcher", 44),
            FamilyMember("Tina", "Belcher", 13),
            FamilyMember("Gene", "Belcher", 11),
            FamilyMember("Louise", "Belcher", 9))
    val bob = belchers[0]
    val gene = belchers[3]

    println("Before sort")
    println(belchers)
    println()

    println("Sorting using natural ordering")
    belchers.sort()
    println(belchers)
    println()

    println("Sorting by age")
    belchers.sortBy { it.age }
    println(belchers)
    println()

    println("Testing operator overloading")
    println("Bob > Gene? " + (bob > gene))
}

Output

Before sort
[FamilyMember(firstName=Bob, lastName=Belcher, age=45), FamilyMember(firstName=Linda, lastName=Belcher, age=44), FamilyMember(firstName=Tina, lastName=Belcher, age=13), FamilyMember(firstName=Gene, lastName=Belcher, age=11), FamilyMember(firstName=Louise, lastName=Belcher, age=9)]

Sorting using natural ordering
[FamilyMember(firstName=Bob, lastName=Belcher, age=45), FamilyMember(firstName=Gene, lastName=Belcher, age=11), FamilyMember(firstName=Linda, lastName=Belcher, age=44), FamilyMember(firstName=Louise, lastName=Belcher, age=9), FamilyMember(firstName=Tina, lastName=Belcher, age=13)]

Sorting by age
[FamilyMember(firstName=Louise, lastName=Belcher, age=9), FamilyMember(firstName=Gene, lastName=Belcher, age=11), FamilyMember(firstName=Tina, lastName=Belcher, age=13), FamilyMember(firstName=Linda, lastName=Belcher, age=44), FamilyMember(firstName=Bob, lastName=Belcher, age=45)]

Testing operator overloading
Bob > Gene? false

Kotlin Deque Interface

The Deque interface extends the Queue interface and allows for items to be added and removed from both ends of the Deque. In other words, we are not only able to make FIFO (first in, first out) structures, but we may also make LIFO structures (last-in, first-out). One real-world use case for LIFO may be an accounting program where inventory is tracked on a LIFO basis to reduce a company’s cost. If we were writing a Kotlin application for such a task, we would use Deque.

The Deque has a variety of methods that are commonly used. Here is a list of the methods followed by a brief description.

Creating a Deque

The LinkedList class in an implementation of the Deque.

val deque : Deque<String> = LinkedList()

Operations with Excpetions

addFirst(T) : void

The addFirst method adds an item to the front of the Deque. The method can raise an exception if the operation fails.

deque.addFirst("Bob Belcher")

addLast(T) : void

The addLast method add an item to the end of the Deque. The method can raise an exception if the operation fails.

deque.addLast("Linda Belcher")

removeFirst() : T

The removeFirst() method removes the first item from the Deque and return it. A NoSuchElement exception is thrown if the Deque is empty.

val bob = deque.removeFirst()

removeLast() : T

The removeLast() method removes the last item from the Deque and returns it. A NoSuchElement exception is thrown if the Deque is empty.

val linda = deque.removeLast()

getFirst() : T

The getFirst() method returns the first item from the Deque, but it does not remove it from the Deque. A NoSuchElement exception is thrown if the Deque is empty.

val bob = deque.getFirst();
println( deque.contains("Bob") ) ///prints True

getLast() : T

The getLast() method returns the last item from the Deque, but it does not remove it from the deque. A NoSuchElement exception is thrown if the Deque is empty.

val linda = deque.getLast()
println( deque.contains("Linda") ) //prints True

Methods that Do Not Throw Exceptions

offerFirst(T) : Boolean

Attempts to add the item to the front of the Deque and returns true if successful otherwise false.

deque.offerFirst("Gene")

offerLast(T) : Boolean

Attempts to add the item to the end of the Deque and returns true if successful otherwise false.

deque.offerLast("Tina")

pollFirst() : T?

Removes and returns an element from the front of the Deque. It will return null if the operation fails.

val gene = deque.pollFirst()

pollLast() : T?

Removes and returns an element from the end of the Deque. It will return null if the operation fails.

val tina = deque.pollLast()

peekFirst() : T?

Returns either the element or null from the front of the Deque but does not remove it from the Deque.

val gene = deque.peekFirst()
println( deque.contains("Gene") )//prints true

peekLast() : T?

Returns either the element or null from the end of the Deque but does not remove it from the Deque.

val tina = deque.peekLast()
println( deque.contains("Tina") )//prints true

Putting it Together

Below is an example program that uses a Deque as a stack (a LIFO datastructure). It simulates layers of soils where the top most layer has to be dug up first prior to digging up older layers of soils.

import java.util.*

fun main(args : Array<String>){
    val soils : Deque<String> = LinkedList()

    with (soils){
        add("Bedrock")
        add("Clay")
        add("Sand")
        add("Top Soil")
    }

    while(soils.isNotEmpty()){
        println("Digging up layer => " + soils.removeLast())
    }
}

Output

Digging up layer => Top Soil
Digging up layer => Sand
Digging up layer => Clay
Digging up layer => Bedrock

Kotlin Queue Interface

The Queue interface is a first-in, first-out data structure. In other words, the first item that is added to the queue is the first item to be removed. We can picture the queue as a line for a roller coaster. The first person to enter the line is the first person who gets to ride on the roller coaster.

Example Program

We will use a Queue in this example program to simulate a music player. The Queue is filled with songs that we would like to hear and then it plays them one at a time in the order they were added. Here is the code.

package ch6.queue

import java.util.*

fun main(args : Array<String>){
    //Create the Queue. Note that LinkedList is one of the implementing classes
    val playQueue : Queue<String> = LinkedList()

    //Add songs to the Queue 
    with (playQueue){
        add("Swing Life Away")
        add("Give it All")
        add("Dancing for Rain")
        add("Hero of War")
        add("Black Masks and Gasoline")
    }

    //Loop until the Queue is empty
    while(!playQueue.isEmpty()){
        //The remove() method takes an item from the Queue and returns it
        println("Playing Song => " + playQueue.remove())
    }
}

The program uses LinkedList as the concrete class to implement the Queue interface. We add five songs to the Queue using the with function and Queue’s add() method. Then we loop until the queue is empty. When we call remove() on the Queue, the first item in the queue is removed and returned. In this way, we print all of the songs added to the Queue one at a time.

Playing Song => Swing Life Away
Playing Song => Give it All
Playing Song => Dancing for Rain
Playing Song => Hero of War
Playing Song => Black Masks and Gasoline

Kotlin Maps

Maps are data structures that associate keys with values. When we lookup values in a map, we specify a key and the map returns an associated value. It’s easy to think of a map being like a phone book. When we want to call somebody, and we only know their name but not their phone number, we find their name in the phone book and the phone book has their number associated with a name.

In the phone book example, we can think of a person’s name as the key and the phone number as the value. In Kotlin, we could make such a map like so:

val phoneBook = mapOf("Bob Belcher" to 8675309, "Linda Belcher" to 8675310)
println(phoneBook["Bob Belcher"]) //prints 8675309
println(phoneBook["Linda Belcher"]) //prints 8675310

We create a phoneBook object using the mapOf(), or mutableMap(), and then specifying any number of pair arguments. Each pair is written key to value so in our example, “Bob Belcher” to 8675309 or “Linda Belcher” to 8675309. When we need to access an item in the map, we use the index operator [] and put the key inside of the operator. In other words, to print Bob’s number, we would write phoneBook[“Bob Belcher”].

When we do not know all of the values we are placing in the map ahead of time, we can use mutableMapOf(). This is the read/write version of the map that has methods to add items and remove items from a map. Let’s look at a code example to see how we add items and remove them from a mutableMap.

//Type is needed when we do not specify arguments in mutableMapOf()
val phoneNumbers = mutableMapOf<String, Int>()

//Use the put method to add items
phoneNumbers.put("Bob Belcher", 8675309)
phoneNumbers.put("Linda Belcher", 8675310)

//Use the remove method to remove items
phoneNumbers.remove("Linda Belcher")

In this case, we use the put() method to add items to the map. The first argument is the key and the second argument is the value. The remove method is the opposite operation. It takes the key as the only argument and removes the entry from the map.

Keys in a map have to be unique. If we use put() with a key that already exists, we will overwrite the current value of the map. This normally isn’t a problem, but it can end up being a source of bugs, especially for new learners. We can protect ourselves from such accidents by converting a mutableMap into a an immutableMap.

//Change phoneNumbers into a read only map
val readOnly = phoneNumbers.toMap() 

Map Example Problem

Couting the frequency of words in text is a common use case for maps. This program counts the number of times certain words appear from an except of “Green Eggs and Ham” by Dr. Suess.

package ch6.maps

fun main(args : Array<String>){
    val paragraph = """
        I am Sam.
        Sam I am.

        That Sam-I-am!
        That Sam-I-am!
        I do not like
        That Sam-I-am!

        Do you like
        Green eggs and ham?

        I do not like them,
        Sam-I-Am
        I do not like
        Green eggs and ham.
        """
    //Remove punctuation and new line characters
    val cleanParagraph =
            paragraph.replace(".", "")
                    .replace(",", "")
                    .replace("?", "")
                    .replace("\n", "")

    //Split cleanParagraph into a List
    val words = cleanParagraph.split(" ")
    
    //Create an empty Map
    val wordCounts = mutableMapOf<String, Int>()
    
    //We have a few operations on the next line
    //1) Iterate through words
    //2) Check if the word has been found in wordCounts
    //3) Put an entry into word counts if the word wasn't found
    //4) Otherwise, update the entry found in wordCounts by 1
    words.forEach( { it -> wordCounts.put(it, wordCounts.getOrElse(it, { 0 }) + 1) })
    
    //Now print out the words with their frequencies
    println(wordCounts.toSortedMap())
}

Output

{=93, Do=1, Green=2, I=5, Sam=2, Sam-I-Am=1, Sam-I-am!=3, That=3, am=2, and=2, do=3, eggs=2, ham=2, like=4, not=3, them=1, you=1}

Kotlin Sets

Sets are a data structure that do not allow for duplicate entries. A common use case for sets is when we need to filter out duplicate elements from a list or an array. Like lists, Kotlin has both mutable sets and read-only sets. In addition, there is also a sorted set flavor that inserts elements in a specified order. The sorted set is read only.

Operations

Creation

We use setOf(), mutableSetOf(), sortedSetOf() to create sets. They take any number of parameters.

val belchers = setOf("Bob", "Linda", "Tina", "Gene", "Louise")
val mutableBelchers = mutableSetOf("Bob", "Linda", "Tina", "Gene", "Louise")

val smiths = sortedSetOf("Rick", "Jerry", "Beth", "Summer", "Morty")

Accessing Items

Sets do not overload the index operator. We can access an item with either the first() or last() method, find, or looping through the entire list.

val bob = belchers.first()
val linda = belchers.last()
val gene = belchers.find { it -> it == "Gene" }
words.forEach( {it -> println(it) } )

Testing Membership

We can use the in function to test if the list has an item.

println("Tina" in belchers) //prints true
println("Rick" in belchers) //prints false

Adding or Removing (Mutable Only)

We can use add or remove on mutableSet to change the elements in the set.

mutableBelchers.remove("Tina")
mutableBelchers.add("Tina")

mutableBelchers.add("Bob") //no effect because Bob is already in the set

Putting it Together

This is an example program that finds the unique words in an except from Dr. Suess’s Green Eggs and Ham.

fun main(args : Array<String>){
    val paragraph = """
        I am Sam.
        Sam I am.

        That Sam-I-am!
        That Sam-I-am!
        I do not like
        That Sam-I-am!

        Do you like
        Green eggs and ham?

        I do not like them,
        Sam-I-Am
        I do not like
        Green eggs and ham.
        """
    //Remove punctuation and new line characters
    val cleanParagraph =
            paragraph.replace(".", "")
                     .replace(",", "")
                     .replace("?", "")
                     .replace("\n", "")
    
    //Split the string by space character and convert it into a set
    val words = cleanParagraph.split(" ").toSet()
    
    //Split the string by space character and convert it to a sortedSet
    val sortedWords = cleanParagraph.split(" ").toSortedSet()


    println(words)
    println(sortedWords)
}

Output

[, I, am, Sam, That, Sam-I-am!, do, not, like, Do, you, Green, eggs, and, ham, them, Sam-I-Am]
[, Do, Green, I, Sam, Sam-I-Am, Sam-I-am!, That, am, and, do, eggs, ham, like, not, them, you]

Kotlin Lists

Lists are one of the most common data structures in computer programming because they allow us to group related data into a single object. For example, we may make a list of each letter of the alphabet. We would need 26 variables, one for each letter, without a list. However, a list lets us create a single object that holds all 26 letters of the alphabet.

Kotlin has two forms of lists. One is a read-only list and the other is a mutable list. The read-only list is created once and only allows operations that do not change the list. For example, we are free to look at each element in such a list, but we may not add or remove elements or sort the list. Such operations would change the list. We are free to iterate through a read-only list and look for elements that match certain criteria, but we may not reassign certain members of the list.

Mutable lists support all operations found in a read-only list be we may also modify the list itself. So we are free to sort a mutable list, add items to it, or remove items. We may even reassign elements at certain indexes. Both styles of lists have methods that facilitate conversion between one form of a list to the other.

Read Only Lists

Creation

We create a read only list using the listOf() function and passing any number of arguments to the function.

val family = listOf("Bob", "Linda", "Tina", "Gene", "Lousie")
val numbers = listOf(1, 2, 3, 4)
val anyList = listOf<Any>(1, "Linda", 1.0)
val nullList = listOf<String?>("Teddy", "Mort", null)

The Kotlin compiler is usually able to figure out the type of the list, but in cases of mixed types, we may need to explicity specify the type of objects the list can hold. Lists hold non-null values by default, so if we need the list to store nulls, we need to explicitly tell the list to hold nulls by specifying the type followed by a ‘?’ mark.

Accessing items

Lists overload the index operator [] so we access elements in the list using the index operator.

println(family[0]) //prints Bob

Checking Membership

We can check if an item exists in the list by using the in keyword.

val hasBob = "Bob" in family
val hasTeddy = "Teddy" in family
println(hasBob) //prints true
println(hasTeddy) //prints false

Finding the Max Item

Lists have a max() method that returns the max item in the list.

println(family.max()) //prints "Tina"

Last Item in a List

We can use the last() function to return the last item in a list.

pritnln(family.last()) //prints Louise

Go Through the List

Lists have a forEach method that lets you go through the list one element at a time.

family.forEach( {it -> println(it) } )

The forEach function takes a lambda expression. The “it” variable refers to the current item in the list.

Mutable Lists

Mutable Lists have the same functionality as above but also allow for operations that change the list.

Creation

Use the mutableListOf() function to create a mutable list.

val smiths = mutableListOf("Rick", "Jerry", "Beth", "Summer", "Morty")

Adding / Removing Items

We use the add() method to add an item and the remove method to remove an item.

smiths.add("Rick Clone")
smiths.remove("Rick Clone")

smiths.add("Morty Clone", 0) //insert at index 0
smiths.remove(0) //Remove element at index 0

Sorting

The mutable list also has a handy sort() method.

smiths.sort() //Now the list is sorted in alphabetical order

Conclusion

Kotlin is one of the few languages that distinguishes between read only and read write lists. Read only lists are write protected and only allow non-changing operations. Mutable lists are read and write lists and allow for mutating operations such as adding or removing items. We have demonstrated a few of the more common operations found on lists but there are many more. Please check the Kotlin documentation for more details!

Kotlin Generic Methods

Generic methods are methods that work on a variety of different type arguments. Some concrete examples of generic methods found in the Kotlin standard library are listOf(), setOf() mapOf(), with(), apply(), etc. All of these methods are compatible with any type of variable and work correctly for each type. A generic method promotes code reuse because a method can be written with a variable type argument that is later substituted with a real type by the compiler.

It’s very easy to write our own generic methods. We need only declare our type arguments inside of angle brackets after the fun keyword. Here is an example of a method that fills a MutableList with a value.

/**
 * Generic method that fills a MutableList with a value. The type argument is declared
 * as T which is later substituted with real types by the compiler.
 */
fun <T> fillList(list : MutableList<T>, value : T, length : Int){
    for (i in 0..length){
        list.add(value)
    }
}

Our fill list isn’t anything fancy, but it is very powerful. The fillList() function is compatible with any type of variable because it uses a generic type, T, as a type argument. Later on, the Kotlin compiler will substitute T with Int, String, or any other type that we need for our purposes.

It is worthwhile to note that all of the typed parameters in fillList() are the same type. In other words, the list variable and the value variable both have to be the same type of argument. We are not allowed to pass in a MutableList<String> along with a value of Int. That would result in a compiler error. If we need multiple type arguments, we need to declare them inside of the angle brackets found after the fun keyword.

We can use our fillList() function like any other function when ready. Here is an example of using our function.

package ch6.genericmethods

/**
 * Generic method that fills a MutableList with a value. The type argument is declared
 * as T which is later substituted with real types by the compiler.
 */
fun <T> fillList(list : MutableList<T>, value : T, length : Int){
    for (i in 0..length){
        list.add(value)
    }
}

fun main(args : Array<String>){
    val intList = mutableListOf<Int>()
    val stringList = mutableListOf<String>()

    fillList(intList, 100, 5)
    fillList(stringList, "Bob", 5)
    //fillList(intList, "Bob", 5) Not OK! intList is MutableList<Int> while Bob is a String

    println(intList)
    println(stringList)
}

When run, this program outputs the following.

[100, 100, 100, 100, 100, 100]
[Bob, Bob, Bob, Bob, Bob, Bob]

Kotlin Generic Classes

Generic classes offer a way for developers to create a class that works with multiple types while retaining type safety. A list is one of the most commonly used generic classes in Kotlin because it allows developers to group a bunch of related objects together in a sequence. The list also works with different types of variables.

//Declare a list of Strings
val stringList = listOf("str1", "str2", "str3")

//Declear a list of ints
val intList = listOf(1, 2, 3, 4)

Both types of lists are created using the listOf() of function. The compiler knows that stringList is a list of Strings because the values passed to listOf are strings. Likewise, the compiler can determine that intList is a list of ints because the values passed are ints.

The listOf() function and the object it returns are generic. In other words, they function and the object are the same for both cases of listOf() as opposed to function overloading which defines different functions based on the type of object. Using generics allows for code reuse because we only write a class or a function once and the compiler substitutes the generic type for a real type when needed.

We will use the following Cook class as an example to demonstrate how to write our own generic classes.

data class Cook<T>(
        var id : T, //Generic property. Can be any type
        var name : String)

The Cook class looks like any other class, except for this <T> that appears after the name of the class. That is the type argument and T is a variable for the Type. The first property of Cook, id, is of type T. Later on, the T parameter can become a String, Int, or basically any valid Kotlin type, including other classes. This allows the Cook class to be flexible about what type of values are stored in the id property.

We need to supply the type of id when we create an instance of Cook.

//bob will be a Cook<String> because the compiler knows that "Bob's Burgers" is a string
val bob = Cook("Bob's Burgers", "Bob")

//jimmy will be a Cook<Int> because the compiler knows that 10001 is an Int
val jimmy = Cook(10001, "Jimmy")

In bob’s case, the compiler knows that “Bob’s Burgers” is a String. So the id in Cook becomes a String and takes on the value of “Bob’s Burgers”. Going forward, bob.id will hold Strings. In jimmy’s case, the compiler knows that 10001 is an Int type, so the id property becomes an Int and jimmy.id will only hold ints.

Using generics protects us from type errors later on. Suppose we were to try assigning a number to bob’s id property.

bob.id = 1000 //BAD! This won't compile

The compiler will refuse to compile the above code because when we created Bob, we used a String for the id property. The compiler check is good because it protects us against ClassCastExceptions at runtime. Since Cook is a generic class, we only need to write the class once and we can use T to represent anytime that is needed for the ID property.

Putting it Together

Here is a working program that shows our Cook generic class in action.

package ch6.genericclasses

data class Cook<T>(
        var id : T, //Generic property. Can be any type
        var name : String)

fun main(args : Array<String>){
    //bob will be a Cook<String> because the compiler knows that "Bob's Burgers" is a string
    val bob = Cook("Bob's Burgers", "Bob")

    //jimmy will be a Cook<Int> because the compiler knows that 10001 is an Int
    val jimmy = Cook(10001, "Jimmy")

    println(bob)
    println(jimmy)

    //The next two lines do not compile
    //bob.id = 1000 (Remember, id is a String in Bob's case and 1000 is an Int so this doesn't compile)
    //jimmy.id = "Jimmy's Pizzeria" (In jimmy's case, id is an Int and "Jimmy's Pizzeria" is a string so this doesn't compile)
}

Output

Cook(id=Bob's Burgers, name=Bob)
Cook(id=10001, name=Jimmy)

Kotlin Composition

In the real world, complex systems are built from simple parts. For example, we might decide to make a kitchen. A kitchen would have a refrigerator, a stove, a dishwasher, and countertops. The stove would consist of burgers, switches to turn the burners on and off, and gas values. The gas valve may have a cutoff mechanism and a pressure regulator.

All of these complex objects are created by smaller and simpler components. It is also worth noting that the Kitchen has a stove. The Stove has a burner, and so on. In OOP, the creation of complex objects from simpler components is known as composition. We create classes that are of small concern and scope and then combine them into larger classes.

Kotlin seems to favor composition by the way. You may notice that classes are final by default and that developers need to mark classes as open prior to using inheritance. Likewise, methods need to be marked as open before they can be overridden. Kotlin has a delegation mechanism where the compiler can create delegate methods when using another object. Finally, our interfaces can even have properties and default behaviors. Even if composition wasn’t the intention, all of these mechanisms in the language tend to steer a developer towards composition.

All of which is desirable. We could create classes and add more features to them by using inheritance. Nevertheless, classes in Kotlin are only allowed to have one parent class which means we can’t use two or more parent classes to create a single type. Furthermore, we could end up creating instability in the code as changes in base classes could end up breaking child classes. Finally, unit testing classes become more difficult because do we continue to test superclass behavior in child classes to prevent regressions?

Composition addresses many such issues. Complexe classes use simple objects to break down a problem into manageable scopes. Classes that implement interfaces become loosely coupled and allow for additional maintainability. We can easily unit test small classes and then move onto to unit testing larger classes. Finally, we can swap or add components as needed to keep the code maintainable.

Inheritance describes an is-a relationship. As in, a Truck is-a a vehicle. Composition describes a has-a relationship as in a Cook has-a spatula. We certainly would not want to extend a Cook from a Spatula or a Spatula from a Cook. The relationship would make no sense. Would we really want methods that target Cooks also be able to accept Spatula objects through polymorphism? Should all Cooks get the same properties and behaviors as a Spatula?

We solve such problems by having Cooks own a spatula. Both classes are distinct entities that have different concerns. Our Cooks should not only be able to posses Spatulas but also other cooking utensils as needed. Each utensil may be used for a different purpose. For example, the cook uses the thermometer for checking food temperature and a spatula for flipping a burger patty.

It’s not difficult to model such a relationship in Kotlin. We only need to use classes and interfaces to put such a relationship together. Let’s start with a Utensil interface.

interface Utensil {
    val name : String

    fun interact(f : Food)
}

Now we only need to create two classes that implement the Utensil interface. Here is the thermometer.

class Thermometer : Utensil {
    override val name: String
        get() = "Thermometer"

    override fun interact(f: Food) {
        println("The ${f.name} has a temperature of 160")
    }
}

Followed by the Spatula

class Spatula : Utensil{
    override val name: String
        get() = "Spatula"

    override fun interact(f: Food) {
        println("Flipping the ${f.name}")
    }
}

Our Cook class can use either tool.

class Cook(var utensil: Utensil) {
    val name = "Bob"

    fun cook() : Food {
        val burger = object : Food {
            override val name: String
                get() = "Burger"
        }
        println("Bob is cooking")
        println("Now Bob is using the ${utensil.name}")
        utensil.interact(burger)
        println("Bob is done cooking")
        return burger
    }
}

Since Cook has a Utensil property, he is free to use either the Thermometer or the Spatula. This flexibility would have been almost impossible to model using inheritance and equally difficult to maintain. However, since our Cook possesses Utensil objects, we can freely swap out different utensils as needed.

Putting it Together

Use composition when you need to create complex objects using simple objects. Try and remember that each object should have its own unique concern. Finally, use interfaces when possible to avoid tight coupling between classes. The following program demonstrates a program that uses compositions and interfaces to create a complex object.

package ch5.interfaces.composition

/**
 * We shouldn't tie any object to one specific kind of Food
 */
interface Food {
    val name : String
}

/**
 * Likewise, we shouldn't tie any one specific object to a specific
 * Utensil. We should always try and think generally.
 */
interface Utensil {
    val name : String

    fun interact(f : Food)
}

/**
 * The Spatula is a Utensil
 */
class Spatula : Utensil{
    override val name: String
        get() = "Spatula"

    override fun interact(f: Food) {
        println("Flipping the ${f.name}")
    }
}

/**
 * The Thermmometer is another Utensil
 */
class Thermometer : Utensil {
    override val name: String
        get() = "Thermometer"

    override fun interact(f: Food) {
        println("The ${f.name} has a temperature of 160")
    }
}

/**
 * Cook is a complex object that is made up of Utensils and outputs Food
 */
class Cook(var utensil: Utensil) {
    val name = "Bob"

    fun cook() : Food { //Notice the return type is Food
        val burger = object : Food {
            override val name: String
                get() = "Burger"
        }
        println("Bob is cooking")
        println("Now Bob is using the ${utensil.name}")
        utensil.interact(burger)
        println("Bob is done cooking")
        return burger
    }
}

fun main(args : Array<String>){
    val bob = Cook(Thermometer())
    bob.cook()
    println()

    bob.utensil = Spatula()
    bob.cook()
}

Output

Bob is cooking
Now Bob is using the Thermometer
The Burger has a temperature of 160
Bob is done cooking

Bob is cooking
Now Bob is using the Spatula
Flipping the Burger
Bob is done cooking

Kotlin Data Access Object

Even the most trivial of computer programs work with data. In many cases, the user will wish to save data and reuse it between one run of the program and a later session. Since different systems handle data differently, the user may need to save data in multiple formats for compatibility purposes. The program should know how to not only persist data but also read data from a variety of supported formats.

Let’s consider a word processes. When we write a document in a word processor, we may normally save documents in the word processor’s default file format. Later on, we may wish to share the document with another coworker or friend, but they have a different word processor than what we are using. The solution may be to use the word processor’s export feature to convert our document from the word processor’s default format to the file format our friend’s word processor is using.

Converting data isn’t just an issue for desktop programs either. We may have a web application deployed that may store data in an RBDMS but have to export the same data to XML or JSON. Likewise, it’s very likely a web application may need to consume JSON and export data as a CSV format. Clearly, there is plenty of need for an application to be flexible about file formats.

That means we should not tie the persistent mechanism to a data structure. It might seem to go against the OOP principle of encapsulation where each object is responsible for its own data but think about the implications of such a design. If we write the persistence logic into a class, the class is tied to a particular file format. What happens when the class needs to read and write to a different file format? We could add additional persistence mechanisms to the class, but eventually, the class becomes bloated as we add more and more persistence schemes to it.

Another approach might be to define abstract methods for saving and retrieving data, but this is equally as bad of an approach. For one thing, are we really going to create an entire class hierarchy just for reading and writing data? Furthermore, we have no means with which to enforce a certain storage mechanism since all instances of such a class would resolve their storage logic using virtual methods. Finally, we would most likely need to convert instances of one subclass to another subclass in order to switch between storage mechanisms, which could send us down the rabbit hole of deep cloning objects.

If you think about it, storing and retrieving data is a completely separate concern of the application. A data object should never end up being responsible for its own persistence. That needs to be the concern of a completely different class or set of classes. Thus we end up with the data access pattern.

The data access pattern relies on a transfer object to hold the data. The transfer object is passed between the view (which is normally the UI) and the source of the data. Kotlin has a special data class that we can use for this specific purpose. Let’s have a look at an example.

/**
 * This a model class that is persisted to disk in this program
 */
data class Cook(val fName : String,
                val lName : String,
                val position : String,
                val age : Int) : Serializable

Koltin data classes get a built-in implementation of hashCode(), equals(), and toString(). We are free to add additional methods to them if needed. We also have this class implementing Serializable for a later demonstration. The important part to note about the Cook class is that it does not handle its own persistence. That is done by another class that is specific for this purpose.

Once we have our transfer object, we can begin by defining our persistence classes. Since we plan on using different formats for reading and writing data, we should use an interface as an abstraction point.

/**
 * This interface describes the kinds of Data Access Operations available
 */
interface CookDao {
    fun save(c : Cook, outStream: OutputStream)

    fun read(inStream: InputStream) : Cook
}

Our CookDao interface defines two methods. The first method, save(), takes a Cook object and an OuputStream. The Cook object is, of course, the transfer object. As for the OutputStream, it’s best to use OutputStream rather than File because it allows this Dao class to work with non-file streams such as network sockets. The DAO class should be concerned with how the object is persisted and retrieved, but it should not be coupled to a particular source. This keeps it flexible and allows us to send and store data on File Systems, Network Sockets, Http Responses, etc.

The same principle holds true for the read() function as well. This method takes an InputStream, the reciprocal class for OutputStream, and returns a Cook object. This allows us to create a Cook from any input stream. Once again, the source of the InputStream isn’t important to the class, only that it contains the data needed to create a Cook object.

Now that we have our interface, let’s look out our implementations. At first, our application wishes to store Cooks as Serialized Java Objects. Let’s create a version of CookDao that accomplishes this task.

/**
 * Implementation of CookDao that uses JVM serialization
 */
class SerializedCookDao : CookDao{
    override fun save(c: Cook, outStream: OutputStream) {
        val o = ObjectOutputStream(outStream)
        outStream.use {
            o.writeObject(c)
        }
    }

    override fun read(inStream: InputStream) : Cook{
        val i = ObjectInputStream(inStream)
        inStream.use {
            return i.readObject() as Cook
        }
    }
}

The SerializedCookDao implements CookDao and stores Cook as a Serialized Java object. When we wish to restore the Cook, we use the readObject() method found on ObjectInputStream to restore the Cook. Either way, the application at large isn’t concerned about how cook is persisted and restored.

Later on, we decide to support command separated values format or CSV. We don’t need to change any client code to accomplish such as task. All we need to do is define another class that implements CookDao.

/**
 * Implementation of CookDao that converts Cooks to a CSV file
 */
class CsvCookDao : CookDao {

    //Private extension function on Cook for creating
    //CSV files
    private fun Cook.toCSV() : String {
        return "${this.fName},${this.lName},${this.age},${this.position}"
    }

    //Private function to convert a CSV line to a Cook
    private fun parseCsv(csv : String) : Cook{
        val parts = csv.split(",")
        return Cook(fName = parts[0],
                    lName = parts[1],
                    age = parts[2].toInt(),
                    position = parts[3])
    }

    override fun save(c: Cook, outStream: OutputStream) {
        outStream.use {
            PrintWriter(outStream, true).println(c.toCSV())
        }
    }

    override fun read(inStream: InputStream): Cook {
        inStream.use {
            val sc = Scanner(inStream)
            return parseCsv(sc.nextLine())
        }
    }
}

The CsvCookDao class does the job. There are a couple of things to note about this class that makes it more interesting. The first is the extension function Cook.toCSV(). Kotlin extension functions let us add extra functionality to a class. We could have added a toCSV() method to Cook, but let’s think about the implications of such a design. For one thing, only CsvCookDao is concerned about making a Cook into a line of CSV. If we added toCSV() to Cook, then we are saying that all Cook objects should be able to turn themselves into CSV at any time.

That’s bad because we are violating the separation of concerns principle again. We don’t want Cook to concern itself with persistence. That’s the job of our DAO classes. Thus, our CsvCookDao gets a private method to convert Cooks into CSV. Likewise, we have the parseCsv function which also turns CSV back into Cooks. We could violate the separation of concerns by adding a constructor to the Cook class, but that would be equally as bad as adding a toCSV() method.

Now that we have our DAO classes, we need a way to pass Cooks from the view to the DAO. We can define a service class for this purpose.


/**
 * CookService uses Kotlin's delegation mechanism. CookService will have
 * all of the same methods as CookDao but the implementation will depend
 * on the CookDao that was provided
 */
class CookService(private val cookDao: CookDao) : CookDao by cookDao

The CookService class is powerful but incredibly compact. Instances of this class are using Kotlin’s delegation mechanism where all of the methods of CookService are wrapped by methods found in CookDao. It’s this mechanism that lets our application switch between serialization and CSV so easily. The view will use CookService. The CookService is created by passing an instance of CookDao to the constructor of CookService. Whichever CookDao is used will determine the data format the application is currently using!

Let’s have a look at the final portion of the application to see this in action.

fun saveAndRestore(bob : Cook, cookService : CookService, inStream : InputStream, outStream : OutputStream){
    println("Bob before saving => " + bob)

    cookService.save(bob, outStream)

    var restoredBob = cookService.read(inStream)
    println("Bob after restoring => " + restoredBob)
}

fun createIfNeeded(name : String) : File{
    val f = File(name)
    if(!f.exists()){
        f.createNewFile()
    }
    return f
}

fun main(args : Array<String>){
    val bob = Cook("Bob", "Belcher", "Owner", 45)
    println("Using CSV")
    saveAndRestore(bob,
            CookService(CsvCookDao()), //Application is using CSV
            FileInputStream(createIfNeeded("bob.csv")),
            FileOutputStream(createIfNeeded("bob.csv")))
    println()
    println("Using serialization")
    saveAndRestore(bob,
            CookService(SerializedCookDao()), //Application is using Serialization
            FileInputStream(createIfNeeded("bob.ser")),
            FileOutputStream(createIfNeeded("bob.ser")))
}

The saveAndRestore function does the job of saving and restoring a Cook object. However, it has no idea of where or how a Cook is handled. The saveAndRestore function interacts soley with CookService. The CookService objects are created in the main method. When a CsvCookDao is used in the CookService constructor, the Cooks are saved in CSV format. When SerialiazedCookDao is used instead, the application will use JVM serialization to save and restore a Cook.

It’s easy to imagine how easily this program can be extended later on. Suppose we which to support XML. All we need to do is create a new CookDao class that handles the transformation of a Cook object to and from XML. Later on, we would pass this CookDao class to the constructor of CookService and the rest of the application would continue to work. In this fashion, we can easily continue to add additional file formats as needs change.

Putting it Together

Here is the program in its entirety followed by output.

package ch4.dataacesspattern

import java.io.*
import java.util.*

/**
 * This a model class that is persisted to disk in this program
 */
data class Cook(val fName : String,
                val lName : String,
                val position : String,
                val age : Int) : Serializable

/**
 * This interface describes the kinds of Data Access Operations available
 */
interface CookDao {
    fun save(c : Cook, outStream: OutputStream)

    fun read(inStream: InputStream) : Cook
}

/**
 * Implementation of CookDao that uses JVM serialization
 */
class SerializedCookDao : CookDao{
    override fun save(c: Cook, outStream: OutputStream) {
        val o = ObjectOutputStream(outStream)
        outStream.use {
            o.writeObject(c)
        }
    }

    override fun read(inStream: InputStream) : Cook{
        val i = ObjectInputStream(inStream)
        inStream.use {
            return i.readObject() as Cook
        }
    }
}

/**
 * Implementation of CookDao that converts Cooks to a CSV file
 */
class CsvCookDao : CookDao {

    //Private extension function on Cook for creating
    //CSV files
    private fun Cook.toCSV() : String {
        return "${this.fName},${this.lName},${this.age},${this.position}"
    }

    //Private function to convert a CSV line to a Cook
    private fun parseCsv(csv : String) : Cook{
        val parts = csv.split(",")
        return Cook(fName = parts[0],
                    lName = parts[1],
                    age = parts[2].toInt(),
                    position = parts[3])
    }

    override fun save(c: Cook, outStream: OutputStream) {
        outStream.use {
            PrintWriter(outStream, true).println(c.toCSV())
        }
    }

    override fun read(inStream: InputStream): Cook {
        inStream.use {
            val sc = Scanner(inStream)
            return parseCsv(sc.nextLine())
        }
    }
}

/**
 * CookService uses Kotlin's delegation mechanism. CookService will have
 * all of the same methods as CookDao but the implementation will depend
 * on the CookDao that was provided
 */
class CookService(private val cookDao: CookDao) : CookDao by cookDao

fun saveAndRestore(bob : Cook, cookService : CookService, inStream : InputStream, outStream : OutputStream){
    println("Bob before saving => " + bob)

    cookService.save(bob, outStream)

    var restoredBob = cookService.read(inStream)
    println("Bob after restoring => " + restoredBob)
}

fun createIfNeeded(name : String) : File{
    val f = File(name)
    if(!f.exists()){
        f.createNewFile()
    }
    return f
}

fun main(args : Array<String>){
    val bob = Cook("Bob", "Belcher", "Owner", 45)
    println("Using CSV")
    saveAndRestore(bob,
            CookService(CsvCookDao()), //Application is using CSV
            FileInputStream(createIfNeeded("bob.csv")),
            FileOutputStream(createIfNeeded("bob.csv")))
    println()
    println("Using serialization")
    saveAndRestore(bob,
            CookService(SerializedCookDao()), //Application is using Serialization
            FileInputStream(createIfNeeded("bob.ser")),
            FileOutputStream(createIfNeeded("bob.ser")))
}

Output

Using CSV
Bob before saving => Cook(fName=Bob, lName=Belcher, position=Owner, age=45)
Bob after restoring => Cook(fName=Bob, lName=Belcher, position=Owner, age=45)

Using serialization
Bob before saving => Cook(fName=Bob, lName=Belcher, position=Owner, age=45)
Bob after restoring => Cook(fName=Bob, lName=Belcher, position=Owner, age=45)