Kotlin Abstract Factory Pattern

The Abstract Factory pattern builds on the Factory pattern by abstracting the concrete factory implementation behind an abstract class or an interface. It allows us to loosely couple our factories in situations where we are making a family classes. Rather than having one factory class for each concrete class, we can have specialized factories that build a specific set of classes.

The code remains loosely coupled from both the concrete class itself but also from the factory that creates the class. By using the factory itself as an abstraction point, we can add additional factories later on with their own sets of concrete classes. In the end, not only is client code disconnected from the actual object in use, but it’s also loosely coupled from the creation of the object.

This post shows a demonstration of the Abstract Factory Pattern. We have an interface, CookFactory that abstracts two concrete classes. One of the Factory classes is a BurgerCookFactory that produces Cooks who make burgers. The other Factory is a PizzaCookFactory that produces Cooks who make pizzas. The client code uses the Cook interface.

Let’s begin with the interfaces that serve as abstraction points for both Cooks and Factories.

/**
 * Our factories are going to produce objects of type Cook
 */
interface Cook {
    val name : String
    fun cook()
}

/**
 * BurgerCook is made by the BurgerCookFactory
 */
interface BurgerCook : Cook

/**
 * PizzaCook is made by the PizzaCookFactory
 */
interface PizzaCook : Cook

/**
 * The CookFactory is the base interface for our Factory
 */
interface CookFactory {
    fun getInstance(name : String) : Cook
}

We have four interfaces here. Cook is the base interface of all Cook objects. From Cook, we have two subinterfaces called BurgerCook and PizzaCook. The final interface is the CookFactory, which produces all Cook objects. Of course, we now need some concrete classes to go with these interfaces, so let’s begin with BurgerCookFactory.

/**
 * BurgerCookFactory implements CookFactory and serves as a concrete class to
 * make Cooks that make burgers
 */
class BurgerCookFactory : CookFactory {
    override fun getInstance(name: String) : BurgerCook {
       return when (name){
            "Bob" -> Bob()
            "Tina" -> Tina()
            else -> throw IllegalArgumentException("No class available for $name")
        }
    }

    /**
     * Bob is one concrete class
     */
    private class Bob : BurgerCook {
        override val name: String
            get() = "Bob"

        override fun cook() {
            println("Bob cooked One Fish, Two Fish, Red Fish Hamburger")
        }

    }

    private class Tina : BurgerCook {
        override val name: String
            get() = "Tina"

        override fun cook() {
            println("Tina dropped the burger on the floor while cooking it")
        }

    }
}

The above BurgerCookFactory is responsible for making all BurgerCooks. You will notice that it’s making use of covarient return types by returning BurgerCook rather than Cook. The intent behind BurgerCookFactory is to group all classes that implement BurgerCook into this factory class so that this factory is responsible for this family of classes. Inside of BurgerCookFactory, we have two BurgerCooks, Bob and Tina. We are free to add more BurgerCooks later on and need not worry about breaking existing client code.

After making our BurgerCooks, we decided that we need PizzaCooks also. The existing framework we have makes this fairly straightforward. We only need a PizzaCookFactory.

/**
 * This factory is for PizzaCooks
 */
class PizzaCookFactory : CookFactory {
    override fun getInstance(name: String) : PizzaCook {
        return when (name){
            "Jimmy" -> Jimmy()
            "Jr" -> JimmyJr()
            else -> throw IllegalArgumentException("No class available for $name")
        }
    }

    private class Jimmy : PizzaCook {
        override val name: String
            get() = "Jimmy"

        override fun cook() {
            println("Jimmy is cooking a pizza")
        }

    }

    private class JimmyJr : PizzaCook {

        override val name: String
            get() = "Jimmy Junior"

        override fun cook() {
            println("Jimmy Junior started dancing rather than cooking a pizza")
        }
    }
}

The PizzaCookFactory is another Factory class that implements CookFactory. It makes Cooks that implement the PizzaCook interface, which in turn extends the Cook interface. At this point, PizzaCookFactory is usable anywhere Cooks and CookFactory are needed. As our program grows and changes, we can continue to add more factories and cooks when needed.

The Client code for using the factories is really simple. Here is a simple function that accepts a factory and returns a cook.

fun makeCook(factory: CookFactory, name : String) : Cook {
    return factory.getInstance(name)
}

Of course, this is a gross simplification but demonstrates the point. The makeCook function does not care if the factory is BurgerCookFactory or PizzaCookFactory. It only cares that is has a factory of some sort and returns a cook of some sort. Any CookFactory will do and so will all Cooks.

Putting it Together

Here is a complete working program that demonstrates the Abstract Factory Pattern followed by the output.

package ch4.abstractfactory

/**
 * Our factories are going to produce objects of type Cook
 */
interface Cook {
    val name : String
    fun cook()
}

/**
 * BurgerCook is made by the BurgerCookFactory
 */
interface BurgerCook : Cook

/**
 * PizzaCook is made by the PizzaCookFactory
 */
interface PizzaCook : Cook

/**
 * The CookFactory is the base interface for our Factory
 */
interface CookFactory {
    fun getInstance(name : String) : Cook
}

/**
 * BurgerCookFactory implements CookFactory and serves as a concrete class to
 * make Cooks that make burgers
 */
class BurgerCookFactory : CookFactory {
    override fun getInstance(name: String) : BurgerCook {
       return when (name){
            "Bob" -> Bob()
            "Tina" -> Tina()
            else -> throw IllegalArgumentException("No class available for $name")
        }
    }

    /**
     * Bob is one concrete class
     */
    private class Bob : BurgerCook {
        override val name: String
            get() = "Bob"

        override fun cook() {
            println("Bob cooked One Fish, Two Fish, Red Fish Hamburger")
        }

    }

    private class Tina : BurgerCook {
        override val name: String
            get() = "Tina"

        override fun cook() {
            println("Tina dropped the burger on the floor while cooking it")
        }

    }
}

/**
 * This factory is for PizzaCooks
 */
class PizzaCookFactory : CookFactory {
    override fun getInstance(name: String) : PizzaCook {
        return when (name){
            "Jimmy" -> Jimmy()
            "Jr" -> JimmyJr()
            else -> throw IllegalArgumentException("No class available for $name")
        }
    }

    private class Jimmy : PizzaCook {
        override val name: String
            get() = "Jimmy"

        override fun cook() {
            println("Jimmy is cooking a pizza")
        }

    }

    private class JimmyJr : PizzaCook {

        override val name: String
            get() = "Jimmy Junior"

        override fun cook() {
            println("Jimmy Junior started dancing rather than cooking a pizza")
        }
    }
}

fun makeCook(factory: CookFactory, name : String) : Cook {
    return factory.getInstance(name)
}

