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

Kotlin Overriding Methods

Method overriding is an important part of polymorphism. When we override methods, we are redefining the behavior of a base class method in a child class. Doing so allows us to create specialized cases of our classes that still work with existing code. At runtime, the JVM will use the proper implementation of a method depending on the object’s type.

Kotlin has it’s own twist on overriding classes and methods. OOP has an issue known as “fragile super-classes” where changes in base classes break child classes due to developers not fully considering how changes in a base class may affect a child class. Due to the fragile super-class issue, Kotlin disables both inheritance and method overriding by default. In order to use either, we must signal to the compiler that we can extend a class or override its methods by using the open keyword. Open classes and methods force developers to consider the possibility that a class may be extended or it’s methods or overridable.

Here is an example of an open class.

/**
 * Base class. This class has to be marked as open to allow
 * inheritance in Kotlin
 */
open class GrillCook {

    /**
     * We also have to mark our methods as open to allow
     * for overriding
     */
    open fun print() {
        println("Grill Cook is the Master")
    }
}

Our GrillCook class can be extended because we have included the open keyword in front of the class keyword. Likewise, our print() method is also marked as open can be therefore overridden by child classes. So our next class, BobBelcher takes advantage of the opportunity.

/**
 * Child class that override print in the base class. Notice that this
 * class declares it's name property as open, which means child classes
 * can override the property also
 */
open class BobBelcher(open val name : String) : GrillCook(){

    //override keyword signals that we are overriding a super class method
    override fun print(){
        println("$name is the Master")
    }
}

BobBelcher overrides the print() method and changes what is printed to the console. Since this class has a name property, we print “$name is the Master” rather than “Grill Cook is the Master”. To override print(), Bob has to have the keyword override in the method signature. This is to prevent another common OOP problem where developers intended to override a function but accidentally overloaded it instead, usually due to some sort of typo.

Kotlin supports property overriding if properties are marked as open. The name property on BobBelcher is marked as open, which means child classes can also override the name property. This is what LindaBelcher does.

/**
 * LindaBelcher is a child class of BobBelcher and overrides the
 * name property to set it to Linda rather than some other string
 */
class LindaBelcher : BobBelcher("") {
    override val name = "Linda" //Use the override keyword to override a property

    override fun print() {
        //Use the print implementation found in BobBelcher
        super.print()
        println("Alright!!!")
    }
}

LindaBelcher overrides the name property found in BobBelcher and returns Linda. That means that no matter what String gets passed to the BobBelcher constructor invocation in LindaBelcher, we end up getting “Linda” when we use the name property. It’s also worth noting that Linda has a call to super.print() inside of the print() function. Using the super.print() tells the compiler to call the implementation of print() found in Bob. Then Linda’s print function completes by printing “Alright!!!” to the console.

Putting it Together

Here is a complete program that demonstrates method overriding in Kotlin.

package ch1.overriding

/**
 * Base class. This class has to be marked as open to allow
 * inheritance in Kotlin
 */
open class GrillCook {

    /**
     * We also have to mark our methods as open to allow
     * for overriding
     */
    open fun print() {
        println("Grill Cook is the Master")
    }
}

/**
 * Child class that override print in the base class. Notice that this
 * class declares it's name property as open, which means child classes
 * can override the property also
 */
open class BobBelcher(open val name : String) : GrillCook(){

    override fun print(){
        println("$name is the Master")
    }
}

/**
 * LindaBelcher is a child class of BobBelcher and overrides the
 * name property to set it to Linda rather than some other string
 */
class LindaBelcher : BobBelcher("") {
    override val name = "Linda"

    override fun print() {
        //Use the print implementation found in BobBelcher
        super.print()
        println("Alright!!!")
    }
}

fun main(args : Array<String>){
    val grillCook = GrillCook()
    val bob = BobBelcher("Bob")
    val linda = LindaBelcher()

    println("Using grillCook::print")
    grillCook.print()

    println("\nUsing BobBelcher::print")
    bob.print()

    println("\nUsing LindaBelcher::print")
    linda.print()
}

