В Kotlin можно объявить один класс внутри другого. Как правило это полезно, когда нужно расширить функционал внешнего класса: будет чёткое разделение прямого предназначения класса от новой функциональности, при этом код будет рядом с местом использования. Подобные классы разделяются на вложенные (nested) и внутренние (inner).
Вложенные классы (Nested classes)
Для определения вложенного класса достаточно заключить его в фигурные скобки внутри внешнего класса. Не нужно указывать какие-либо ключевые слова, потому что вложенные классы - это значение по умолчанию.
1
2
3
4
5
6
7
class Outer { // внешний класс
...
class Nested { // вложенный класс
...
}
}
У вложенных классов есть два ограничения:
- Экземпляр внешнего класса не может напрямую обращаться к вложенному классу. Вложенный класс должен быть инициализирован внутри внешнего класса.
1
2
3
4
5
6
7
8
9
10
11
class Outer {
...
class Nested {
...
}
}
---
val nested = Outer.Nested() // скомпилируется
val nested = Outer().Nested() // не скомпилируется
- Вложенный класс не имеет доступа к экземпляру внешнего класса, поэтому не сможет обращаться к его компонентам.
1
2
3
4
5
6
7
class Outer {
val x = 10
class Nested {
fun getX() = "x = $x" // не скомпилируется
}
}
Внутренние классы (Inner classes)
Для определения внутреннего класса нужно не только заключить его в фигурные скобки внутри внешнего класса, но и отметить ключевым словом inner
.
1
2
3
4
5
6
7
class Outer { // внешний класс
...
inner class Inner { // внутренний класс
...
}
}
По сути внутренний класс - это вложенный класс, который может обращаться к компонентам внешнего класса. В этом и есть их ключевое отличие.
1
2
3
4
5
6
7
class Outer {
val x = 10
inner class Inner {
fun getX() = "x = $x" // скомпилируется
}
}
Что если имена переменных и функций внутреннего класса совпадают с именами внешнего класса? В этом случае при обращении к компонентам внешнего класса используется ключевое слово this
.
1
2
3
4
5
6
7
8
class Outer {
val x = 10
inner class Inner {
val x = 20
fun getX() = "Outer x = ${this@Outer.x}, Inner x = $x"
}
}
Второе ключевое отличие заключается в том, как происходит обращение к внутреннему классу. Экземпляр внутреннего класса всегда хранит ссылку на экземпляр внешнего класса. Поэтому для обращения к нему (откуда угодно) нужно сначала создать экземпляр внешнего класса, а затем на его основе экземпляр внутреннего класса.
1
2
val inner = Outer().Inner() // скомпилируется
val inner = Outer.Inner() // не скомпилируется
Сравнения с Java
Сами по себе понятия вложенных и внутренних классов в Java ничем не отличаются от Kotlin. Отличается только то, как они объявляются. Поэтому я думаю, что таблички в качестве сравнения будет достаточно.
Класс, объявленный внутри другого класса | Java | Kotlin |
---|---|---|
Вложенный класс | static class B | class B |
Внутренний класс | class B | inner class B |
Полезные ссылки
Nested and Inner Classes - официальная документация.
Вложенные классы - перевод на русский.