fun main(args : Array<String>){
    val burgerFactory = BurgerCookFactory()
    val pizzaFactory = PizzaCookFactory()

    var cook = makeCook(burgerFactory, "Bob")
    cook.cook()

    println()
    cook = makeCook(burgerFactory, "Tina")
    cook.cook()

    println()
    cook = makeCook(pizzaFactory, "Jimmy")
    cook.cook()

    println()
    cook = makeCook(pizzaFactory, "Jr")
    cook.cook()
}

Output

Bob cooked One Fish, Two Fish, Red Fish Hamburger

Tina dropped the burger on the floor while cooking it

Jimmy is cooking a pizza

Jimmy Junior started dancing rather than cooking a pizza

Kotlin Factory Pattern

The Factory Pattern is an OOP design pattern where a client requests one object to create an instance of another object for it. The actual object is normally hidden behind an interface to allow for loose coupling, but the factory knows how to produce the correct object. Let’s consider for example a client that needs a product.

The Product is an interface representing a product. The client doesn’t know what sort of product it is, nor should it care. When a product is needed the client can call a method on a ProductFactory object and tell it what sort of product to product. Internally, the ProductFactory parses the arguments and creates an instance of a usually private class the implements the Product interface. The new object is returned to the caller.

In our example, we will have a Cook that is produced by a CookFactory. The client will specify the kind of cook it wants and the CookFactory will produce the correct instance of Cook. The client will never actually know what sort of Cook it is getting, but that should not be of any concern to the client anyway.

/**
 * A Cook interface that is implemented by private classes in the factory
 */
interface Cook {
    val name : String

    fun cook()
}

We can’t directly create a cook because it’s an interface. So to get an instance of a Cook, we need a class that implements this interface. Our CookFactory singleton provides the necessary classes.

/**
 * A singleton class that produces cooks
 */
object CookFactory {
    enum class CookType {BOB, JIMMY}

    /**
     * Called by clients to get a Cook based on the CookType
     */
    fun getCookInstance(type : CookType) : Cook{
        return when (type) {
            CookType.BOB -> Bob()
            CookType.JIMMY -> Jimmy()
        }
    }

    /**
     * Bob is a Cook that is private to the Factory. Only the Factory can
     * create Bob
     */
    private class Bob : Cook {
        override val name: String
            get() = "Bob"

        override fun cook() {
            println("Bob is cooking the Longest Chard Burger\n")
        }
    }

    /**
     * Jimmy is a Cook that is private to the Factory. Only the Factory can
     * create Jimmy
     */
    private class Jimmy : Cook {
        override val name: String
            get() = "Jimmy"

        override fun cook() {
            println("Jimmy is cooking a pizza\n")
        }

    }
    
    //Note that we can add other cooks here also!
}

The CookFactory only has one method: getCookInstance(). It accepts a value from the CookType enum class and returns an instance of Cook based on the input. Right now the cook is either Bob or Jimmy. It’s important to keep in mind that this is providing an abstraction point.

For example, let’s suppose we want to add more cooks as we maintain the program. We can easily tuck away new cooks inside of the CookFactory object or add additional arguments to the CookType enum or the getCookInstance() method. The clients of CookFactory still work as needed since they are working with the Cook interface rather than concrete Cook classes. Since the Factory Pattern is forcing us to program to an interface rather than a type, we are not only loosely coupled, but our code is highly flexible and maintainable.

Putting it Together

package ch4.factory

/**
 * A Cook interface that is implemented by private classes in the factory
 */
interface Cook {
    val name : String

    fun cook()
}

/**
 * A singleton class that produces cooks
 */
object CookFactory {
    enum class CookType {BOB, JIMMY}

    /**
     * Called by clients to get a Cook based on the CookType
     */
    fun getCookInstance(type : CookType) : Cook{
        return when (type) {
            CookType.BOB -> Bob()
            CookType.JIMMY -> Jimmy()
        }
    }

    /**
     * Bob is a Cook that is private to the Factory. Only the Factory can
     * create Bob
     */
    private class Bob : Cook {
        override val name: String
            get() = "Bob"

        override fun cook() {
            println("Bob is cooking the Longest Chard Burger\n")
        }
    }

    /**
     * Jimmy is a Cook that is private to the Factory. Only the Factory can
     * create Jimmy
     */
    private class Jimmy : Cook {
        override val name: String
            get() = "Jimmy"

        override fun cook() {
            println("Jimmy is cooking a pizza\n")
        }

    }

    //Note that we can add other cooks here also!
}

fun main(args : Array<String>){
    val bob = CookFactory.getCookInstance(CookFactory.CookType.BOB)
    val jimmy = CookFactory.getCookInstance(CookFactory.CookType.JIMMY)

    println("Testing Bob")
    bob.cook()

    println("Testing Jimmy")
    jimmy.cook()
}

Output

Testing Bob
Bob is cooking the Longest Chard Burger

Testing Jimmy
Jimmy is cooking a pizza

Kotlin Singletons

Singleton objects is a design pattern that allows for one and only one instance of a class to exist at a time. Singletons are useful when declaring classes that manage a single shared resource such as a logging file or a network connection. We can use a single object to work with such a resource as opposed to have a bunch of objects in memory that needs synchronized access instead.

Kotlin uses object expressions to create singletons. When we declare an object expression at the top level of a Kotlin module, it becomes a singleton. Here is an example.

object Bob {
    val fName = "Bob"
    val lName = "Belcher"

    fun cook(burgerName : String){
        println("Cooking $burgerName")
    }
}

Many readers will notice that the definition of this Bob object looks almost exactly like a class. That is indeed the case as a matter of fact, only we replace the class keyword with object instead.

We can use a singleton by using the object name followed by a method or property.

fun main(args : Array<String>){
    Bob.cook("Pear Goes the Neighborhood")
}

As a matter of fact, Bob is a global object and is used like any other variable. Developers who are familiar with Java may think of these as static methods, but that is not the case. Static methods are associated with a class. In this case, Bob is actually an instance of a Bob class. His methods belong to an instance, not a class itself.

Since Bob is a singleton, he has some rules. We are free to assign Bob to other variables, but we can’t make more Bob’s (he wouldn’t be a singleton otherwise). Bob also has to be initialized right away with a no-argument constructor (after all, he is created by the runtime). Besides those rules, we use singletons just like any other objects. For example, we could have Bob extend classes or even implement interfaces.

By making Bob a singleton, we are ensuring that only one instance of Bob exists in memory. We are free to use singleton objects just like any other Kotlin object, but we are not free to make new instances of a singleton. Singletons are useful for controlling access to shared resources such as thread pools, logging, caching, or other shared resources. Kotlin makes creating singletons easy by using object expressions.

Putting it Together

Here is a demonstration of singletons followed by program output.

object Bob {
    val fName = "Bob"
    val lName = "Belcher"

    fun cook(burgerName : String){
        println("Cooking $burgerName")
    }
}

fun main(args : Array<String>){
    Bob.cook("Pear Goes the Neighborhood")
}

Output

Cooking Pear Goes the Neighborhood

