Вложенные классы
Иногда небольшой класс нужен только как часть другого класса: например, описывает его внутреннюю деталь, помогает выполнять одну узкую задачу или имеет смысл только рядом с внешним классом. Вместо отдельного файла такой класс можно объявить внутри другого класса.
Такие классы называют вложенными. Это не отдельный механизм наследования или инкапсуляции, а способ расположить связанные типы ближе друг к другу и точнее ограничить область их использования.
Статический вложенный класс
Если вложенный класс объявлен с static, ему не нужен объект внешнего класса.
Он связан с внешним классом прежде всего логически: имя внешнего класса становится частью его полного имени.
class Order {
private final int id;
Order(int id) {
this.id = id;
}
static class Validator {
boolean isValid(Order order) {
return order.id > 0;
}
}
}
Создать такой объект можно через имя внешнего класса:
Order.Validator validator = new Order.Validator();
boolean valid = validator.isValid(new Order(10));
У Validator нет неявной ссылки на конкретный Order,
поэтому к нестатическому состоянию внешнего класса он обращается только через явно переданный объект.
При этом как вложенный класс он может обращаться даже к private-членам Order.
Внутренний класс
Нестатический вложенный класс называют внутренним классом (inner class). Его объект связан с конкретным объектом внешнего класса и может напрямую использовать его поля и методы.
class Counter {
private int value;
class Incrementer {
void increment() {
value++;
}
}
}
Поэтому сначала нужен внешний объект, а уже через него создаётся внутренний:
Counter counter = new Counter();
Counter.Incrementer incrementer = counter.new Incrementer();
incrementer.increment();
Здесь incrementer связан именно с объектом counter.
Если создать второй Counter, его внутренний объект будет работать уже с состоянием второго экземпляра.
static здесь определяет не возможность иметь статические члены.
Современный Java разрешает внутренним классам объявлять статические члены.
Главное различие в другом: внутренний класс связан с экземпляром внешнего класса, а статический вложенный — нет.
Локальный класс
Класс можно объявить ещё уже — прямо внутри метода или другого блока. Такой локальный класс виден только в той области, где объявлен, поэтому подходит для небольшой вспомогательной логики, которая нигде больше не нужна.
void printMessage(String prefix) {
class Printer {
void print(String text) {
System.out.println(prefix + text);
}
}
new Printer().print("Ready");
}
Локальный класс может использовать переменные окружающего метода, если они объявлены
final или являются effectively final — то есть после присваивания больше не изменяются.
В примере параметр prefix не меняется, поэтому доступен внутри Printer.
Анонимный класс
Если отдельное имя классу вообще не понадобится, класс можно объявить и сразу создать его объект одним выражением. Это анонимный класс. Чаще всего так создают одноразовую реализацию интерфейса или небольшого специализированного наследника уже существующего класса.
interface Greeter {
void greet();
}
Greeter greeter = new Greeter() {
@Override
public void greet() {
System.out.println("Hello");
}
};
greeter.greet();
После new Greeter() идёт тело нового безымянного класса,
который реализует Greeter. Собственного конструктора у анонимного класса быть не может,
потому что у него нет имени. Как и локальный класс, он может использовать только
final или effectively final локальные переменные окружающей области.
Как выбрать подходящий вариант
- Статический вложенный класс — когда тип логически относится к внешнему классу, но не должен быть связан с его конкретным объектом.
- Внутренний класс — когда объекту вложенного класса нужен прямой доступ к состоянию конкретного внешнего объекта.
- Локальный класс — когда именованный вспомогательный тип нужен только внутри одного метода или блока.
- Анонимный класс — когда нужна одноразовая реализация или специализация и отдельное имя только мешало бы.
На практике чаще всего стоит начинать со статического вложенного класса: связь с внешним экземпляром нужна не всегда, а лишняя неявная связь усложняет устройство объектов. Внутренний, локальный или анонимный класс выбирают тогда, когда их более тесная связь с окружающим кодом действительно полезна.