Skip to main content

Kotlin Inner Classes

Introduction

In Kotlin, classes can be defined within other classes to create a hierarchical structure. These classes are known as inner classes. Inner classes are a powerful feature of Kotlin that allow you to logically group classes that are used only in one place, increase encapsulation, and create more readable and maintainable code.

Kotlin distinguishes between two types of classes defined within another class:

  1. Nested classes - which don't have access to the outer class's members
  2. Inner classes - which can access the outer class's members

This tutorial focuses specifically on inner classes in Kotlin, how they work, and how they differ from regular nested classes.

Nested vs Inner Classes

Before diving into inner classes, let's quickly understand the difference between nested and inner classes in Kotlin:

kotlin
class OuterClass {
private val outerProperty = "I belong to the outer class"

// This is a nested class (by default)
class NestedClass {
fun accessOuterProperty() {
// Error: Cannot access 'outerProperty'
// println(outerProperty)
}
}

// This is an inner class (note the 'inner' keyword)
inner class InnerClass {
fun accessOuterProperty() {
// Works fine! Inner classes can access outer class properties
println(outerProperty)
}
}
}

The key difference is that:

  • Nested classes (without inner keyword) cannot access outer class members
  • Inner classes (with inner keyword) can access outer class members

Creating and Using Inner Classes

To define an inner class in Kotlin, you need to use the inner keyword before the class keyword:

kotlin
class OuterClass {
private val message = "Hello from the outer class"

inner class InnerClass {
fun displayOuterMessage() {
println(message) // Can access outer class property
}
}
}

To instantiate an inner class, you first need an instance of the outer class:

kotlin
fun main() {
val outer = OuterClass()
val inner = outer.InnerClass()
inner.displayOuterMessage() // Output: Hello from the outer class
}

Accessing the Outer Class Instance

Within an inner class, you can access members of the outer class. To explicitly refer to the outer class instance, you can use this@OuterClass syntax:

kotlin
class OuterClass {
private val value = 10

inner class InnerClass {
private val value = 20

fun printValues() {
println("Inner class value: $value")
println("Outer class value: ${this@OuterClass.value}")
}
}
}

fun main() {
val outer = OuterClass()
val inner = outer.InnerClass()
inner.printValues()
}

Output:

Inner class value: 20
Outer class value: 10

Real-World Example: UI Components

Inner classes are often used in Android development and other UI frameworks to implement event handlers or callbacks. Here's a simplified example:

kotlin
class Button(val text: String) {
private var clickListener: OnClickListener? = null

// Interface for the click listener
interface OnClickListener {
fun onClick()
}

fun setOnClickListener(listener: OnClickListener) {
this.clickListener = listener
}

fun performClick() {
clickListener?.onClick()
}
}

class Screen {
private val message = "Button clicked from Screen class!"

fun setupUI() {
val button = Button("Click Me")

// Using an inner class to implement the interface
button.setOnClickListener(object : Button.OnClickListener {
override fun onClick() {
// Can access the outer class property
println(message)
}
})

// Simulate button click
button.performClick()
}
}

fun main() {
Screen().setupUI()
}

Output:

Button clicked from Screen class!

Real-World Example: Iterator Implementation

Inner classes are useful for implementing iterators, as they need access to the outer collection's internals:

kotlin
class SimpleCollection<T>(private vararg val items: T) {

// Function that returns an iterator
fun iterator(): SimpleIterator {
return SimpleIterator()
}

// Inner class that can access the items in the outer class
inner class SimpleIterator {
private var index = 0

fun hasNext(): Boolean = index < items.size

fun next(): T {
if (!hasNext()) throw NoSuchElementException()
return items[index++]
}
}
}

fun main() {
val collection = SimpleCollection("apple", "banana", "cherry")
val iterator = collection.iterator()

while (iterator.hasNext()) {
println(iterator.next())
}
}

Output:

apple
banana
cherry

When to Use Inner Classes

Inner classes are useful in several scenarios:

  1. When a class is only useful in the context of another class
  2. When the inner class needs access to the properties and methods of the outer class
  3. When you want to logically group classes that are used together
  4. When implementing callbacks or listeners
  5. When implementing data structures with iterators or other helper components

However, be careful not to overuse inner classes as they can increase complexity and create tight coupling between classes.

Limitations and Considerations

When working with inner classes, keep these points in mind:

  1. Inner classes hold a reference to the outer class, which can prevent garbage collection of the outer class if the inner class instance survives longer
  2. Creating many instances of inner classes can be memory-intensive
  3. Inner classes can make code harder to test in isolation
  4. Too many levels of nesting can make code difficult to understand

Summary

Inner classes in Kotlin are a powerful feature that allows one class to access the members of its enclosing class. Unlike regular nested classes, inner classes require the use of the inner keyword and need an instance of the outer class to be instantiated.

Key points to remember:

  • Use the inner keyword to create an inner class
  • Inner classes can access properties and methods of the outer class
  • To instantiate an inner class, you need an instance of the outer class
  • Use this@OuterClass to refer to the outer class instance explicitly
  • Inner classes are useful for implementing callbacks, iterators, and other helper components

Exercises

  1. Create a Library class with books as private members, and an inner class BookLender that can access and lend these books
  2. Implement a simple linked list data structure with an inner class for the node implementation
  3. Create a UI component with an inner class for handling different events
  4. Convert a regular nested class to an inner class and explore how it changes the access to outer class members
  5. Implement a simple state machine using an outer class for the machine and inner classes for different states

Additional Resources



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)