# Inner Classes

Inner classes are like nested classes, but an object of an inner class maintains a reference to the outer class.

An inner class has an implicit link to the outer class. In the following example, Hotel is like Airport from [Nested Classes](javascript:void(0)), but it uses inner classes. Note that reception is part of Hotel, but callReception(), which is a member of the nested class Room, accesses reception without qualification:

// InnerClasses/Hotel.kt
package innerclasses
import atomictest.eq

class Hotel(private val reception: String) {
  open inner class Room(val id: Int = 0) {
    // Uses 'reception' from outer class:
    fun callReception() =
      "Room $id Calling $reception"
  }
  private inner class Closet : Room()
  fun closet(): Room = Closet()
}

fun main() {
  val nycHotel = Hotel("311")
  // You need an outer object to
  // create an instance of the inner class:
  val room = nycHotel.Room(319)
  room.callReception() eq
    "Room 319 Calling 311"
  val sfHotel = Hotel("0")
  val closet = sfHotel.closet()
  closet.callReception() eq "Room 0 Calling 0"
}

Because Closet inherits the inner class Room, Closet must also be an inner class. Nested classes cannot inherit from inner classes.

Closet is private, so it is only visible within the scope of Hotel.

An inner object keeps a reference to its associated outer object. Thus, when creating an inner object you must first have an outer object. You cannot create a Room object without a Hotel object, as you see with nycHotel.Room().

inner data classes are not allowed.

# Qualified this

One of the benefits of classes is the this reference. You don’t have to explicitly say “the current object” when you access a property or member function.

With a simple class, the meaning of this is obvious, but with an inner class, this could refer to either the inner object or an outer object. To resolve this issue, Kotlin provides the qualified this syntax: this followed by @ and the name of the target class.

Consider three levels of classes: an outer class Fruit containing an inner class Seed, which itself contains an inner class DNA:

// InnerClasses/QualifiedThis.kt
package innerclasses
import atomictest.eq
import typechecking.name

class Fruit { // Implicit label @Fruit
  fun changeColor(color: String) =
    "Fruit $color"
  fun absorbWater(amount: Int) {}
  inner class Seed { // Implicit label @Seed
    fun changeColor(color: String) =
      "Seed $color"
    fun germinate() {}
    fun whichThis() {
      // Defaults to the current class:
      this.name eq "Seed"
      // To clarify, you can redundantly
      // qualify the default this:
      this@Seed.name  eq "Seed"
      // Must explicitly access Fruit:
      this@Fruit.name  eq "Fruit"
      // Cannot access a further-inner class:
      // this@DNA.name
    }
    inner class DNA { // Implicit label @DNA
      fun changeColor(color: String) {
        // changeColor(color) // Recursive
        this@Seed.changeColor(color)
        this@Fruit.changeColor(color)
      }
      fun plant() {
        // Call outer-class functions
        // Without qualification:
        germinate()
        absorbWater(10)
      }
      // Extension function:
      fun Int.grow() { // Implicit label @grow
        // Default is the Int.grow() receiver:
        this.name eq "Int"
        // Redundant qualification:
        this@grow.name  eq "Int"
        // You can still access everything:
        this@DNA.name  eq "DNA"
        this@Seed.name  eq "Seed"
        this@Fruit.name  eq "Fruit"
      }
      // Extension functions on outer classes:
      fun Seed.plant() {}
      fun Fruit.plant() {}
      fun whichThis() {
        // Defaults to the current class:
        this.name eq "DNA"
        // Redundant qualification:
        this@DNA.name  eq "DNA"
        // The others must be explicit:
        this@Seed.name  eq "Seed"
        this@Fruit.name  eq "Fruit"
      }
    }
  }
}

// Extension function:
fun Fruit.grow(amount: Int) {
  absorbWater(amount)
  // Calls Fruit's version of changeColor():
  changeColor("Red") eq "Fruit Red"
}

// Inner-class extension function:
fun Fruit.Seed.grow(n: Int) {
  germinate()
  // Calls Seed's version of changeColor():
  changeColor("Green") eq "Seed Green"
}

// Inner-class extension function:
fun Fruit.Seed.DNA.grow(n: Int) = n.grow()

fun main() {
  val fruit = Fruit()
  fruit.grow(4)
  val seed = fruit.Seed()
  seed.grow(9)
  seed.whichThis()
  val dna = seed.DNA()
  dna.plant()
  dna.grow(5)
  dna.whichThis()
  dna.changeColor("Purple")
}

Fruit, Seed and DNA all have functions called changeColor(), but there’s no overriding—this is not an inheritance relationship. Because they have the same name and signature, the only way to distinguish them is with a qualified this, as you see in DNA’s changeColor(). Inside plant(), functions in either of the two outer classes can be called without qualification if there are no name collisions.

Even though it’s an extension function, grow() can still access all the objects in the outer class. grow() can be called anywhere the Fruit.Seed.DNA implicit receiver is available; for example, inside an extension function for DNA.

# Inner Class Inheritance

An inner class can inherit another inner class from a different outer class. Here, Yolk in BigEgg is derived from Yolk in Egg:

// InnerClasses/InnerClassInheritance.kt
package innerclasses
import atomictest.*

open class Egg {
  private var yolk = Yolk()
  open inner class Yolk {
    init { trace("Egg.Yolk()") }
    open fun f() { trace("Egg.Yolk.f()") }
  }
  init { trace("New Egg()") }
  fun insertYolk(y: Yolk) { yolk = y }
  fun g() { yolk.f() }
}

class BigEgg : Egg() {
  inner class Yolk : Egg.Yolk() {
    init { trace("BigEgg.Yolk()") }
    override fun f() {
      trace("BigEgg.Yolk.f()")
    }
  }
  init { insertYolk(Yolk()) }
}

fun main() {
  BigEgg().g()
  trace eq """
    Egg.Yolk()
    New Egg()
    Egg.Yolk()
    BigEgg.Yolk()
    BigEgg.Yolk.f()
  """
}

BigEgg.Yolk explicitly names Egg.Yolk as its base class, and overrides its f() member function. The function insertYolk() allows BigEgg to upcast one of its own Yolk objects into the yolk reference in Egg, so when g() calls yolk.f(), the overridden version of f() is used. The second call to Egg.Yolk() is the base-class constructor call of the BigEgg.Yolk constructor. You can see that the overridden version of f() is used when g() is called.

As a review of object construction, study the trace output until it makes sense.

# Local & Anonymous Inner Classes

Classes defined inside member functions are called local inner classes. These can also be created anonymously, using an object expression, or using a [SAM conversion](javascript:void(0)). In all cases, the inner keyword is not used, but is implied:

// InnerClasses/LocalInnerClasses.kt
package innerclasses
import atomictest.eq

fun interface Pet {
  fun speak(): String
}

object CreatePet {
  fun home() = " home!"
  fun dog(): Pet {
    val say = "Bark"
    // Local inner class:
    class Dog : Pet {
      override fun speak() = say + home()
    }
    return Dog()
  }
  fun cat(): Pet {
    val emit = "Meow"
    // Anonymous inner class:
    return object: Pet {
      override fun speak() = emit + home()
    }
  }
  fun hamster(): Pet {
    val squeak = "Squeak"
    // SAM conversion:
    return Pet { squeak + home() }
  }
}

fun main() {
  CreatePet.dog().speak() eq "Bark home!"
  CreatePet.cat().speak() eq "Meow home!"
  CreatePet.hamster().speak() eq "Squeak home!"
}

A local inner class has access to other elements in the function as well as elements in the outer-class object, thus say, emit, squeak and home() are available within speak().

You can identify an anonymous inner class because it uses an object expression, which you see in cat(). It returns an object of a class inherited from Pet that overrides speak(). Anonymous inner classes are smaller and more straightforward and do not create a named class that will only be used in one place. Even more compact is a [SAM conversion](javascript:void(0)), as seen in hamster().

Because inner classes keep a reference to the outer-class object, local inner classes can access all members of the enclosing class:

// InnerClasses/CounterFactory.kt
package innerclasses
import atomictest.*

fun interface Counter {
  fun next(): Int
}

object CounterFactory {
  private var count = 0
  fun new(name: String): Counter {
    // Local inner class:
    class Local : Counter {
      init { trace("Local()") }
      override fun next(): Int {
        // Access local identifiers:
        trace("$name $count")
        return count++
      }
    }
    return Local()
  }
  fun new2(name: String): Counter {
    // Instance of an anonymous inner class:
    return object: Counter {
      init { trace("Counter()") }
      override fun next(): Int {
        trace("$name $count")
        return count++
      }
    }
  }
  fun new3(name: String): Counter {
    trace("Counter()")
    return Counter { // SAM conversion
      trace("$name $count")
      count++
    }
  }
}

fun main() {
  fun test(counter: Counter) {
    (0..3).forEach { counter.next() }
  }
  test(CounterFactory.new("Local"))
  test(CounterFactory.new2("Anon"))
  test(CounterFactory.new3("SAM"))
  trace eq """
    Local() Local 0 Local 1 Local 2 Local 3
    Counter() Anon 4 Anon 5 Anon 6 Anon 7
    Counter() SAM 8 SAM 9 SAM 10 SAM 11
  """
}

A Counter keeps track of a count and returns the next Int value. new(), new2() and new3() each create a different implementation of the Counter interface. new() returns an instance of a named inner class, new2() returns an instance of an anonymous inner class, and new3() uses a [SAM conversion](javascript:void(0)) to create an anonymous object. All the resulting Counter objects have implicit access to the elements of the outer object, thus they are inner classes and not just nested classes. You can see from the output that count in CounterFactory is shared by all Counter objects.

SAM conversions are limited—for example, they do not support init clauses.

  • -

In Kotlin, files can contain multiple top-level classes and functions. Because of this, there’s rarely a need for local classes, so if you do need them they should be basic and straightforward. For example, it’s reasonable to create a simple data class that’s only used inside a function. If a local class becomes complex, you should probably take it out of the function and make it a regular class.

Exercises and solutions can be found at www.AtomicKotlin.com.