Observer Pattern in Kotlin

JDK provides java.util.Observable and java.util.Observer as an abstract class / interface combination that allows an easy mechanism with which to implement the observer pattern. The framework allows one class to monitor changes in another class and react as needed to the changes. By using the class and interface provided in JDK, we can loosely couple one class to another while still maintaining the observer pattern.

We are going to use three classes in this example: Burger, Bob, and Tina. Bob is a cook who cooks Burgers. Tina is a server who serves the Burgers, and Burger is the object that is passed between Bob and Tina. Bob will notify Tina when a burger is ready, but he doesn’t actually know anything about Tina. All Bob knows (or cares about) is that Tina is an observer.

Let’s begin with both Burger and Bob (since Burger is one line of code).

class Burger(val name: String)

class Bob : Observable() {

    val name = "Bob"

    fun cookBurger(name : String){
        var burger = Burger(name)

        //Call setChanges() prior to calling notifyObservers()
        setChanged() //Inherited from Observable()
        notifyObservers(burger) //Inherited from Observable()
    }
}

We see here that Bob extends Observable. Observable is an abstract class found in the java.util package. In the cookBurger method, Bob creates an new instance of Burger and then calls setChanged(), a method defined in Observable. After calling setChanged(), Bob calls notifyObservers(), which also comes from Observable. All registered Observer objects are notified and since Bob passes the new Burger object as an argument to notifyObservers, they recieve the Burger Bob created.

The Tina class is the receiving end of this pattern. Tina doesn’t know about Bob or any other Observable. She only acts as a receiver for all Observables.

class Tina : Observer{

    val name = "Tina"

    override fun update(o: Observable?, arg: Any?) {
        when (o){
            is Bob -> {
                if (arg is Burger){
                    println("$name is serving the ${arg.name} cooked by ${o.name}")
                }
            }
            else -> println(o?.javaClass.toString())
        }
    }
}

Since Observer is an interface, Tina is forced to implement the update method. The update method has two parameters. The first one is the Observable that is being updated. The other argument is any additional information the Observable passed when calling notifyObservers().

Kotlin distinguishes between nullable and non-null types but Java does not. Anytime we choose to implement Java interfaces or extend abstract classes found in Java, we have to choose if we want null safety or not. There is no way for the Kotlin compiler to know.

In Tina’s case, we have chosen to declare both o and arg as nullable types by adding the question mark (?) after their types. By doing so, we are choosing to accept the null safety offered by the kotlin compiler. We are free to choose otherwise and disregard the null safety, provided we are reasonably sure the types are not going to be null. For example, when I use the Spring framework in Kotlin, I rarely use the null safety because I am usually correct that I am not going to get null from a Spring method.

In this case, I felt it’s better to use the null safety. Observable and Observer are older classes, and we don’t know for sure what may get passed in as parameters to update. Inside of the implementation of update, we have to cast our parameters to the types we need. We can use is operator combined with the when function (see casting). We check if o is Bob and if arg is Burger. When they are, we print out that a Burger is getting served.

There is one final piece to this example. Although Bob and Tina can now work together thanks to Observable and Observer, they need to know about each other. Since Bob extends Observable, he has an addObserver() method that let’s us pass in an instance of Observer.

fun main(args : Array<String>){
    val bob = Bob()
    //Provide Bob and instance of Tina
    bob.addObserver(Tina())

    bob.cookBurger("It takes Bun to Know Bun Burger")
}

Going forward, whenever Bob calls cookBurger, Tina will get notified and serve it.

Putting it Together

Here is the complete program with its output.

package ch4.observer

import java.util.*

class Burger(val name: String)

class Bob : Observable() {

    val name = "Bob"

    fun cookBurger(name : String){
        var burger = Burger(name)

        //Call setChanges() prior to calling notifyObservers()
        setChanged() //Inherited from Observable()
        notifyObservers(burger) //Inherited from Observable()
    }
}

class Tina : Observer{

    val name = "Tina"

    override fun update(o: Observable?, arg: Any?) {
        when (o){
            is Bob -> {
                if (arg is Burger){
                    println("$name is serving the ${arg.name} cooked by ${o.name}")
                }
            }
            else -> println(o?.javaClass.toString())
        }
    }
}

fun main(args : Array<String>){
    val bob = Bob()
    bob.addObserver(Tina())

    bob.cookBurger("It takes Bun to Know Bun Burger")
}

Output

Tina is serving the It takes Bun to Know Bun Burger burger cooked by Bob

Kotlin Interfaces

Interfaces provide an abstract point between two entities. They work by defining a set of behaviors that implementing classes are expected to implement. Rather than using a concrete class, a client of a class can use the interface as an abstraction point providing loose coupling between objects. Loose coupling promotes code reuse because a client is free to use any object that implements the interface.

Kotlin interfaces look similar to classes, but use the interface keyword rather than the class keyword.

/**
 * This is a basic interface called Server. It defines a single abstract method called serveOrder.
 * All classes that implement Server have to define serveOrder.
 */
interface Server {
    fun serveOrder()
}

Our server interface defines a single method, serverOrder(). Note that we cannot create an instance of an interface. So it’s not legal to write code such as this:

val server = Server() //ILLEGAL!

We use Server by writing a class that implements Server.

/**
 * Tina is a class that implements Server. She can be used anywhere a Server is needed.
 */
class Tina : Server {
    /**
     * Since Server doesn't provide a default implementation of serverOrder, it's up to Tina
     * to provide a method body for this function.
     */
    override fun serveOrder() {
        println("Tina is serving an order")
    }

}

Since our Server class only defines abstract methods, the Tina class is expected to override the serverOrder() method. We can then use Tina in any variable that expects a Server.

val server : Server = Tina() //OK because Tina is a Server!

fun orderUp(s : Server) = s.serveOrder()

orderUp(Tina()) //OK because once again, Tina is a Server

Advance Interfaces

Kotlin allows interfaces to have properties and default methods. Here is a Cook interface that we will use an example.

/**
 * This is a cook interface. Kotlin allows us to define properties in our interfaces but unlike abstract
 * class, interfaces may not hold state.
 */
interface Cook {
    val name : String

    /**
     * Interfaces methods can have default implementations
     */
    fun sayHello() = "$name says Hello"

    fun cook()

    /**
     * Notice how this method takes a server object. That means any class that implements Server
     * may be used for this method. In other words, Cook is coupled to Servers but not to any
     * class that implements servers.
     */
    fun orderUp(s : Server) = s.serveOrder()
}

The Cook interface has a name property. However, it is not allowed to assign a value to name. Interfaces can never hold state (use abstract classes instead). Since the Cook interface has such a property, all classes that implement Cook must override the name property.

Cook also has two other methods that have default implementations. The sayHello() method and the orderUp(Server) method. Classes that implement Cook do not need to provide an implementation of these methods. Notice that cook() is still abstract and must be overrode. Unlike classes, methods on an interface are open by default and classes are free to override them as needed.

