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