JavaRush /Курсы /JAVA 25 SELF /Внутренние классы (non-static inner)

Внутренние классы (non-static inner)

JAVA 25 SELF
16 уровень , 0 лекция
Открыта

1. Что такое внутренний класс (inner class)?

В Java класс может быть объявлен не только на верхнем уровне (в файле), но и внутри другого класса. Такой класс называется вложенным (nested class). Если такой класс объявлен без модификатора static, то он называется внутренним (или non-static inner class, или просто inner class).

Внутренний класс — это класс, который объявлен внутри другого класса и связан с экземпляром этого внешнего класса. Он может обращаться ко всем полям и методам внешнего класса, даже если они private. Это как если бы у объекта был свой "секретный помощник", которому разрешено всё!

Это примерно как дом с комнатами. Дом — это внешний класс, а комната — внутренний класс. Комната не может существовать без дома, но при этом имеет доступ к его ресурсам: свету, отоплению, мебели. Если дом исчезнет, исчезнет и комната. Точно так же работают внутренние классы в Java!

Зачем нужны внутренние классы?

  • Логическая связь: Когда один класс нужен только для работы с другим классом и не имеет смысла вне его.
  • Инкапсуляция: Позволяет скрывать детали реализации, не засоряя пространство имён пакета.
  • Доступ к приватным членам: Внутренний класс может обращаться к приватным полям и методам внешнего класса.
  • Компактность: Уменьшает количество "мусорных" классов на уровне пакета.

Синтаксис объявления внутреннего класса

Объявить внутренний класс очень просто: он объявляется внутри тела другого класса, без модификатора static.

class Outer {
    // поля и методы внешнего класса

    class Inner {
        // поля и методы внутреннего класса
        void printHello() {
            System.out.println("Hello from Inner!");
        }
    }
}

Здесь Inner — внутренний класс по отношению к Outer.

Картинка: Визуализация

Outer
│
├─ поля/методы
│
└─ Inner (внутренний класс)
      └─ свои поля/методы

2. Как создать экземпляр внутреннего класса

Вот тут кроется одна из самых частых ловушек для новичков! Экземпляр внутреннего класса всегда связан с конкретным объектом внешнего класса.

Пример:

Outer outer = new Outer();             // создаём объект внешнего класса
Outer.Inner inner = outer.new Inner(); // создаём внутренний класс через объект внешнего!
inner.printHello();                    // Hello from Inner!

Попытка сделать просто new Inner() приведёт к ошибке компиляции, потому что Java не знает, с каким объектом внешнего класса связать этот внутренний.

Почему так?

Внутренний класс может обращаться к полям и методам внешнего класса. Поэтому он должен "знать", с каким именно объектом внешнего класса он работает.

3. Пример использования внутреннего класса

Давайте рассмотрим пример, где внутренний класс действительно полезен.

Пример 1: Модель "Рюкзак и предметы"

Допустим, у нас есть класс Backpack, который может хранить предметы (Item). Но хотим, чтобы класс Item был доступен только внутри Backpack, потому что вне рюкзака предметы нас не интересуют.

public class Backpack {
    private String owner;

    public Backpack(String owner) {
        this.owner = owner;
    }

    // Внутренний класс — предмет может существовать только в рюкзаке!
    class Item {
        private String name;

        public Item(String name) {
            this.name = name;
        }

        public void printInfo() {
            // Магия! Видим приватное поле внешнего класса
            System.out.println(owner + " имеет предмет: " + name);
        }
    }
}

Использование:

Backpack bp = new Backpack("Вася");
Backpack.Item item = bp.new Item("Учебник Java");
item.printInfo(); // Вася имеет предмет: Учебник Java

Обратите внимание: Item может обращаться к полю owner, даже если оно private!

Пример 2: Итератор для собственной коллекции

В Java коллекции часто реализуют внутренний класс-итератор. Давайте попробуем сделать свою простую коллекцию с внутренним классом-итератором.

public class IntList {
    private int[] data = new int[10];
    private int size = 0;

    public void add(int value) {
        data[size++] = value;
    }

    // Внутренний класс — итератор!
    class Iterator {
        private int index = 0;

        public boolean hasNext() {
            return index < size;
        }

        public int next() {
            return data[index++];
        }
    }
}

Использование:

IntList list = new IntList();
list.add(10);
list.add(20);

IntList.Iterator it = list.new Iterator();
while (it.hasNext()) {
    System.out.println(it.next());
}

4. Особенности внутренних классов

Доступ к членам внешнего класса

Внутренний класс может обращаться к любым (даже private) полям и методам внешнего класса. Это удобно, но требует осторожности: если внешний класс изменится, внутренний может внезапно "сломаться".

