Вложенные классы
Виды
Класс, объявленный внутри другого типа, называется nested class. Есть четыре практических формы:
- статический вложенный класс;
- нестатический inner class;
- локальный класс внутри блока/метода;
- анонимный класс.
Вложенность уместна, когда вспомогательный тип тесно связан с внешним и не является самостоятельной частью API.
Static nested class
Статический вложенный класс не имеет неявной ссылки на экземпляр внешнего:
final class Request {
private final String path;
private Request(String path) {
this.path = path;
}
static final class Builder {
private String path;
Builder path(String value) {
path = value;
return this;
}
Request build() {
return new Request(path);
}
}
}
Request request = new Request.Builder()
.path("/health")
.build();Создание не требует Request-объекта. Вложенный класс может обращаться к private-членам внешнего класса, но для нестатического состояния ему всё равно нужен экземпляр.
Inner class
Нестатический inner class связан с конкретным внешним объектом:
final class Bag<E> {
private final List<E> values = new ArrayList<>();
final class Cursor {
private int index;
boolean hasNext() {
return index < values.size();
}
E next() {
return values.get(index++);
}
}
}
Bag<String> bag = new Bag<>();
Bag<String>.Cursor cursor = bag.new Cursor();Неявная ссылка позволяет читать поля Bag, но может случайно удерживать внешний объект в памяти. Если связь не нужна, выбирают static.
Локальные и анонимные классы
Локальный класс виден только внутри блока. Анонимный создаёт единственный безымянный подтип:
Comparator<String> byLength = new Comparator<>() {
@Override
public int compare(String left, String right) {
return Integer.compare(left.length(), right.length());
}
};Для функционального интерфейса это обычно короче записать лямбдой. Анонимный класс остаётся полезен, когда нужно переопределить несколько методов или иметь собственное состояние.
Локальные переменные, захваченные локальным/анонимным классом или лямбдой, должны быть final или effectively final.
Доступ и ограничения
Top-level класс может быть только public или package-private. Nested class может иметь private, protected, package-private или public.
Статический вложенный класс не может использовать параметр типа внешнего класса без собственного параметра:
class Box<T> {
static class Entry<U> {
U value;
}
}Ловушки
- Называть любой nested class «inner»: термин inner относится только к нестатической форме.
- Делать класс нестатическим, хотя ему не нужен внешний объект.
- Не замечать, что inner class удерживает внешний объект.
- Использовать анонимный класс вместо простой лямбды.
- Захватывать большой mutable-контекст в долгоживущем callback.
- Ожидать доступ к
Tвнешнего класса из static nested class.
Вопросы на собеседовании
- Чем static nested class отличается от inner class?
- Как создать экземпляр нестатического inner class?
- Почему inner class может удерживать внешний объект в памяти?
- Какие локальные переменные можно захватывать?
- Чем анонимный класс отличается от лямбды?
- Какие модификаторы доступа допустимы для top-level и nested-классов?
Связанные заметки
Объекты Java · Классы обертки Java · Лямбда выражения · Функциональные интерфейсы · Модификаторы доступа