W3docs

Локальные классы в Java

Объявляйте классы внутри тела метода в Java с доступом к effectively-final переменным метода.

Локальный класс — это класс, объявленный внутри тела метода (или конструктора, или блока инициализации). Его область видимости ограничена блоком, в котором он объявлен — ничто за пределами этого блока не может ссылаться на него по имени. В остальном он ведёт себя во многом как внутренний класс: имеет доступ к членам объемлющего экземпляра и к effectively-final локальным переменным метода.

Локальные классы редко встречаются в современном Java. Лямбды вытеснили их основные сценарии использования для однометодных колбэков, а анонимные классы обычно короче для одноразовых реализаций. Однако локальный класс иногда оказывается наиболее чистым вариантом, когда нужен именованный тип, участвующий в логике объемлющего метода и не нужный нигде за его пределами.

Если вы ещё не знакомы с другими членами семейства вложенных классов, главы обзор вложенных классов и внутренние классы введут термины, используемые ниже.

Объявление

Объявление класса внутри тела метода:

public void process(List<String> lines) {
  class Counter {
    int n = 0;
    void inc() { n++; }
  }

  Counter c = new Counter();
  for (String s : lines) if (!s.isBlank()) c.inc();

  System.out.println("non-blank lines: " + c.n);
}

За пределами process никто не видит Counter — его имя не существует за закрывающей скобкой метода. Внутри он используется как любой другой класс.

Почему выбрать его вместо анонимного класса?

У локального класса есть настоящее имя, что даёт несколько преимуществ перед анонимным классом:

  • Конструктор. Ему можно передавать параметры, добавлять валидацию и иметь несколько конструкторов.
  • Несколько экземпляров. Поскольку у класса есть имя, можно написать new Counter() более одного раза. Определение анонимного класса создаёт только один экземпляр на выражение.
  • Самоссылки. Counter может принимать себя в качестве параметра, хранить поле Counter next, рекурсировать — всё, что могут именованные типы.
  • Читаемость. Имя вроде Counter документирует назначение вспомогательного класса. Анонимные блоки new Object() { ... } труднее читать.

Доступ к объемлющему состоянию

Локальные классы могут:

  • Читать и записывать поля объемлющего экземпляра (если объемлющий метод не статический).
  • Вызывать методы экземпляра объемлющего класса.
  • Читать — но не переназначать — final или effectively final локальные переменные и параметры объемлющего метода.
void run(int multiplier) {
  class Scaler {
    int scale(int x) { return x * multiplier; }   // captures the parameter
  }
  System.out.println(new Scaler().scale(7));
}

multiplier захватывается в синтетический класс как поле. В этом и состоит требование «effectively final» — если бы multiplier можно было переназначить внутри run после того, как Scaler захватил его старое значение, они бы молча разошлись.

В статическом методе

В static методе локальный класс неявно статический — у него нет объемлющего экземпляра для обращения:

public static int demo() {
  class Helper {
    int square(int n) { return n * n; }
  }
  return new Helper().square(7);
}

Здесь нет Outer.this, потому что объемлющий метод статический. Helper по-прежнему может захватывать effectively-final локальные переменные из demo, но не поля экземпляра объемлющего объекта, которого не существует.

Когда действительно использовать

Конкретные случаи, когда локальный класс действительно лучше альтернатив:

  • У вспомогательного класса более одного метода, и лучше видеть методы, сгруппированные вместе с чётким именем, чем разбросанные по шаблону анонимного класса.
  • Вспомогательному классу нужен конструктор для валидации его настройки.
  • Вспомогательный класс рекурсирует или хранит ссылки на другие экземпляры своего типа.

Если справится лямбда или короткий анонимный класс — предпочтите их. Локальные классы — нишевый инструмент.

Модификаторы

Локальный класс принимает лишь ограниченный набор модификаторов:

  • final — помечает класс как нерасширяемый. У него уже нет имени для внешнего кода, чтобы его расширить, поэтому это в основном декларация о намерениях.
  • abstract — допустимо, но редко: потребовался бы второй локальный класс в том же блоке для его расширения.
  • Модификаторы доступа (public, protected, private) не допускаются. Локальный класс существует целиком внутри одного блока, поэтому нет области видимости за его пределами, из которой можно было бы управлять видимостью.

Также его нельзя объявить static напрямую. Захватывает ли он объемлющий экземпляр, зависит от того, является ли окружающий метод статическим, как показано выше, — а не от ключевого слова.

Развёрнутый пример

java— editable, runs on the server

Что дальше

На этом завершается обзор вложенных классов — и большей части классического ООП-механизма Java. Следующий раздел части 6 переходит к специальным видам классов: типам, которые Java предоставляет для определённых форм данных, начиная с enum для фиксированных наборов констант. Продолжите с Java enums.

Практика

Практика
Почему вы можете предпочесть локальный класс анонимному для одной и той же задачи?
Почему вы можете предпочесть локальный класс анонимному для одной и той же задачи?
Was this page helpful?