Вложенные классы
Класс можно объявить внутри другого класса. Так поступают с небольшими вспомогательными классами, которые нужны только своему «хозяину». Вложение показывает связь между классами и не засоряет программу лишними именами.
Статический вложенный класс
Класс с модификатором static внутри другого класса — это обычный класс, просто его полное имя включает имя внешнего: Order.Item. С объектами внешнего класса он не связан:
class Order {
static class Item {
String title;
int quantity;
Item(String title, int quantity) {
this.title = title;
this.quantity = quantity;
}
}
}
void main() {
Order.Item item = new Order.Item("Чай", 2);
IO.println(item.title + " x" + item.quantity);
}Результат:
Чай x2Внутренний класс
Вложенный класс без static называется внутренним. Каждый его объект привязан к объекту внешнего класса и видит его поля, даже закрытые:
class Counter {
private int count;
class Clicker {
void click() {
count++;
}
}
Clicker newClicker() {
return new Clicker();
}
int getCount() {
return count;
}
}
void main() {
Counter counter = new Counter();
Counter.Clicker clicker = counter.newClicker();
clicker.click();
clicker.click();
IO.println(counter.getCount());
}Результат:
2Объект Clicker увеличивает счётчик именно того объекта Counter, которым была создана.
Локальный класс
Класс можно объявить прямо внутри метода. Он виден только в этом методе — удобно, когда вспомогательный тип нужен в одном месте. Чаще всего так объявляют локальную запись:
void main() {
record Score(String name, int points) {}
Score[] scores = { new Score("Анна", 90), new Score("Олег", 75) };
for (Score s : scores) {
IO.println(s.name() + ": " + s.points());
}
}Результат:
Анна: 90
Олег: 75Анонимный класс
Анонимный класс объявляется и создаётся в одном выражении, без имени. Обычно так на ходу реализуют интерфейс:
interface Greeter {
String greet(String name);
}
void main() {
Greeter polite = new Greeter() {
@Override
public String greet(String name) {
return "Добрый день, " + name + "!";
}
};
IO.println(polite.greet("Анна"));
}Результат:
Добрый день, Анна!Если в интерфейсе один абстрактный метод, вместо анонимного класса обычно пишут лямбду: Greeter polite = name -> "Добрый день, " + name + "!";.
Какой выбрать
Начинайте со статического вложенного класса: он проще и не держит ссылку на внешний объект. Внутренний нужен, только если классу действительно нужны поля внешнего объекта. Кстати, все классы в примерах этой главы — тоже вложенные: компилятор помещает их внутрь неявного класса Main.
Частая ошибка
Объект внутреннего класса не может существовать без внешнего объекта. Попытка создать его как статический вложенный не компилируется:
class Counter {
private int count;
class Clicker {
void click() {
count++;
}
}
}
void main() {
Counter.Clicker clicker = new Counter.Clicker();
}Результат:
Main.java:12: error: an enclosing instance that contains Main.Counter.Clicker is required
Counter.Clicker clicker = new Counter.Clicker();
^Создайте объект через внешний: counter.new Clicker(), или сделайте класс static, если поля внешнего объекта ему не нужны.