Внутренние классы и доступ к внешнему классу
Используйте нестатические внутренние классы, хранящие ссылку на экземпляр внешнего класса.
«Внутренние классы и доступ к внешнему классу» — бесплатный урок Java Academy на CoddyKit. Это урок 2 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Java Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Java Academy содержит 4 уроков всего.
Внутренние классы
Нестатический внутренний класс хранит неявную ссылку на экземпляр внешнего класса. Он может обращаться ко всем членам внешнего класса, включая приватные.
Объявление и использование внутреннего класса
Создавайте экземпляр внутреннего класса только через экземпляр внешнего класса, используя outer.new InnerClass().
class BankAccount {
private double balance;
private String owner;
BankAccount(String owner, double initial) {
this.owner = owner; this.balance = initial;
}
class Transaction {
void deposit(double amount) {
balance += amount; // accesses outer field directly
System.out.println(owner + " deposited " + amount);
}
}
}
BankAccount acct = new BankAccount("Alice", 500);
BankAccount.Transaction tx = acct.new Transaction();
tx.deposit(100); // Alice deposited 100.0Доступ к членам внешнего класса
Внутренние классы могут обращаться ко всем членам внешнего класса — даже к приватным полям и методам.
class Encryption {
private final String secretKey = "s3cr3t"; // private!
class Encryptor {
String encrypt(String data) {
// Inner class accesses private outer field
return data + "|" + secretKey.hashCode();
}
}
class Decryptor {
String decrypt(String encrypted) {
int idx = encrypted.lastIndexOf('|');
return encrypted.substring(0, idx);
}
}
}Скрытие имён с помощью this
Если во внутреннем классе есть поле с таким же именем, как у внешнего класса, используйте OuterClass.this.field, чтобы обратиться к полю внешнего класса.
class Outer {
int value = 10;
class Inner {
int value = 20; // shadows outer.value
void printBoth() {
System.out.println("Inner value: " + value); // 20
System.out.println("Outer value: " + Outer.this.value); // 10
}
}
}
new Outer().new Inner().printBoth();
// Inner value: 20
// Outer value: 10Шаблон Iterator с внутренним классом
Классический вариант использования: реализация итератора как внутреннего класса, имеющего доступ к приватным данным содержащей его коллекции.
class NumberRange implements Iterable<Integer> {
private final int start;
private final int end;
NumberRange(int start, int end) {
this.start = start; this.end = end;
}
@Override
public java.util.Iterator<Integer> iterator() {
return new RangeIterator(); // inner class instance
}
private class RangeIterator implements java.util.Iterator<Integer> {
private int current = start; // accesses outer start
public boolean hasNext() { return current <= end; }
public Integer next() { return current++; }
}
}
for (int n : new NumberRange(1, 5)) System.out.print(n + " ");
// 1 2 3 4 5Обработчики событий как внутренние классы
В программировании GUI и событийно-ориентированном программировании внутренние классы часто используют для реализации обработчиков событий с доступом к состоянию содержащего их компонента интерфейса.
// Swing-style example (conceptual)
class LoginPanel {
private String username = "";
class LoginButtonListener {
void onButtonClicked() {
if (username.isEmpty()) {
System.out.println("Please enter username"); // accesses outer field
} else {
System.out.println("Logging in as: " + username);
}
}
}
void setUsername(String name) { this.username = name; }
}
LoginPanel panel = new LoginPanel();
panel.setUsername("alice");
panel.new LoginButtonListener().onButtonClicked();
// Logging in as: aliceВнутренние классы в коллекциях
Некоторые реализации коллекций используют внутренние классы для хранения курсоров или представлений, связанных с содержащей их коллекцией.
class SimpleStack<T> {
private Object[] elements;
private int size = 0;
SimpleStack(int capacity) {
elements = new Object[capacity];
}
void push(T item) {
if (size >= elements.length) throw new StackOverflowError();
elements[size++] = item;
}
T pop() {
if (size == 0) throw new java.util.EmptyStackException();
T item = (T) elements[--size];
elements[size] = null;
return item;
}
int size() { return size; }
}Предупреждение об утечке памяти
Экземпляры внутренних классов хранят ссылку на внешний экземпляр. Если внутренний класс покидает область внешнего класса (например, сохраняется в долго живущей коллекции), внешний экземпляр не может быть удалён сборщиком мусора.
// Memory leak: anonymous inner class (implicit outer ref) stored statically
class LeakExample {
byte[] data = new byte[1024 * 1024]; // 1MB
// BAD: this Runnable holds a reference to LeakExample.this
Runnable leak = new Runnable() {
public void run() { System.out.println(data.length); }
};
}
// If 'leak' is stored somewhere, LeakExample (and its 1MB) is never GC'd
// FIX: use a static nested class or a lambda that captures only needed values
static Runnable noLeak(byte[] data) {
return () -> System.out.println(data.length); // captures array, not outer
}Сравнение внутреннего класса и лямбда-выражения
Современные лямбда-выражения Java часто заменяют анонимные внутренние классы для интерфейсов с одним методом. Лямбда-выражения короче, но NOT имеют собственного this.
// Old way: anonymous inner class
Runnable r1 = new Runnable() {
@Override
public void run() {
System.out.println("Running (inner class) — this is the Runnable");
}
};
// Modern: lambda
Runnable r2 = () -> System.out.println("Running (lambda)");
r1.run();
r2.run();
// Lambda this = enclosing class (not the Runnable)
// Inner class this = the Runnable instance itselfКогда использовать внутренние классы
Используйте нестатические внутренние классы, когда:
- Классу нужен доступ к приватному состоянию содержащего его экземпляра
- Каждый внутренний экземпляр логически связан с определённым внешним экземпляром
- Нужен шаблон Iterator для инкапсуляции логики обхода
Практика: Paginator
Практический пагинатор, в котором внутренний класс хранит состояние страницы и получает доступ к внешнему списку данных.
import java.util.*;
class Paginator<T> {
private final List<T> items;
private final int pageSize;
Paginator(List<T> items, int pageSize) {
this.items = items; this.pageSize = pageSize;
}
class Page {
private int pageIndex = 0;
List<T> current() {
int start = pageIndex * pageSize;
int end = Math.min(start + pageSize, items.size()); // outer
return start < items.size() ? items.subList(start, end) : List.of();
}
boolean hasNext() { return (pageIndex + 1) * pageSize < items.size(); }
void next() { if (hasNext()) pageIndex++; }
}
}
Paginator<String> p = new Paginator<>(List.of("a","b","c","d","e"), 2);
Paginator.Page page = p.new Page();
System.out.println(page.current()); // [a, b]
page.next();
System.out.println(page.current()); // [c, d]Быстрая проверка
Что хранит внутренний (нестатический) класс, чего нет у статического вложенного класса?
Повторение: внутренние классы и доступ к внешнему классу
Основные выводы:
- Нестатические внутренние классы хранят неявную ссылку на содержащий экземпляр внешнего класса
- Создавайте экземпляры внутренних классов только через экземпляр внешнего класса: outer.new Inner()
- Внутренние классы могут обращаться ко всем членам внешнего класса, включая закрытые поля
- Используйте Outer.this, чтобы различать затенённые поля
- Лучшие варианты применения: Iterator, обработчики событий, внутренние структуры данных
- Риск: экземпляры внутренних классов не позволяют сборщику мусора удалить экземпляры внешнего класса
Часто задаваемые вопросы
Урок «Внутренние классы и доступ к внешнему классу» бесплатный?
Да — полный текст урока «Внутренние классы и доступ к внешнему классу» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Java Academy, подпишись на CoddyKit PRO. Курс Java Academy содержит 4 уроков всего.
Чему я научусь в уроке «Внутренние классы и доступ к внешнему классу»?
Используйте нестатические внутренние классы, хранящие ссылку на экземпляр внешнего класса. Ты практикуешь Java Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать Java Academy?
Предыдущий опыт не требуется. Java Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 2 из 4.
Сколько времени занимает урок «Внутренние классы и доступ к внешнему классу»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке Java Academy?
Да. Каждый урок Java Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Статические вложенные классы
- Внутренние классы и доступ к внешнему классу
- Локальные и анонимные классы
- Выбор подходящей стратегии вложенности