This is the output when run.

Using grillCook::print
Grill Cook is the Master

Using BobBelcher::print
Bob is the Master

Using LindaBelcher::print
Linda is the Master
Alright!!!

It works because at runtime, the JVM knows to use the proper implementation of print() based on the objects type at runtime. This is why all three objects print different outputs when print is called.

References

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

Kotlin Covariant Return types

It’s legal to substitute a child class as the return type of a base class when overriding a function. Doing so allows us to avoid unnecessary casting. Let’s look at an example.

abstract class Cook(val name : String) {

    //Abstract method that returns Cook
    abstract fun copy() : Cook
}

class Bob(name : String) : Cook(name) {

    /**
     * Notice how this method changes the
     * return type from Cook to Bob. This is legal
     * because Bob is a child class of Cook.
     *
     * This is called covariant return types
     */
    override fun copy(): Bob {
        return Bob(this.name)
    }
}

In this example, we have a base class Cook that has an abstract copy() method. The copy() method returns a Cook object. However, Bob overrides copy() and returns Bob. The difference may look small, but when used we do not need to downcast Cook objects back to Bob.

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

    //No casting required since copy() returns a Bob
    val bobCopy : Bob = bob.copy()
    val cookCopy : Cook = bob.copy()

    println(bob.name)
    println(bobCopy.name)
    println(cookCopy.name)
}

If we had the copy() method in Bob return Cook rather than Bob, we would need to down cast.

class Bob(name : String) : Cook(name) {

    //This verion returns the Base Type
    override fun copy(): Cook {
        return Bob(this.name)
    }
}

val bobCopy : Bob = bob.copy() as Bob //Now a cast is required

So although we could have Bob.copy() return Cook, we now have to immediately downcast the result back into a Bob object. This doesn’t really make a lot of sense for a copy() method, which is why it’s acceptable to override a method by returning a child class type when needed.

Putting it Together

Here is an example program that shows off covariant return types.

package ch1.overriding

abstract class Cook(val name : String) {

    //Abstract method that returns Cook
    abstract fun copy() : Cook
}

class Bob(name : String) : Cook(name) {

    /**
     * Notice how this method changes the
     * return type from Cook to Bob. This is legal
     * because Bob is a child class of Cook.
     *
     * This is called covariant return types
     */
    override fun copy(): Bob {
        return Bob(this.name)
    }
}

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

    //No casting required since copy() returns a Bob
    val bobCopy : Bob = bob.copy()
    val cookCopy : Cook = bob.copy()

    println(bob.name)
    println(bobCopy.name)
    println(cookCopy.name)
}

Here is the output when run

Bob
Bob
Bob

Function Overloading

Kotlin supports function overloading, which is a form of polymorphism where the compiler deduces which function should get called based on the type and position of the arguments. When we overload functions, we create functions that have the same name, but different return types or arguments. This allows us to write code that is customized based on the type of the parameters based on the function. Let’s begin with an example.

class Bob(val name : String = "Bob")

/**
 * Here are five functions with the same name. Each function is resolved
 * based on the type of parameters that are supplied.
 */
fun foo(name : String) {
    println("Inside foo(String)")
    println(name)
}

fun foo(num : Int){
    println("Inside foo(Int)")
    println(num)
}

fun foo(bob : Bob){
    println("Inside of foo(Bob)")
    println(bob.name)
}

fun foo(any : Any){
    println("Inside of foo(Any)")
    println(any)
}

fun foo(name : String, any : Any){
    println("Inside of foo(name, any)")
    println("$name, $any")
}

