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:
- Static nested classes (simply called nested classes in Kotlin)
- 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:
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 ofOuter
- 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:
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:
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:
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:
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:
Email: [email protected]
Email: [email protected]
Email: [email protected]
Using Nested Classes with Interfaces
Nested classes are also commonly used with interfaces for defining related constants or callbacks:
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
- Favor nested classes over inner classes unless you need access to the outer class
- Keep nested and inner classes small and focused on a single responsibility
- Use nested classes to increase encapsulation when classes are tightly related
- 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:
- Create a
Library
class with a nestedBook
class and an innerCatalog
class - Implement a
Counter
class with an inner class that can increment/decrement the counter - Design a
ShoppingCart
class with a nestedItem
class and an innerCheckout
class - 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! :)