Bob is a Cook and therefore implements the Cook interface.

/**
 * Bob implements the Cook interface. As such, Bob may be used anywhere we need a cook.
 */
class Bob : Cook {

    /**
     * Cook has a name property. Since interfaces can't hold state, Bob has to override the name property
     */
    override val name: String = "Bob"

    /**
     * It's up to Bob to override the cook() method also.
     */
    override fun cook() {
        println("$name is cooking a burger")
    }

    /**
     * Bob is also free to implement any other method in Cook
     */
    override fun sayHello(): String = "$name speaking here. Hello..."
}

Since Bob impelments Cook, he has to override the name property and the cook() method. Bob has chosen to override sayHello(), but he didn’t have to. Has Bob ignored the sayHello() method, he would have simply inherited the behavior defined in Cook.

Using Interfaces

Interfaces are used like any other value in Kotlin. Here is a function that uses the Cook and Server interface.

/**
 * This demoCook function is loosely coupled to Cook and Server. As such, we are free
 * to use any implementation of Cook or Server
 */
fun demoCook(c : Cook, s : Server, name: String){
    println("Demonstrating Cook with $name")
    with (c){
        println(sayHello())
        cook()
        orderUp(s)
    }
    println()
}

The demoCook function is free to use any implementation of Cook and Server. We may only have Bob and Tina right now but should we decide to use a different class implementing Cook and Server, we are still free to use this function with this class. That is the major advantage of using interfaces. Code that is written to use an interface rather than a concrete class remains highly flexible and maintainable.

Putting it Together

The following is an example Kotlin program that uses interfaces followed by its output.

package ch5.interfaces

/**
 * This is a basic interface called Server. It defines a single abstract method called serveOrder.
 * All classes that implement Server have to define serveOrder.
 */
interface Server {
    fun serveOrder()
}

/**
 * This is a cook interface. Kotlin allows us to define properties in our interfaces but unlike abstract
 * class, interfaces may not hold state.
 */
interface Cook {
    val name : String

    /**
     * Interfaces methods can have default implementations
     */
    fun sayHello() = "$name says Hello"

    fun cook()

    /**
     * Notice how this method takes a server object. That means any class that implements Server
     * may be used for this method. In other words, Cook is coupled to Servers but not to any
     * class that implements servers.
     */
    fun orderUp(s : Server) = s.serveOrder()
}

/**
 * Tina is a class that implements Server. She can be used anywhere a Server is needed.
 */
class Tina : Server {
    /**
     * Since Server doesn't provide a default implementation of serverOrder, it's up to Tina
     * to provide a method body for this function.
     */
    override fun serveOrder() {
        println("Tina is serving an order")
    }

}

/**
 * Bob implements the Cook interface. As such, Bob may be used anywhere we need a cook.
 */
class Bob : Cook {

    /**
     * Cook has a name property. Since interfaces can't hold state, Bob has to override the name property
     */
    override val name: String = "Bob"

    /**
     * It's up to Bob to override the cook() method also.
     */
    override fun cook() {
        println("$name is cooking a burger")
    }

    /**
     * Bob is also free to implement any other method in Cook
     */
    override fun sayHello(): String = "$name speaking here. Hello..."
}

/**
 * Jimmy is also a cook. Notice how he is only expected to implement name and cook()
 */
class Jimmy : Cook {
    override fun cook() {
        println("$name is cooking a pizza")
    }

    override val name: String = "Jimmy"
}

/**
 * This demoCook function is loosely coupled to Cook and Server. As such, we are free
 * to use any implementation of Cook or Server
 */
fun demoCook(c : Cook, s : Server, name: String){
    println("Demonstrating Cook with $name")
    with (c){
        println(sayHello())
        cook()
        orderUp(s)
    }
    println()
}

fun main(args : Array<String>){
    val bob = Bob()
    val jimmy = Jimmy()

    val tina = Tina()

    /**
     * We can implement interfaces with object expressions also
     */
    val jimmyJr = object: Server{
        override fun serveOrder() {
            println("Jimmy Jr is serving an order")
        }
    }

    demoCook(bob, tina, "Bob")
    demoCook(jimmy, jimmyJr, "Jimmy")
}

Output

Demonstrating Cook with Bob
Bob speaking here. Hello...
Bob is cooking a burger
Tina is serving an order

Demonstrating Cook with Jimmy
Jimmy says Hello
Jimmy is cooking a pizza
Jimmy Jr is serving an order

Kotlin Enum Classes

Enum classes are used to restrict choices to a set of predetermined values. For example, you may wish to define a set of color constants such as Red, Green, and Blue. The color constants may later be used as an argument to a function or used in a when function. The compiler checks to make sure that only the allowed values are used when required. The type safety offered by enum classes can really valuable when reducing bugs and since the constants are named, the readability of the code is improved.

Let’s start with a basic definition of enum classes in Kotlin. We are going to begin with a Toppings enum class that offers Ketchup, Lettuce, Mayonnaise, and Tomatoes.

enum class Toppings {
    KETCHUP,
    MAYONNAISE,
    LETTUCE,
    TOMATO
}

We can reference the values in toppings like so

val ketchup = Toppings.KETCHUP
val mayo = Toppings.MAYONNAISE
val lettuce = Toppings.LETTUCE
val tomato = Toppings.TOMATO

or we can even use it as an argument to a function

