Kotlin - Nested & Inner Classes
Overview
Kotlin supports nested classes, inner classes, and anonymous classes to help organize code and create focused, encapsulated components. This tutorial covers the differences between these class types, their use cases, and practical implementation patterns.
- Understand nested classes and their access rules
- Learn about inner classes and outer class references
- Master anonymous classes and object expressions
- Apply these concepts in real-world scenarios
- Choose the right class type for different situations
Nested Classes
A nested class is declared inside another class but doesn't hold a reference to the outer class instance. Nested classes are like static nested classes in Java.
Basic Nested Class
class Outer {
private val outerProperty = "Outer"
class Nested {
fun nestedFunction() = "Hello from nested class"
// Cannot access outerProperty directly
}
}
fun main() {
val nested = Outer.Nested()
println(nested.nestedFunction()) // Hello from nested class
}
Nested Classes with Companion Objects
class Calculator {
companion object {
const val PI = 3.14159
class MathConstants {
const val E = 2.71828
const val GOLDEN_RATIO = 1.618
}
}
}
fun main() {
println(Calculator.PI)
println(Calculator.MathConstants.E)
}
Inner Classes
Inner classes are marked with the inner
keyword and hold a reference to the outer class instance, allowing access to outer class members.
Basic Inner Class
class Outer {
private val outerProperty = "Outer Property"
inner class Inner {
fun accessOuter() = "Accessing: $outerProperty"
fun getOuterReference(): Outer = this@Outer
}
}
fun main() {
val outer = Outer()
val inner = outer.Inner() // Note: need outer instance
println(inner.accessOuter()) // Accessing: Outer Property
}
Practical Example: LinkedList Node
class LinkedList {
private var head: Node? = null
inner class Node(val data: T) {
var next: Node? = null
fun addAfter(data: T): Node {
val newNode = Node(data) // Can create Node directly
newNode.next = this.next
this.next = newNode
return newNode
}
fun remove() {
// Access outer class to modify head if needed
if (this == head) {
head = this.next
}
}
}
fun add(data: T): Node {
val newNode = Node(data)
newNode.next = head
head = newNode
return newNode
}
}
Anonymous Classes
Anonymous classes are created using object expressions, implementing interfaces or extending classes on-the-fly.
Anonymous Interface Implementation
interface EventListener {
fun onEvent(message: String)
}
class EventEmitter {
private val listeners = mutableListOf()
fun addListener(listener: EventListener) {
listeners.add(listener)
}
fun emit(message: String) {
listeners.forEach { it.onEvent(message) }
}
}
fun main() {
val emitter = EventEmitter()
// Anonymous class implementing EventListener
emitter.addListener(object : EventListener {
override fun onEvent(message: String) {
println("Received: $message")
}
})
emitter.emit("Hello World")
}
Anonymous Class Extending Abstract Class
abstract class Animal {
abstract fun makeSound()
fun sleep() = println("Sleeping...")
}
fun createAnimal(): Animal {
return object : Animal() {
override fun makeSound() = println("Some animal sound")
}
}
fun main() {
val animal = createAnimal()
animal.makeSound()
animal.sleep()
}
Anonymous Objects with Properties
fun createTemporaryObject() = object {
val name = "Temporary"
val id = 12345
fun display() = println("$name: $id")
}
fun main() {
val temp = createTemporaryObject()
temp.display() // Temporary: 12345
println("Name: ${temp.name}") // Name: Temporary
}
Local Classes
Classes can be declared inside functions (local classes). They have access to local variables and outer class members.
class Outer {
private val outerVal = "outer"
fun createLocalClass(localParam: String) {
val localVal = "local"
class LocalClass {
fun display() {
println("Outer: $outerVal")
println("Local: $localVal")
println("Param: $localParam")
}
}
val local = LocalClass()
local.display()
}
}
fun main() {
Outer().createLocalClass("parameter")
// Output:
// Outer: outer
// Local: local
// Param: parameter
}
Real-World Examples
Builder Pattern with Nested Classes
class HttpRequest private constructor(
val url: String,
val method: String,
val headers: Map,
val body: String?
) {
class Builder {
private var url: String = ""
private var method: String = "GET"
private var headers: MutableMap = mutableMapOf()
private var body: String? = null
fun url(url: String) = apply { this.url = url }
fun method(method: String) = apply { this.method = method }
fun header(key: String, value: String) = apply { headers[key] = value }
fun body(body: String) = apply { this.body = body }
fun build() = HttpRequest(url, method, headers.toMap(), body)
}
}
fun main() {
val request = HttpRequest.Builder()
.url("https://api.example.com/users")
.method("POST")
.header("Content-Type", "application/json")
.body("""{"name": "John", "age": 30}""")
.build()
}
Event System with Inner Classes
class EventSystem {
private val events = mutableMapOf>()
inner class EventHandler(
private val eventType: String,
private val callback: (String) -> Unit
) {
fun handle(data: String) = callback(data)
fun unregister() {
events[eventType]?.remove(this)
}
}
fun on(eventType: String, callback: (String) -> Unit): EventHandler {
val handler = EventHandler(eventType, callback)
events.getOrPut(eventType) { mutableListOf() }.add(handler)
return handler
}
fun emit(eventType: String, data: String) {
events[eventType]?.forEach { it.handle(data) }
}
}
fun main() {
val eventSystem = EventSystem()
val handler = eventSystem.on("message") { data ->
println("Received message: $data")
}
eventSystem.emit("message", "Hello!")
handler.unregister()
}
Best Practices
When to Use Each Type
- Nested Class: When you need a helper class that doesn't require access to outer instance
- Inner Class: When the class is tightly coupled to the outer class and needs access to its members
- Anonymous Class: For one-time implementations of interfaces or abstract classes
- Local Class: When you need a class only within a specific function scope
Performance Considerations
class PerformanceExample {
// Nested class - no outer reference (memory efficient)
class NestedHelper {
fun help() = "Help"
}
// Inner class - holds outer reference (uses more memory)
inner class InnerHelper {
fun help() = "Help from inner"
}
}
// Prefer nested when outer access isn't needed
class PreferNested {
class Utility { // No 'inner' needed
fun utility() = "utility function"
}
}
Common Patterns
State Machine with Nested Classes
class StateMachine {
private var currentState: State = IdleState()
abstract class State {
abstract fun handle(input: String): State
abstract fun getName(): String
}
class IdleState : State() {
override fun handle(input: String): State {
return when (input) {
"start" -> ProcessingState()
else -> this
}
}
override fun getName() = "Idle"
}
class ProcessingState : State() {
override fun handle(input: String): State {
return when (input) {
"complete" -> CompleteState()
"error" -> ErrorState()
else -> this
}
}
override fun getName() = "Processing"
}
class CompleteState : State() {
override fun handle(input: String) = IdleState()
override fun getName() = "Complete"
}
class ErrorState : State() {
override fun handle(input: String) = IdleState()
override fun getName() = "Error"
}
fun processInput(input: String) {
println("Current state: ${currentState.getName()}")
currentState = currentState.handle(input)
println("New state: ${currentState.getName()}")
}
}
Key Takeaways
- Nested classes don't hold outer class references and cannot access private outer members
- Inner classes hold outer references and can access all outer class members
- Anonymous classes are perfect for one-time interface implementations
- Local classes have access to local variables and outer class members
- Choose the appropriate type based on coupling and access requirements
- Consider memory implications when choosing between nested and inner classes
Practice Exercises
- Create a `BinaryTree` class with an inner `Node` class that can access tree methods
- Implement a nested `Builder` class for a `User` data class
- Use anonymous classes to create different sorting strategies for a list
- Build a simple calculator with nested operation classes
Quiz
- What's the main difference between nested and inner classes?
- Can a nested class access private members of its outer class?
- When would you prefer an anonymous class over a named class?
Show Answers
- Nested classes don't hold a reference to the outer instance; inner classes do and can access outer members.
- No, nested classes cannot access private members of the outer class because they don't have an outer instance reference.
- Use anonymous classes for one-time implementations, event handlers, or when you need a quick implementation without creating a separate class file.