Skip to main content

Kotlin Nested Classes

Introduction

In Kotlin, just like many other object-oriented programming languages, you can define a class inside another class. These are known as nested classes. Nested classes help you organize code that belongs together, increase encapsulation, and create cleaner, more readable code structures.

Kotlin offers two types of nested classes:

  1. Static nested classes (simply called nested classes in Kotlin)
  2. Inner classes (which have access to members of their outer class)

Understanding the difference between these two types and knowing when to use each is essential for writing effective Kotlin code.

Nested Classes vs. Inner Classes

Nested Classes

In Kotlin, a nested class is by default static, meaning it doesn't have access to the outer class instance. This is different from Java where you need to explicitly use the static keyword.

Let's look at a basic example of a nested class:

kotlin
class Outer {
private val outerProperty = "I'm in the outer class"

class Nested {
fun accessOuterClass() {
// Cannot access outerProperty directly
println("This is a nested class")
}
}
}

fun main() {
// Creating an instance of the nested class
val nested = Outer.Nested()
nested.accessOuterClass()
}

Output:

This is a nested class

Notice that:

  • To create an instance of Nested, you don't need an instance of Outer
  • The nested class cannot access members of the outer class directly

Inner Classes

If you want a nested class to be able to access the members of its outer class, you need to use the inner keyword to create an inner class:

kotlin
class Outer {
private val outerProperty = "I'm in the outer class"

inner class Inner {
fun accessOuterClass() {
// Can access outerProperty because this is an inner class
println("Accessing outer property: $outerProperty")
}
}
}

fun main() {
// Creating an instance of the inner class requires an instance of the outer class
val inner = Outer().Inner()
inner.accessOuterClass()
}

Output:

Accessing outer property: I'm in the outer class

Key differences:

  • Inner classes are created with the inner keyword
  • Inner classes can access properties and methods of the outer class
  • To create an inner class instance, you need an instance of the outer class first

Accessing the Outer Class Reference

In an inner class, you can refer to the outer class instance using the this@OuterClassName syntax:

kotlin
class Outer {
private val name = "Outer"

inner class Inner {
private val name = "Inner"

fun printNames() {
println("Inner name: $name")
println("Outer name: ${this@Outer.name}")
}
}
}

fun main() {
Outer().Inner().printNames()
}

Output:

Inner name: Inner
Outer name: Outer

This is very useful when the inner class has properties or methods with the same names as the outer class.

Practical Applications

Example 1: UI Components with State Handling

Nested and inner classes are often used in UI frameworks to manage component states:

kotlin
class Button(private val text: String) {
private var isEnabled = true

// Configuration is a nested class - doesn't need Button instance
class Configuration {
var fontSize = 14
var fontColor = "black"
var backgroundColor = "white"
}

// State is an inner class - needs access to Button properties
inner class State {
fun toggle() {
isEnabled = !isEnabled
println("Button '$text' is now ${if (isEnabled) "enabled" else "disabled"}")
}

fun isEnabled() = isEnabled
}

fun getState(): State = State()
}

fun main() {
// Using the nested class without a Button instance
val config = Button.Configuration()
config.fontSize = 16
config.fontColor = "blue"

// Using the inner class with a Button instance
val loginButton = Button("Login")
val buttonState = loginButton.getState()

buttonState.toggle() // Disable the button
buttonState.toggle() // Enable the button again
}

Output:

Button 'Login' is now disabled
Button 'Login' is now enabled

Example 2: Data Processing with Iterators

Inner classes are great for implementing iterators:

kotlin
class EmailCollection(private val emails: List<String>) {

fun getIterator(): EmailIterator = EmailIterator()

inner class EmailIterator {
private var index = 0

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

fun next(): String {
if (!hasNext()) throw NoSuchElementException("No more emails")
return emails[index++]
}
}
}

fun main() {
val emailList = EmailCollection(listOf("[email protected]", "[email protected]", "[email protected]"))
val iterator = emailList.getIterator()

while (iterator.hasNext()) {
println("Email: ${iterator.next()}")
}
}

Output:

Using Nested Classes with Interfaces

Nested classes are also commonly used with interfaces for defining related constants or callbacks:

kotlin
interface NetworkCallback {
fun onSuccess(response: Response)
fun onError(error: Error)

// Nested classes for response data types
class Response(val data: String, val statusCode: Int)
class Error(val message: String, val statusCode: Int)
}

class NetworkClient {
fun makeRequest(callback: NetworkCallback) {
// Simulate successful request
callback.onSuccess(NetworkCallback.Response("{'user': 'John'}", 200))
}
}

fun main() {
val client = NetworkClient()

client.makeRequest(object : NetworkCallback {
override fun onSuccess(response: NetworkCallback.Response) {
println("Request succeeded with data: ${response.data}")
}

override fun onError(error: NetworkCallback.Error) {
println("Request failed: ${error.message}")
}
})
}

Output:

Request succeeded with data: {'user': 'John'}

When to Use Nested vs. Inner Classes

Use a nested class when:

  • The class doesn't need access to the outer class properties
  • You want to logically group classes together
  • The class makes sense even without an instance of the outer class

Use an inner class when:

  • The class needs access to properties and methods of the outer class
  • The class only makes sense in the context of the outer class
  • You're implementing specific behavior tied to an instance of the outer class

Best Practices

  1. Favor nested classes over inner classes unless you need access to the outer class
  2. Keep nested and inner classes small and focused on a single responsibility
  3. Use nested classes to increase encapsulation when classes are tightly related
  4. Avoid deeply nested inner classes (inner classes inside inner classes) as they can become difficult to read

Summary

Kotlin provides two types of classes that can be defined within another class:

  • Nested classes (static by default): Cannot access outer class members
  • Inner classes (declared with the inner keyword): Can access outer class members

These classes help organize your code by grouping related functionality together while maintaining appropriate access boundaries. They are particularly useful for implementing complex UI components, data structures with iterators, and callback mechanisms.

Exercises

To practice your understanding of nested and inner classes:

  1. Create a Library class with a nested Book class and an inner Catalog class
  2. Implement a Counter class with an inner class that can increment/decrement the counter
  3. Design a ShoppingCart class with a nested Item class and an inner Checkout class
  4. Create a custom iterator using an inner class to iterate through a custom collection

Additional Resources



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