fun printToppings(t : Toppings){
    if (t == Toppings.KETCHUP){
        print("Ketchup)
    } else {
        print("Not ketchup")
    }
}

Enum classes also work great with the when function

val t = Toppings.LETTUCE

val topping = 
    when (t) {
       Toppings.KETCHUP -> "Ketchup"
       Toppings.MAYONNAISE -> "Mayonnaise"
       Toppings.LETTUCE -> "Lettuce"
       Toppings.TOMATO -> "Tomato"
    }
print(topping)

Enum classes also have a name and ordinal property

val topping = Toppings.MAYONNAISE
println(topping.name) //Prints MAYONNAISE
println(topping.ordinal) //Prints 1

Advanced Enum Classes

Since enum classes are classes, they are free to have properties and methods like any other classes. Here is an example of an enum class with properties.

enum class Burgers(val burgerName : String){
    CAPTAIN_JACK("Captain Pepper Jack Marrow Burger"),
    MY_FARRO_LADY("My Farro Lady Burger"),
    ENDIVE("Endive Had the Time of My Life Burger")
}

Since each constant in the enum class is an instance of the enum class, we need to initialize all properties like we would any other class. We reference the enum’s property just like any other class property

val b = Burgers.CAPTAIN_JACK
println(b.burgerName) //prints Captain Pepper Jack Marrow Burger

We can also define enum classes with methods, including abstract methods. This example shows an enum class where each constant in the enum class overrides the abstract method found in the enum.

enum class Family{

    LOUISE {
        override fun sayCatchPhrase(): String =
                "I can smell fear on you"
    },

    GENE {
        override fun sayCatchPhrase(): String =
                "I can't tell you my full name! You know Mom won't tell me my middle name!"
    },

    TINA {
        override fun sayCatchPhrase(): String =
                "Here's a bunch of numbers that may look random, but they're my phone number"
    },

    LINDA {
        override fun sayCatchPhrase(): String =
                "Teddy will eat whatever you put in front of him. Remember when he ate that receipt?"
    },

    BOB {
        override fun sayCatchPhrase(): String =
                "So that's what a prostate exam is"
    };

    abstract fun sayCatchPhrase() : String
}

We call the method like we would on any other class.

val f = Family.BOB
print(f.sayCatchPhrase()) //Prints So that's what a prostate exam is

Putting it Together

Here is an example program that shows off all three of the enum examples discussed.

package ch4.enumclasses

enum class Toppings {
    KETCHUP,
    MAYONNAISE,
    LETTUCE,
    TOMATO
}

enum class Burgers(val burgerName : String){
    CAPTAIN_JACK("Captain Pepper Jack Marrow Burger"),
    MY_FARRO_LADY("My Farro Lady Burger"),
    ENDIVE("Endive Had the Time of My Life Burger")
}

enum class Family{

    LOUISE {
        override fun sayCatchPhrase(): String =
                "I can smell fear on you"
    },

    GENE {
        override fun sayCatchPhrase(): String =
                "I can't tell you my full name! You know Mom won't tell me my middle name!"
    },

    TINA {
        override fun sayCatchPhrase(): String =
                "Here's a bunch of numbers that my look random, but they're my phone number"
    },

    LINDA {
        override fun sayCatchPhrase(): String =
                "Teddy will eat whatever you put in front of him. Remember when he ate that receipt?"
    },

    BOB {
        override fun sayCatchPhrase(): String =
                "So that's what a prostate exam is"
    };

    abstract fun sayCatchPhrase() : String
}

fun familyDemo(f : Family){
    println("${f.name} says ${f.sayCatchPhrase()}")
}

fun main(args : Array<String>){
    //Enum classes have a name and ordinal property
    println("Printing all values found in Toppings")
    Toppings.values().forEach { println("${it.name}: ${it.ordinal}") }.also { print("\n") }

    //We can also add our own properties like we did in Burgers
    println("Printing all values found in Burgers with burgerName")
    Burgers.values().forEach { println("${it.name}: ${it.ordinal} => ${it.burgerName}") }.also { print("\n")}

    //And we can even call methods on the enum class
    println("Printing all values found in Family with catchPhrase()")
    Family.values().forEach { println("${it.name}: ${it.ordinal} => ${it.sayCatchPhrase()}") }
    println()

    var burger = "Burger with "
    val topping = Toppings.LETTUCE

    //Enums work great with the when function
    burger += when (topping){
        Toppings.KETCHUP -> "ketchup"
        Toppings.MAYONNAISE -> "mayonnaise"
        Toppings.LETTUCE -> "lettuce"
        Toppings.TOMATO -> "tomato"
    }

    println("Burger is => $burger \n")

    val family = Family.GENE
    familyDemo(family)
}

We get the following output when the program is run.

Printing all values found in Toppings
KETCHUP: 0
MAYONNAISE: 1
LETTUCE: 2
TOMATO: 3

Printing all values found in Burgers with burgerName
CAPTAIN_JACK: 0 => Captain Pepper Jack Marrow Burger
MY_FARRO_LADY: 1 => My Farro Lady Burger
ENDIVE: 2 => Endive Had the Time of My Life Burger

Printing all values found in Family with catchPhrase()
LOUISE: 0 => I can smell fear on you
GENE: 1 => I can't tell you my full name! You know Mom won't tell me my middle name!
TINA: 2 => Here's a bunch of numbers that my look random, but they're my phone number
LINDA: 3 => Teddy will eat whatever you put in front of him. Remember when he ate that receipt?
BOB: 4 => So that's what a prostate exam is

References

https://kotlinlang.org/docs/reference/enum-classes.html

Kotlin Companion Objects

Kotlin has a companion object feature that lets us associate a single object with a class. Companion objects can serve a number of purposes. For example, we may wish to associate methods such as factory methods with a companion object rather than an instance of a class. Likewise, we can use companion object to store constants or count the number of times an instance of a class was created.

Here is an example Burger class that uses a companion object.

class Burger(val name : String) {
    
    //Create and define a companion object.
    companion object SpecialBurgers {
        //Here are some constants
        const val TOP_BUN_BURGER = "Top Bun Burger"
        const val NATIONAL_PASS_THYME_BURGER = "National Pass Thyme Burger"
        const val KALE_MARY_BURGER = "Kale Mary Burger"
        const val SNIPWRECKED_BURGER = "Snipwrecked Burger"

        //Count the number of burgers that were created
        private var _instances = 0
        
        val burgers : Int
            get() = _instances

        fun burgerList() =
                listOf(TOP_BUN_BURGER,
                        NATIONAL_PASS_THYME_BURGER,
                        KALE_MARY_BURGER,
                        SNIPWRECKED_BURGER)
    }

    init {
        check(name in burgerList(), { "$name not in Burger List"})
        
        //Increment the number of burgers created
        _instances++
    }
}

The Burger class has a companion object called SpecialBurgers. Readers will notice that creating a companion object is almost the same as writing a class. The difference is that the companion object is instantiated with the JVM loads the Burger class into memory. Companion objects can have init blocks, but they are not allowed to have constructors. Finally, we are not allowed to create instances of companion objects.

The SpecialBurgers object has four constants, a variable called _instances, and some methods. The init block in Burger increments _instances. It also calls burgerList() found in the companion object to check if the name variable is found in burgerList(). If the name isn’t in burgerList(), the program will throw an exception.

We can use the companion object later on by using the class name followed by the dot (.) syntax. Here is the later half of the program that uses the companion object.

fun main(args : Array<String>){
    val badBurger = "Barley Davidson Burger"

    try {
        //This will throw an exception because of the
        //check in Burger's init block
        val barleyBurger = Burger(badBurger)
    } catch (e : Exception){
        println("$badBurger is not in ${Burger.burgerList()}")
    }

    //We can reference the KALE_MARY_BURGER constant found in 
    //Burger's companion object
    val firstBurger = Burger.KALE_MARY_BURGER
    val kaleBurger = Burger(firstBurger)
    
    //This next line uses the burgers property found in SpecialBurgers to
    //print how many burgers we have created
    println("Created ${kaleBurger.name} and we have served ${Burger.burgers} burgers")

    //We can also reference our burger constants directly
    val snipsBurger = Burger(Burger.SNIPWRECKED_BURGER)
    println("Created ${snipsBurger.name} and we have served ${Burger.burgers} burgers")

    //Finally, the SpecialBurgers is an actual object, so we
    //can store it in a variable when needed
    val burgerCompanion = Burger.SpecialBurgers
    
    val topBunBurger = Burger(burgerCompanion.TOP_BUN_BURGER)
    println("Created ${topBunBurger.name} and we have served ${Burger.burgers} burgers")
}

We use the SpecialBurgers object in a few different ways in main. First, we try and make a barleyBurger. Creating a barleyBurger throws an exception because it’s burger name, “Barley Davidson Burger” is fails the name check found in Burger’s init block.

The next burger we try making is the kaleBurger. First we show how to store a value found in Burger.KALE_MARY_BURGER in a variable. As you can see, it acts just like any other variable. The next line creates an instance of Burger by passing firstBurger to the constructor of Burger. Creating the kaleBurger succeeds and the init block in Burger increments the _instances variable by one. The next line prints out the name of the burger and how many burgers we have served. Notice that it’s Burger.burgers because we are using the burger property found on SpecialBurgers.

The snipsBurger is created by passing Burger.SNIPWRECKED_BURGER direclty into the Burger’s constructor. Once against _instances is incremented and we now have served two specialty burgers. The final burger is created by first assigning SpecialBurgers to a variable first. Since SpecialBurgers is an object, it can be assigned to other references when needed. We create our topBunBurger by using burgerCompanion.TOP_BUN_BURGER.

Putting it Together

Here is the entire program, followed by the output.

package ch4.companionobjects

class Burger(val name : String) {
    companion object SpecialBurgers {
        //Heere are some constants
        const val TOP_BUN_BURGER = "Top Bun Burger"
        const val NATIONAL_PASS_THYME_BURGER = "National Pass Thyme Burger"
        const val KALE_MARY_BURGER = "Kale Mary Burger"
        const val SNIPWRECKED_BURGER = "Snipwrecked Burger"

        //Count the number of burgers that were created
        private var _instances = 0

        val burgers : Int
            get() = _instances

        fun burgerList() =
                listOf(TOP_BUN_BURGER,
                        NATIONAL_PASS_THYME_BURGER,
                        KALE_MARY_BURGER,
                        SNIPWRECKED_BURGER)
    }

    init {
        check(name in burgerList(), { "$name not in Burger List"})

        //Increment the number of burgers created
        _instances++
    }
}

fun main(args : Array<String>){
    val badBurger = "Barley Davidson Burger"

    try {
        //This will throw an exception because of the
        //check in Burger's init block
        val barleyBurger = Burger(badBurger)
    } catch (e : Exception){
        println("$badBurger is not in ${Burger.burgerList()}")
    }

    //We can reference the KALE_MARY_BURGER constant found in
    //Burger's companion object
    val firstBurger = Burger.KALE_MARY_BURGER
    val kaleBurger = Burger(firstBurger)

    //This next line uses the burgers property found in SpecialBurgers to
    //print how many burgers we have created
    println("Created ${kaleBurger.name} and we have served ${Burger.burgers} burgers")

    //We can also reference our burger constants directly
    val snipsBurger = Burger(Burger.SNIPWRECKED_BURGER)
    println("Created ${snipsBurger.name} and we have served ${Burger.burgers} burgers")

    //Finally, the SpecialBurgers is an actual object, so we
    //can store it in a variable when needed
    val burgerCompanion = Burger.SpecialBurgers

    val topBunBurger = Burger(burgerCompanion.TOP_BUN_BURGER)
    println("Created ${topBunBurger.name} and we have served ${Burger.burgers} burgers")
}

This is the output when run.

Barley Davidson Burger is not in [Top Bun Burger, National Pass Thyme Burger, Kale Mary Burger, Snipwrecked Burger]
Created Kale Mary Burger and we have served 1 burgers
Created Snipwrecked Burger and we have served 2 burgers
Created Top Bun Burger and we have served 3 burgers

References

https://kotlinlang.org/docs/reference/properties.html

Kotlin Nested Classes

Nested classes are classes that exist inside of another class. They serve a variety of purposes. For example, we may use a nested class to associate an object with its outer class. Some classes are used to implement another class but only have meaning inside of the class they are used in. Think about a Node class that is used inside of a Linked List implementation. A Node on its own doesn’t have a lot of value outside of a Linked List. Finally, we may use classes that only have a single use and are no longer needed. Event handling code is a common use case for such classes.

Kotlin supports nested classes, inner classes, and anonymous inner classes. Let’s examine each in detail.

Nested Classes

Nested classes are most often used to associate two related classes together where one class would have very little meaning or no meaning without its outer class. Builder objects are a common use case for such classes. Let’s take a look at an example.

/**
 * The Restaurant class is using the Builder pattern. It has a private
 * constructor. Inside of the Restaurant class is a RestaurantBuilder class. The
 * RestaurantBuilder has access to Restaurant's private constructor, but not it's properties
 */
class Restaurant private constructor(val name: String, val address: String) {

    class RestaurantBuilder {
        var name: String = ""
        var address: String = ""

        fun addName(name: String): RestaurantBuilder {
            this.name = name
            return this
        }

        fun addAddress(address: String): RestaurantBuilder {
            this.address = address
            return this
        }

        fun build(): Restaurant = Restaurant(name, address)
    }

    override fun toString(): String = "$name, $address"
}

Creating a nested class in Kotlin just involves writing another class inside of its outer class. The nested class has access all of the outer class’s behavior, but it does not hold a reference to the outer class and as such, it may not access any of the outer class’ properties. In the above code snippet, our RestaurantBuilder class has a build() method that can use Restaurant’s private constructor.

This is how we would use the RestaurantBuilder class to create an instance of Restaurant.

val builder = Restaurant.RestaurantBuilder()
val bobsBurgers = builder
                    .addAddress("104 N Main")
                    .addName("Bob's Burgers")
                    .build()
println(bobsBurgers)

Inner Classes

Inner classes are similar to nested classes but they hold a reference to the outer class instance. This allows the inner class object to use both state and behavior from that belongs to the outer object. For this reason, an inner class object can only be created within the outer class. Attempting to create an inner class object outside of the outer class results in a compiler error.

Linked lists are a good use case for inner classes. Many Linked list implementations use a helper Node class to help manage the implementation of the list. The Node really has no use outside of the linked list, so it doesn’t make sense to make it a standalone class. Also, since the Node may need to access the Linked Lists’s private data, it’s helpful to make it an inner class.

This DeliveryRoute class is a Linked List like object that uses an inner class. All of the Stop class instances refer to the next stop on the route.

/**
 * DeliveryRoute has a Stop inner class. Since Stop is an inner class, Stop has
 * access to both the properties and methods of DeliveryRoute
 */
class DeliveryRoute {
    var currentStop: Stop?

    init {
        currentStop = Stop("Bobs",
                Stop("Jimmys",
                        Stop("Teddys",
                                Stop())))
    }

    inner class Stop(val name: String? = null, val next: Stop? = null) {
        fun advance(): Stop? {
            //CurrentStop is a property in DeliveryRoute
            currentStop = currentStop?.next
            return currentStop
        }

        override fun toString(): String {
            return this.name ?: "End of Route"
        }
    }
}

Kotlin inner classes are created by using the inner keyword in front of the class keyword inside the body of an outer class. Our stop class works like both a linked list node and an iterator. It holds a reference to the next stop and the advance method updates DeliveryRoute’s current stop property. When we use it later on, we can use the stop object to advance through the delivery router.

val route = DeliveryRoute()
var stop : DeliveryRoute.Stop? = route.currentStop

while(stop != null){
    println(route.currentStop)
    stop = stop.advance()
}

Anonymous Inner Classes

Anonymous inner classes are classes that have no name (and are therefore anonymous) and are created on the fly. The most common use case is GUI event handling code. This is because the code for each control on GUI is specific to each control and it, therefore, makes no sense to have named classes for each event object.

Kotlin uses the object expressions to create anonymous inner classes.

fun addAction(e: Runnable) {
    println(e.toString())
}

addAction(object: Runnable{
        override fun run() {}

        override fun toString(): String = "Object expression toString()"

})

The object expression can be used with both abstract classes and interfaces. Lambda expressions are generally preferable when dealing with single method interfaces such as Runnable, but it is an easy interface to use when demonstrating object expressions.

Putting it Together

Here is a complete program that shows off all three nested classes and the output.

package ch4.nestedclasses

/**
 * The Restaurant class is using the Builder pattern. It has a private
 * constructor. Inside of the Restaurant class is a RestaurantBuilder class. The
 * RestaurantBuilder has access to Restarant's private constructor, but not it's properties
 */
class Restaurant private constructor(val name: String, val address: String) {

    class RestaurantBuilder {
        var name: String = ""
        var address: String = ""

        fun addName(name: String): RestaurantBuilder {
            this.name = name
            return this
        }

        fun addAddress(address: String): RestaurantBuilder {
            this.address = address
            return this
        }

        fun build(): Restaurant = Restaurant(name, address)
    }

    override fun toString(): String = "$name, $address"
}

/**
 * DeliveryRoute has a Stop inner class. Since Stop is an inner class, Stop has
 * access to both the properties and methods of DeliveryRoute
 */
class DeliveryRoute {
    var currentStop: Stop?

    init {
        currentStop = Stop("Bobs",
                Stop("Jimmys",
                        Stop("Teddys",
                                Stop())))
    }

    inner class Stop(val name: String? = null, val next: Stop? = null) {
        fun advance(): Stop? {
            //CurrentStop is a property in DeliveryRoute
            currentStop = currentStop?.next
            return currentStop
        }

        override fun toString(): String {
            return this.name ?: "End of Route"
        }
    }
}

fun addAction(e: Runnable) {
    println(e.toString())
}

fun main(args : Array<String>){
    val builder = Restaurant.RestaurantBuilder()
    val bobsBurgers = builder
                        .addAddress("104 N Main")
                        .addName("Bob's Burgers")
                        .build()
    println(bobsBurgers)

    val route = DeliveryRoute()
    var stop : DeliveryRoute.Stop? = route.currentStop

    while(stop != null){
        println(route.currentStop)
        stop = stop.advance()
    }

    addAction(object: Runnable{
        override fun run() {
            TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
        }

        override fun toString(): String = "Object expression toString()"

    })
}

This is the output when run.

Bob's Burgers, 104 N Main
Bobs
Jimmys
Teddys
End of Route
Object expression toString()

Kotlin Abstract Classes

Abstract classes provide developers a place to provide an abstraction point while still being able to include common functionality. In a manner of speaking, an abstract class can be thought of as an unfinished class. It depends on subclasses to finish the class by defining the final behavior of the class. Since abstract classes serve as base classes for concrete classes, we can use them for polymorphism purposes.

Let’s consider a problem that uses abstract classes. We have two cooks. One cook is Bob and he makes burgers. The other cook is Jimmy and he makes pizza. Both cooks have names and own their own restaurants. They also cook food. So in this sense, they both have a lot in common. Although both cooks have the same behavior, cook(), they both cook different things. Bob cooks burgers while Jimmy cooks pizza.

We don’t want to define two classes that are almost completely the same. That would be a really bad practice because then a change in both classes would need to get updated in both places. We also would not be able to define methods that could use both classes without function overloading. In other words, our code would become highly coupled to the implementation of each cook instead of being able to use both cooks generally.

Abstract Class

Here is our Cook class that defines properties and behaviors for both Bob and Jimmy.

abstract class Cook(val name : String, val resturant : String){

    /**
     * This method doesn't have a body.
     * Child classes must define the cook() method
     * or they too must be abstract
     */
    abstract fun cook() : String

    override fun toString(): String {
        return this.name
    }
}

Classes are made abstract in Kotlin by adding the abstract keyword in front of the class keyword. We will never be able to create an instance of the Cook class due to it being marked abstract. Since our cooks cook different meals, we leave the cook method undefined by marking it as abstract as well. Abstract methods are open by default in Kotlin since they have to be overridden by definition. Since we have an abstract cook() method in Cook, we are forcing all subclasses to define this behavior.

Bob

Bob is a Cook so it makes sense that he subclasses Cook. By extending Cook, we are saying that Bob cooks. However, the Cook class doesn’t say what he cooks or how he cooks. We need Bob to extend Cook and define the cook() method.

class Bob : Cook("Bob Belcher", "Bob's Burgers") {

    override fun cook(): String {
        return "Chorizo Your Own Adventure Burger"
    }
}

Since Bob is a cook, he has a name and a restaurant. He not only cooks, but we now know how Bob cooks. Since Bob is a child class of Cook, he can be assigned to any Cook variable.

Jimmy

Jimmy is similar to Bob but he cooks pizzas.

class Jimmy : Cook("Jimmy Pesto", "Petso's Pizzeria"){
    override fun cook(): String {
        return "Boring Pizza"
    }
}

Using Abstract Classes

Now that we have Cook, Bob, and Jimmy defined, we are free to use them in our program. Let’s write a function that takes a Cook, says hello, and tells us what they cook.

fun sayHello(c : Cook){
    println("Hello! My name is ${c.name} and I cook ${c.cook()}")
}

Since Cook has a name property and a cook() method, the compiler knows that its safe to call cook() on any cook. Which version of the cook() method gets called depends on the runtime type of Cook. Regardless of who is the cook, the program will still work properly.

The sayHello function is said to be loosely coupled to the Cook class. Although we can only actually make Bob or Jimmy objects, the Cook class provides a degree of indirection for developer purposes. If we add other cooks, later on, the sayHello function can utilize those objects as well because they are all members of a common supertype.

Putting it Together

Use abstract classes when

  • You need an abstraction point
  • You have one or more child classes that have common behavior and properties
  • You need to declare behavior but need to define it in child classes

Here is an example program that ties everything together

package ch4.abstractclasses

abstract class Cook(val name : String, val resturant : String){

    /**
     * This method doesn't have a body.
     * Child classes must define the cook() method
     * or they too must be abstract
     */
    abstract fun cook() : String

    override fun toString(): String {
        return this.name
    }
}

class Bob : Cook("Bob Belcher", "Bob's Burgers") {

    override fun cook(): String {
        return "Chorizo Your Own Adventure Burger"
    }
}

class Jimmy : Cook("Jimmy Pesto", "Petso's Pizzeria"){
    override fun cook(): String {
        return "Boring Pizza"
    }
}

fun sayHello(c : Cook){
    println("Hello! My name is ${c.name} and I cook ${c.cook()}")
}

fun main(args : Array<String>){
    val bob = Bob()
    val jimmy = Jimmy()

    sayHello(bob)
    sayHello(jimmy)
}

We get this output when run.

Hello! My name is Bob Belcher and I cook Chorizo Your Own Adventure Burger
Hello! My name is Jimmy Pesto and I cook Boring Pizza

Kotlin Casting

Kotlin is a strongly typed language and as such, the compiler only allows developers to assign values to variables of the correct type. What that means in English is that only whole numbers can be assigned to type Int, Strings can only be assigned to variables of type String, and so on. Attempting to assign the number three to a String variable results in a compiler error.

The typing system is incredibly advantageous and helps reduce many bugs that are known to happen in weakly typed languages. Developer tools can also more easily examine variables in strongly typed languages and provide code suggestions or check for incompatibilities. For example, when passing a variable to a function, both the compiler and the IDE will issue errors if the variable is incompatible with the method signature.

Types in strongly typed languages can’t be converted readily. In order to convert one type of object into another type, we have to use a technique called casting. Casting tells the compiler that a conversion is OK to make and we are accepting the risk of converting from one data type into another type. Let’s begin with a few classes that give us an idea of what we are working with here.
Casting
Now let’s begin with some examples of casting between different types.

//This is an upcast. Any is the supertype of all Kotlin classes so it's
//acceptable to assign a Bob object to an Any reference
val bob : Any = Bob()

//Now let's downcast Bob to GrillCook. We use the is operator for this task.
if (bob is GrillCook){
    //We can now use the bob varaible as if it were GrillCook rather than Any
    //This is because the compiler now knows that bob is at least a GrillCook
    println("Bob is a GrillCook")
}

The above example shows upcasting and downcasting. When we created the bob variable, we declared its type to be Any. Any is the base class of all Kotlin objects, so it’s complete acceptable to treat a Bob object as if it’s an Any. Later on, we wish to cast Bob from any Any object into a GrillCook.

The compiler has no way to verify if Bob is a GrillCook. It only knows that Bob is any Any object at this point. To make change Bob from Any into GrillCook, we have to perform a downcast. That’s done in the if statement with if (bob is GrillCook. This statement checks the type of Bob at runtime and if Bob is a GrillCook, it will convert Bob from Any to GrillCook. The compiler will treat Bob like a GrillCook inside of the if block because it knows that if the is test succeeded, Bob must be a GrillCook.

We can also check if an object isn’t a certain kind of object. Let’s consider this code example.

val linda : Any = Linda()

if (linda !is GrillCook){
    //Linda will still be treated as Any because the compiler only
    //knows that she isn't the grill cook
    println("Linda is not the GrillCook)
}

When we add an exclamation point in front of is to make !is, we are saying to check if the object isn’t a certain kind of object. In this case, Linda does not extend GrillCook, so she’s not of type GrillCook. We proceed into the if block but Linda is still treated as type Any. The compiler only knows that she isn’t a GrillCook, but it still doesn’t know exactly what Linda is other than Any.

We can use the when expression for casting also. Let’s take a look at Gene.

val gene : Children = Gene()

when (gene) {
    //This line runs if gene is Gene
    is Gene -> println("Gene is Gene")

    //This line would only run if gene is Tina (he's not)
    is Tina -> println("Gene is Tina")

    //This line would only run if gene is Louise (he's not)
    is Louise -> {
        println("Gene is Louise")
    }
}

In the case of the when statement, if the gene variable, which if of type Children, is Gene, then the is Gene -> code is executed. The gene variable will be treated as type Gene for the duration of the block because once again, the compiler knows that the variable has to be Gene at that point. The other two clauses, is Tina -> and is Louise ->, do not execute in this example. This is because the gene variable is not either Tina or Louise so the is check returns false for those two conditions.

So far, all of the casts we have performed are known as safe casts. They are safe because we have verified the type of the object at runtime and only performed the cast after it was safe to do so. However, we can perform unsafe casts. When we perform an unsafe cast, the compiler will usually issue a warning, but it will allow us to make the cast.

val teddy : Any = GrillCook() as Children

This cast will always result in a ClassCastException. That’s because GrillCook is not a Children object.

Putting it Together

package ch1.casting

open class GrillCook

class Bob : GrillCook()

open class Wife

class Linda : Wife()

open class Children

class Tina : Children()

class Gene : Children()

class Louise : Children()

fun main(args : Array<String>){
    //These are all upcasts. Since our variables are of the base type
    //we can safely assign them to child objects without the need to cast.
    val bob : Any = Bob()
    val linda : Any = Linda()
    val gene : Children = Gene()

    //This is a safe form of down casting.
    //If Bob is in fact a GrillCook, the next line will print
    //"Bob is a GrillCook"
    if (bob is GrillCook){
        //Going forward, the compiler will treat Bob as a GrillCook object
        //Because it now knows that Bob is a GrillCook

        println("Bob is a GrillCook")
    }

    //This is the reverse of what was shown above.
    //The line inside of the if statement only runs when linda
    //is not a GrillCook. Note that the linda variable is still treated
    //as type Any because the compiler only knows that Linda isn't a GrillCook
    if (linda !is GrillCook){
        println("Linda is not the GrillCook")
    }

    //We can also cast with the when expression
    when (gene) {
        //This line runs if gene is Gene
        is Gene -> println("Gene is Gene")

        //This line would only run if gene is Tina (he's not)
        is Tina -> println("Gene is Tina")

        //This line would only run if gene is Louise (he's not)
        is Louise -> {
            println("Gene is Louise")
        }
    }

    //This is an example of an unsafe cast. It forces the compiler
    //to make the conversion, but at runtime, we get a ClassCastException.
    //Notice that the compiler does issue a warning here, but since this statement
    //is valid, the code will compile
    val teddy : Any = GrillCook() as Children
}

When run, we get the following output.

Bob is a GrillCook
Linda is not the GrillCook
Gene is Gene
Exception in thread "main" java.lang.ClassCastException: ch1.casting.GrillCook cannot be cast to ch1.casting.Children
	at ch1.casting.CastingKt.main(Casting.kt:58)

References

https://kotlinlang.org/docs/reference/typecasts.html

%d bloggers like this: