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()
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s