class Outer {
    private int secret = 42;

    class Inner {
        void showSecret() {
            System.out.println("Секрет: " + secret);
        }
    }
}

Внутренний класс не может содержать статические члены

Внутренний (non-static inner) класс не может содержать статические поля или методы, кроме статических final-констант (например, public static final int MY_CONST = 123;). Это связано с тем, что внутренний класс всегда "привязан" к экземпляру внешнего класса.

Если вам нужно объявить статический вложенный класс — используйте модификатор static (об этом в следующей лекции).

Видимость внутреннего класса

Внутренний класс может быть private, protected, public или иметь видимость уровня пакета (package-private).

public class Outer {
    private class Inner { /* ... */ }
}

5. Полезные нюансы

Когда использовать внутренние классы

Внутренние классы — не для красоты, а для логической организации кода. Используйте их, когда:

  • Класс нужен только в одном месте (например, вспомогательный итератор, обработчик, часть сложной структуры).
  • Класс тесно связан с внешним классом и не имеет смысла вне его.
  • Хотите скрыть детали реализации от других классов пакета.

Не используйте внутренние классы только ради моды! Иногда лучше вынести класс на верхний уровень, если он может быть полезен в других местах.

Внутренний класс и this

Внутри внутреннего класса можно обратиться к полям и методам внешнего класса с помощью OuterClassName.this.

class Car {
    private String model = "Tesla";

    class Engine {
        void printModel() {
            // Явно обращаемся к внешнему объекту
            System.out.println("Модель: " + Car.this.model);
        }
    }
}

Такой синтаксис особенно полезен, если внутренний и внешний классы имеют поля с одинаковыми именами.

Когда НЕ стоит использовать внутренние классы

Не используйте, если:

  • Внутренний класс не обращается к полям/методам внешнего класса
  • Класс может быть полезен в других местах программы
  • Внешний класс становится слишком большим и сложным

В таких случаях лучше:

  • Сделать класс статическим вложенным (static class)
  • Вынести класс в отдельный файл
  • Использовать обычные методы вместо целого класса

6. Типичные ошибки при работе с внутренними классами

Ошибка № 1: Попытка создать внутренний класс без объекта внешнего класса.
Если написать new Inner(), компилятор выдаст ошибку: "No enclosing instance of type Outer is accessible". Всегда создавайте внутренний класс через объект внешнего класса: outer.new Inner().

Ошибка № 2: Попытка объявить статические поля или методы во внутреннем классе.
Внутренний класс не может содержать статические члены (кроме констант). Если нужно — используйте статический вложенный класс, то есть объявите класс с модификатором static.

Ошибка № 3: Избыточное использование внутренних классов.
Если внутренний класс не использует поля/методы внешнего класса, вероятно, он должен быть static или вообще вынесен наружу. Не создавайте внутренние классы "на всякий случай".

Ошибка № 4: Путаница с обращением к полям внешнего класса.
Если внутренний и внешний классы имеют поля с одинаковыми именами, используйте OuterClassName.this.field для явного обращения к полю внешнего класса.

1
Задача
JAVA 25 SELF, 16 уровень, 0 лекция
Недоступна
Складской учет: маркировка коробок 📦
Складской учет: маркировка коробок 📦
1
Задача
JAVA 25 SELF, 16 уровень, 0 лекция
Недоступна
Персональный ассистент: вежливое приветствие 🗣️
Персональный ассистент: вежливое приветствие 🗣️
1
Задача
JAVA 25 SELF, 16 уровень, 0 лекция
Недоступна
Магическая библиотека: каталогизация свитков 📜
Магическая библиотека: каталогизация свитков 📜
1
Задача
JAVA 25 SELF, 16 уровень, 0 лекция
Недоступна
Умный дом: адресация помещений 🏠
Умный дом: адресация помещений 🏠
Комментарии (4)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
kasnil Уровень 24
17 января 2026
JAVA 25 SELF 🤣 JEP 395: Records

Static members of inner classes
It is currently specified to be a compile-time error if an inner class declares a member that is explicitly or implicitly static, unless the member is a constant variable. This means that, for example, an inner class cannot declare a record class member, since nested record classes are implicitly static.

We relax this restriction in order to allow an inner class to declare members that are either explicitly or implicitly static. In particular, this allows an inner class to declare a static member that is a record class.
Kenny Уровень 52
20 ноября 2025
Разве сейчас нельзя создавать статик поля во внутреннем классе? Сам проверял, всё получается
Andrey Уровень 1
11 сентября 2025
16
nastya_zhadan Уровень 66
20 сентября 2025
+