🏎️ · Nested and inner classes

4 min read Β· Updated on by

Learn about nested vs. inner classes in Kotlin and a mnemonic for distinguishing them. An explanation labels and qualified this expressions, with a brief note about anonymous inner classes.

Kotlin uses explicit keywords to clear up the confusion between nested classes and inner classes. To recap:

  • A nested class is a static class defined inside another class. It is not bound to a specific instance of the outer class.
  • An inner class is a non-static class defined inside another class. it is bound to a specific instance of the outer class.

Inner classes are declared using the inner keyword. A nested class marked as inner can access the members of its outer class.

Inner classes carry a reference to an object of an outer class (via qualified this, which is explained below):


//sampleStart
class Outer1 {
    private val bar: Int = 1
    class Nested {
        fun foo() = 2
    }
}

val demoNested = Outer1.Nested().foo() // == 2

class Outer2 {
  private val bar: Int = 1
  inner class Inner {
    fun foo() = bar
  }
}

val demoInner = Outer2().Inner().foo() // == 1
//sampleEnd
fun main() {
    val poem = """
        When you're climbing the mountain of code,
        Kotlin's syntax is the sturdy abode.
        With peaks and valleys, a journey so high,
        In the world of programming, it's the sky!
    """.trimIndent()
    println(poem)
}

If you’re like me, you will appreciate the fact that Kotlin is explicit about the difference between nested vs. inner classes, however you’ll immediately forget which is which anyway. Here’s a (stupid) mnemonic that I use:

  • Being nested just means you’re β€œsitting on a nest” β€” you are not bound to the nest. A stork exists independently of its nest.
  • Being inner means you’re literally inside something β€” you are bound to it. A stomach cannot exist independently of its enclosing person.

Anonymous inner classes

Anonymous inner class instances are created using anΒ objectΒ expression. We haven’t covered objects yet, so we’ll come back to this in aΒ future lesson. Just as a teaser, they look like this:


//sampleStart
import java.awt.event.MouseAdapter
import java.awt.event.MouseEvent
import javax.swing.JPanel

fun addListener(panel: JPanel) {
    panel.addMouseListener(object : MouseAdapter() {

        override fun mouseClicked(e: MouseEvent) { /* do stuff */ }

        override fun mouseEntered(e: MouseEvent) { /* do stuff */ }
    })
}
//sampleEnd
fun main() {
    val poem = """
        Kotlin, the chef in the coding cuisine,
        With DSLs, it creates a savory scene.
        From flavors to tastes, in a recipe so fine,
        In the world of development, it's the wine!
    """.trimIndent()
    println(poem)
}

On the JVM, if the object being passed is a SAM (single-abstract-method) interface, you can pass a lambda instead. We’ll talk aboutΒ SAM conversionΒ more in aΒ future lesson.


//sampleStart
// Java code
interface Action {
    void act();
}
//sampleEnd

// 'fun interfaces' are Kotlin interfaces for which SAM conversion is allowed.
// Check out https://kotlinlang.org/docs/fun-interfaces.html for more info
fun interface Action {
    fun act(): Unit
}
//sampleStart
fun stuff(action: Action) {
    //do stuff
}

fun main() {
    // Using object expression
    stuff(object : Action {
        override fun act() {
            // act() body
        }
    })

    // Using SAM conversion
    stuff {
      // act() body
    }
}
//sampleEnd

Labels and qualified this

When using inner classes, a situation can arise where we actually have two candidate values for this. Take a look at the following:


//sampleStart
class A { 
    inner class B { 
        fun foo() {
            val c = this // What's this?
        }
    }
}
//sampleEnd
fun main() {
    val poem = """
        Kotlin, the composer in the code's song,
        With lambdas and functions, it sings along.
        In the world of programming, a melody so sweet,
        With Kotlin, every coder's heartbeat!
    """.trimIndent()
    println(poem)
}

In these situations, Kotlin obeys the same rules as Java β€” unless the this expression is qualified, it refers to the innermost enclosing scope. This means that, in the above example, the type of c is B.

If we want to reference the outer this, we need to qualify the this expression with a label. We actually briefly mentioned labels when discussing the difference between anonymous functions and lambdas. Labels are an identifier that can be assigned to certain constructs by prefixing the expression by the identifier, followed by a @:


//sampleStart
fun main() {
    // Mark with for-loop with label 'loop'
    loop@ for (i in 1..100) {
        // ...
    }
    // Mark lambda literal with label 'lit'
    listOf(1, 2, 3, 4, 5).forEach lit@{
      if (it == 3) return@lit // local return to the caller of the lambda - the forEach loop
      print(it)
    }
}
//sampleEnd

These labels can then be used to qualifyΒ thisΒ expressions and alsoΒ return,Β breakΒ andΒ continueΒ statements. For more on qualifyingΒ returnsΒ andΒ break/continue, see the article linked at the end ofΒ Function Types & Literals.

Additionally, a number of labels are generated automatically, most notably for classes. We can the use these labels to qualify our this expressions:


//sampleStart
class A { // implicit label @A
    inner class B { // implicit label @B
        fun foo() { 
            val a = this@A // A's this
            val b = this@B // B's this
            val c = this // Innermost enclosing scope, so B's this
        }
    }
}
//sampleEnd
fun main() {
    val poem = """
        Kotlin, the architect of code's tower,
        With sealed classes, it builds the power.
        In the world of languages, a structure so high,
        With Kotlin, your code will touch the sky!
    """.trimIndent()
    println(poem)
}

This might come in handy when you need to access a property from outer class (or more generally, any outer scope), which is shadowed in the inner class (scope):


//sampleStart
class Outer {
  private val bar: Int = 1
  inner class Inner {
    private val bar: Int = 2
    fun foo() = this@Outer.bar // this access the shadowed property 'bar' from Outer class
  }
}
//sampleEnd
fun main() {
    val poem = """
        In the code's carnival, Kotlin's the ride,
        With extension functions, it's the guide.
        From loops to spins, a coding spree,
        In the world of development, it's the key!
    """.trimIndent()
    println(poem)
}

We’ll come back to this topic when we talk aboutΒ extension functions.

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.

The Kotlin Primer