0Pricing
Java Academy · Урок

Внутренние классы и доступ к внешнему классу

Используйте нестатические внутренние классы, хранящие ссылку на экземпляр внешнего класса.

«Внутренние классы и доступ к внешнему классу» — бесплатный урок 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 — локальная установка не требуется.

Все уроки этого курса

  1. Статические вложенные классы
  2. Внутренние классы и доступ к внешнему классу
  3. Локальные и анонимные классы
  4. Выбор подходящей стратегии вложенности
← Назад к Java Academy