Here are five functions named foo. The compiler can figure out which function to use because all five of our foo functions take a different type as an argument. So when we call foo(“Hello World”) the version of foo that takes a String parameter is used because “Hello World” is a String. Likewise, when we call foo(12), the compiler will choose the foo(Int) version because 12 is an int. The final version of foo takes two parameters, String and Any. It will get called when we supply a String and another object so foo(“Hello”, “Bob Belcher”) matches to foo(String, Any) as well as foo(“Bob is #”, 1).

Optional Arguments

The designers of Kotlin realized that while function overloading is useful, it can also turn into verbose code. Kotlin supports optional arguments and named arguments to help address the problem.

fun foo(num : Long = 0,
        name : String = "",
        bob : Bob = Bob(),
        any : Any = object{
    override fun toString() = "Object"
}){
    println("Inside of default argument function")
    println(num)
    println(name)
    println(bob)
    println(any)
}

When this code compiles, the compiler will basically overload the foo function for us, but we can avoid writing a bunch of overloaded function when we use optional arguments correctly. This version of foo can be called by writing foo(), which uses all of the defaults, foo(num = 12L), which uses the defaults except for the num parameter which is set to 12. We can also write foo(any = Bob()), and so on.

Putting it Together

Here is a sample driver program that shows off function overloading and optional arguments.

package ch1.overloading

class Bob(val name : String = "Bob")

/**
 * Here are five functions with the same name. Each function is resolved
 * based on the type of parameters that are supplied.
 */
fun foo(name : String) {
    println("Inside foo(String)")
    println(name)
}

fun foo(num : Int){
    println("Inside foo(Int)")
    println(num)
}

fun foo(bob : Bob){
    println("Inside of foo(Bob)")
    println(bob.name)
}

fun foo(any : Any){
    println("Inside of foo(Any)")
    println(any)
}

fun foo(name : String, any : Any){
    println("Inside of foo(name, any)")
    println("$name, $any")
}

//Note that we can also use default arguments in Kotlin
//which lets us reduce function overloading
fun foo(num : Long = 0,
        name : String = "",
        bob : Bob = Bob(),
        any : Any = object{
    override fun toString() = "Object"
}){
    println("Inside of default argument function")
    println(num)
    println(name)
    println(bob)
    println(any)
}

fun main (args : Array<String>){
    //Resolves to foo(String) because "Hello World" is a String
    foo("Hello World")

    //Resolves to foo(Int) because 1 is an int
    foo(1)

    //Resolve to foo(Bob) because Bob is Bob
    foo(Bob())

    //Resolves to foo(Any) because the object expression matches
    //to Any, which is the base type of all objects in Kotlin
    foo(object { override fun toString() = "Object"})
    
    //Matches to foo(String, Any)
    foo("Hello ", Bob())

    foo() //Called with optional arguments.

    foo(num = 2L) //Called with only using one of the optional arguments
}

When run, we get this output

Inside foo(String)
Hello World
Inside foo(Int)
1
Inside of foo(Bob)
Bob
Inside of foo(Any)
Object
Inside of foo(name, any)
Hello , ch1.overloading.Bob@3caeaf62
Inside of default argument function
0

ch1.overloading.Bob@6a38e57f
Object
Inside of default argument function
2

ch1.overloading.Bob@5577140b
Object

Kotlin Access Modifiers

Encapsulation is a huge part of OOP. Hiding method and behavior is an important part in achieving encapsulation so Kotlin provides us with access modifiers to help in this effort.

Kotlin has four kinds of access modifiers

  • private—Items marked as private are visible only to the class
  • protected-Items marked as protected are visible to the class and it’s child classes
  • internal—Items marked as internal are accessible to all members of the Kotlin module but are not available outside of the module
  • public—Items marked as public are available to the module and outside of the module using the import statement
    1. Let’s walk through some examples.

      Public

      Public is the default visiblity in Kotlin. If the public keyword is omitted, the member will be marked as public.

      fun printBurgerOfTheDay(burgerName : String = "Never been Feta")
              = println(burgerName)
      

      The printBurgerOfTheDay function is accessible to all code within the module that contains it, plus any module that imports the printBurgerOfTheDay function.

      Internal

      Members marked as internal are visible to the module but are not accessible outside of the module. In other words, internal members act like public members, but they can’t be imported into other modules.

      internal var burgerName = "Mission A-Corn-Plished Burger"
      

      The above variable can be accessed in the module. It may not be imported into another module.

      Protected

      While public and internal access can be used on both class and non-class members, protected only applies to classes.

      open class Cook {
          protected val position = "Cook"
          private val name = "No Name"
      
          override fun toString(): String {
              return position + ", " + name
          }
      }
      
      class Bob : Cook() {
      
          fun printPosition() = println(position)
      }
      

      In the above example, we have a Cook and a Bob class. The Cook class has a protected property called position. The position property is accessible to Cook but it’s also accessible to Bob because Bob is a child class of Cook. In the above example, Bob has a printPosition() function that uses the position property.

      Private

      Private access is the most restrictive. When a member is marked as private, it is only accessible to the class. In the above code snippet, Cook also has a name property, but the name property is not accessible to Bob because it is private.

      Putting it all together

      Below is an example program that shows all of the possible access modifiers in question.

      package ch1.accessmodifiers
      
      /**
       * When no access modifier is used, public is used by default.
       * This printBurgerOfTheDay function is visible to the entire program
       */
      fun printBurgerOfTheDay(burgerName : String = "Never been Feta")
              = println(burgerName)
      
      /**
       * This extension function is marked private. It can't even be used in this module
       */
      private fun String.makeBurgerOfTheDay(burgerName : String) : String = burgerName
      
      /**
       * This variable is marked as internal. It's visible throughout the module,
       * but it can't be accessed outside of the module
       */
      internal var burgerName = "Mission A-Corn-Plished Burger"
      
      /**
       * The position property on Cook is marked as protected. It is only accessible
       * to the Cook class an it's child classes. The name property is private and may
       * only be used by Cook.
       */
      open class Cook {
          protected val position = "Cook"
          private val name = "No Name"
      
          override fun toString(): String {
              return position + ", " + name
          }
      }
      
      class Bob : Cook() {
      
          fun printPosition() = println(position)
      }
      
      fun main(args : Array<String>){
          //Using the public function printBurgerOfTheDay
          printBurgerOfTheDay()
      
          //Printing the internal burgerName variable
          printBurgerOfTheDay(burgerName)
      
          //Create an instance of Cook
          val cook = Cook()
          println(cook)
      
          //Create an instance of Bob
          val bob = Bob()
          bob.printPosition()
      
          //try and use the String extension function
          //DOESN'T COMPILE because makeBurgerOfTheDay is private
          //burgerName = String.makeBurgerOfTheDay("Rest in Peas Burger")
      }
      

      Here is the output when run

      Never been Feta
      Mission A-Corn-Plished Burger
      Cook, No Name
      Cook
      

      References

      https://kotlinlang.org/docs/reference/visibility-modifiers.html

Kotlin Constructors

Many OOP languages have special code that is used to initialize a class to a valid state. Such code is referred to as a constructor method. The constructor runs when an instance of a class is created.

Like all methods, constructors can have zero or more parameters. Let’s consider the constructor found in the ArrayList class found in java.util.collections.

ArrayList<?> list = new ArrayList<>(); //Default constructor
ArrayList<?> list2 = new ArrayList<>(list); //Secondary constructor

The above Java code snippet demonstrates multiple constructor. The first constructor is called the default constructor and accepts no arguments. It creates an empty ArrayList object. The other constructor takes an existing collection object and automatically adds all objects contained in the collection passed into the constructor into the new list. Let’s walk through the various forms of constructors we can define in Kotlin.

Default Constructors

When no constructor is specified, Kotlin will supply an empty no argument constructor.

class ChalkBoard {
    val message = "New Baconings"
}

val chalkBoard = ChalkBoard()
println(chalkBoard.message)

The ChalkBoard class has no constructor. When we create a ChalkBoard object, we just use the () for the default constructor.

Constructor with Required Parameters

We can also define constructors that force us to use valid data.

class Bob(val position : String)

val bob = Bob("Cook")
println(bob.position)

Notice how the Bob class has a position parameter of type String. When we create an instance of Bob, we have to supply a String to the constructor. There is no default constructor for Bob. Since we used the val keyword in front of the position argument, Kotlin created a position property on Bob. This lets use print Bob’s position in the println statement.

Constructor with Initialization Block

We aren’t limited to just setting properties in a constructor. Kotlin also lets us create an initialization block that runs immediately after our properties have been set.

class Linda(val position : String){
    //This is the initialization block that runs when an instance
    //of this class is created
    init {
        check(position.isNotBlank(), { "Linda needs a job!" })
        println("Linda was created with $position")
    }
}

val linda = Linda("Cook's wife")
println(linda.position)

The above code still set’s Linda’s position property, but when the constructor runs, it will also print “Lnda was created with [specified position]” because of the init block found immedialy after the constructor as long was position is not an empty String. If position is blank, the init block would throw an exception with the message “Linda needs a job!”. Init blocks can do other things besides validation, but validation is certainly a common use case of the init block.

Property Initializers

Kotlin also provides property initialization blocks. Let’s consider Gene.

class Gene(position : String){
    //This is the property initialization section
    val beefSquash = position.toUpperCase()
}

val gene = Gene("Beefsquash")
println(gene.beefSquash)

Gene has a constructor that takes a position, but notice that there is no val or var keyword prior to position. Since the var/val keyword is missing, Kotlin does not generate a position property for Gene. On the first line of the class body, we have val beefSquash = position.toUpperCase(). The code creates a beefSquash property on Gene and sets it to the upper case value of position. This lets us create properties outside of the constructor and initialize them to the arguments found in the constructor.

Access modifiers on Constructors

A common OOP pattern is to create private constructors and use factory or builder objects to create an instance of a class. Our Tina class shows a stripped down example of the factory pattern.

class Tina private constructor(val position : String){

    //We have to use a companion object to make getInstance() visible
    companion object {
        //We can only use the constructor from within Tina because it's private
        fun getInstance(position : String) = Tina(position)
    }
}

//Can't do this because Tina's constructor is private
//val tina = Tina("Cook");

//We can do this
val tina = Tina.getInstance("Itchy Grill Cook")
println(tina.position)

Whenever we need to add access modifiers or annotations to a constructor, the constructor keyword is required. In this case, we have private constructor in front of the parameters of Tina’s constructor. Only code found within the Tina class may use the constructor because private restricts the visibility of the constructor. Tina objects can only be created by invoking the getInstance() method which creates and returns a Tina object.

Multiple Constructors

Koltin doesn’t limit us to using only constructor. We are actually free to have as many constructors as we need.

class Louise (val position: String){

    //Calling this(...) after the colon but before the { will invoke the
    //first constructor
    constructor(position: String, age : Int): this(position){
        println("Inside secondary constructor with $position and $age")
    }
}
//Call the single argument constructor
val lousieSingle = Louise("Bunny Ears")

//Call the secondary constructor
val louise = Louise("Bunny Ears", 10)
println(louise.position)

Louise has a regular constructor, but inside of the class body, we can define additional constructors by using the constructor keyword and then specifying our arguments. We are free to reuse code between our constructors by using the this keyword followed by the parameters of the constructor we wish to use. Kotlin will use function overloading to resolve to the correct constructor (or issue a compiler error if one can’t be found). In the example, we see two different ways to create a Louise object. We can use her primary constructor, or her secondary constructor, which also print “Inside secondary constructor with $position and $age” to the console.

Optional Arguments

The final constructor technique involves using optional arguments to specify a default value if a parameter is ommitted. Let’s take a look at Teddy.

class Teddy (val position : String = "customer", val favoriteFood : String = "burger")

val teddyDefault = Teddy()
println(teddyDefault.position + ", " + teddyDefault.favoriteFood)

val teddyArguments = Teddy(position = "Best Customer", favoriteFood = "Burger of the Day")
println(teddyArguments.position + ", " + teddyArguments.favoriteFood)

val teddyFood = Teddy(favoriteFood = "Burger of the day")
println(teddyFood.position + ", " + teddyArguments.favoriteFood)

val teddyPosition = Teddy(position = "Best Customer")
println(teddyPosition.position + ", " + teddyArguments.favoriteFood)

We actually have four constructors for Teddy. The first one is a no argument constructor that initializes Teddy’s poistion to customer and his favoriteFood to burger. The other constructor let’s us specify Teddy’s posistion and his favoriteFood. The third constructor let’s use specify Teddy’s favoriteFood and use his default position. Finally we can use the constructor that specifies Teddy’s position, but use the default for favorite foods.

Putting it all Together

package ch1.constructors

class ChalkBoard {
    val message = "New Baconings"
}

/**
 * Kotlin class with an empty constructor
 */
class Bob(val position : String)

/**
 * Kotlin class with initialization block
 */
class Linda(val position : String){
    //This is the initialization block that runs when an instance
    //of this class is created
    init {
        check(position.isNotBlank(), { "Linda needs a job!" })
        println("Linda was created with $position")
    }
}

/**
 * Kotlin class with a property initializer
 */
class Gene(position : String){
    //This is the property initialization section
    val beefSquash = position.toUpperCase()
}

/**
 * Kotlin class with private constructor
 */
class Tina private constructor(val position : String){

    //We have to use a companion object to make getInstance() visible
    companion object {
        //We can only use the constructor from within Tina because it's private
        fun getInstance(position : String) = Tina(position)
    }
}

/**
 * Kotlin class with multiple constructors
 */
class Louise (val position: String){

    //Calling this(...) after the colon but before the { will invoke the
    //first constructor
    constructor(position: String, age : Int): this(position){
        println("Inside secondary constructor with $position and $age")
    }
}

/**
 * Kotlin class that has a constructor with optional arguments. We can use one,
 * both, or none of the arguments when we create an instance of Teddy
 */
class Teddy (val position : String = "customer", val favoriteFood : String = "burger")

fun main(args : Array<String>){
    val chalkBoard = ChalkBoard()
    println(chalkBoard.message)

    val bob = Bob("Cook")
    println(bob.position)

    val linda = Linda("Cook's wife")
    println(linda.position)

    val gene = Gene("Beefsquash")
    println(gene.beefSquash)

    val tina = Tina.getInstance("Itchy Grill Cook")
    println(tina.position)

    val louise = Louise("Bunny Ears", 10)
    println(louise.position)

    val teddyDefault = Teddy()
    println(teddyDefault.position + ", " + teddyDefault.favoriteFood)

    val teddyArguments = Teddy(position = "Best Customer", favoriteFood = "Burger of the Day")
    println(teddyArguments.position + ", " + teddyArguments.favoriteFood)

    val teddyFood = Teddy(favoriteFood = "Burger of the day")
    println(teddyFood.position + ", " + teddyArguments.favoriteFood)

    val teddyPosition = Teddy(position = "Best Customer")
    println(teddyPosition.position + ", " + teddyArguments.favoriteFood)

}

Here’s the output

New Baconings
Cook
Linda was created with Cook's wife
Cook's wife
BEEFSQUASH
Itchy Grill Cook
Inside secondary constructor with Bunny Ears and 10
Bunny Ears
customer, burger
Best Customer, Burger of the Day
customer, Burger of the Day
Best Customer, Burger of the Day

References

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