JavaRush /Курси /JAVA 25 SELF /Локальні класи: оголошення всередині методів

Локальні класи: оголошення всередині методів

JAVA 25 SELF
Рівень 16 , Лекція 3
Відкрита

1. Локальний клас

Локальний клас — це клас, оголошений всередині методу (або навіть у блоці коду, наприклад, у циклі чи в блоці if). Він видимий і доступний лише в межах цього методу, а поза ним — ніби його й не було. На відміну від анонімних класів, локальний клас має імʼя (хоч і просте), може містити кілька методів і навіть поля.

Проведімо аналогію: уявіть, що ви готуєте складну страву за рецептом. На одному з кроків потрібно приготувати особливий соус, який використовують лише тут і більше ніде. Замість того щоб випускати окрему «книгу рецептів» заради одного соусу, ви описуєте його приготування безпосередньо всередині основного рецепта. Локальний клас працює так само — це «рецепт у рецепті», потрібен лише в одному конкретному методі.

Приклад оголошення локального класу

public class Outer {
    void someMethod() {
        class Local {
            void print() {
                System.out.println("Привіт від Local!");
            }
        }
        Local local = new Local();
        local.print();
    }
}

Тут клас Local оголошено всередині методу someMethod класу Outer. Він існує лише в цьому методі й не видимий ззовні.

Синтаксис локальних класів

Оголошення локального класу схоже на звичне оголошення класу, лише відбувається всередині методу:

void myMethod() {
    class MyLocalClass {
        void doSomething() {
            System.out.println("Локальний клас працює!");
        }
    }

    MyLocalClass obj = new MyLocalClass();
    obj.doSomething();
}

Особливості синтаксису:

  • Локальний клас можна оголосити в будь-якому методі, конструкторі або навіть у блоці ініціалізації.
  • Імʼя локального класу обовʼязкове (на відміну від анонімних класів).
  • Локальний клас не можна оголошувати з модифікаторами доступу (public, private, protected) або з модифікатором static.
  • Локальний клас може реалізовувати інтерфейси або успадковувати від інших класів.

Порівняння типів класів:

public class Example {
// 1. Звичайний клас (в окремому файлі)
// class RegularClass { ... }

    // 2. Внутрішній клас (у класі)
    class InnerClass { }
    
    // 3. Статичний вкладений клас (у класі)
    static class NestedClass { }
    
    void method() {
        // 4. Локальний клас (у методі)
        class LocalClass { }
        
        // 5. Анонімний клас (у методі, без імені)
        Object obj = new Object() { };
    }
}

Локальний клас — це іменований клас лише для цього методу!

2. Особливості локальних класів

Доступ до змінних методу

Локальний клас може звертатися до:

  • полів зовнішнього класу (навіть якщо вони private);
  • лише до змінних навколишнього методу, що є final або effectively final.

Що таке «effectively final»?
Це змінна, значення якої не змінюється після ініціалізації. Наприклад:

void foo() {
    int x = 5; // effectively final
    class L { void print() { System.out.println(x); } }
}

Якби ви пізніше написали x = 10;, компілятор видав би помилку.

Чому так?

Це пов’язано з тим, як Java реалізує локальні класи «під капотом»: змінні методу фактично копіюються всередину локального класу, а не зберігаються за посиланням. Якби змінна могла змінюватися, локальний клас працював би із застарілою копією — а це шлях до помилок і головного болю.

Приклад із доступом до змінних

public class Outer {
    private String secret = "таємниця!";

    void revealSecret() {
        String greeting = "Привіт,"; // effectively final

        class Revealer {
            void printSecret() {
                System.out.println(greeting + " " + secret);
            }
        }

        Revealer revealer = new Revealer();
        revealer.printSecret();
    }
}

3. Застосування локальних класів: навіщо і коли?

Локальні класи — інструмент не на щодень, але іноді вони роблять код чистішим і компактнішим. Ось коли варто їх використовувати:

  • Допоміжна логіка, специфічна для одного методу.
    Наприклад, якщо в методі потрібно реалізувати складне сортування, фільтрацію або тимчасову структуру даних.
  • Інкапсуляція, щоб не «засмічувати» клас зайвими типами.
    Якщо клас потрібен лише в одному місці, навіщо робити його видимим у всьому класі або пакеті?
  • Реалізація шаблонів проєктування, де допоміжні об’єкти потрібні тільки локально.

Приклад: сортування за допомогою локального класу

Припустімо, у вас є список імен, і ви хочете відсортувати їх за довжиною. Можна створити локальний клас-компаратор:

import java.util.*;

public class NameSorter {
    public void sortNames(List<String> names) {
        class LengthComparator implements Comparator<String> {
            @Override
            public int compare(String a, String b) {
                return Integer.compare(a.length(), b.length());
            }
        }
        Collections.sort(names, new LengthComparator());
    }
}

Приклад: тимчасова структура

Іноді потрібно створити допоміжну структуру для обробки даних в одному методі:

void processScores(int[] scores) {
    class ScoreInfo {
        int score;
        boolean passed;

        ScoreInfo(int score) {
            this.score = score;
            this.passed = score >= 60;
        }
    }

    for (int s : scores) {
        ScoreInfo info = new ScoreInfo(s);
        System.out.println("Оцінка: " + info.score + ", склав: " + info.passed);
    }
}

4. Локальні класи vs. анонімні класи

Іноді виникає запитання: навіщо взагалі потрібні локальні класи, якщо є анонімні? Розберімося.

  • Локальний клас — це іменований клас, який можна використовувати кілька разів у методі; до нього можна додати поля, кілька методів, вкладені класи.
  • Анонімний клас — це разова реалізація інтерфейсу або класу-нащадка без імені, зазвичай з одним методом (або перевизначенням одного-двох методів).

Коли використовувати:

  • Якщо логіка проста й потрібна лише один раз — використовуйте анонімний клас.
  • Якщо потрібно більше методів або полів чи клас використовуватиметься в кількох місцях методу — використовуйте локальний клас.

Приклад порівняння

Анонімний клас:

Runnable r = new Runnable() {
    @Override
    public void run() {
        System.out.println("Швидко й анонімно!");
    }
};

Локальний клас:

void doWork() {
    class MyWorker implements Runnable {
        @Override
        public void run() {
            System.out.println("Я локальний клас!");
        }
    }
    MyWorker worker = new MyWorker();
    worker.run();
}

5. Обмеження та особливості локальних класів

  • Модифікатори доступу: локальний клас не можна оголосити як public, private або protected. Також не можна додати static.
  • Статичні члени: локальний клас не може містити статичних членів, окрім констант (static final).
  • Область видимості: локальний клас видимий лише в тому блоці, де його оголошено.
  • Використання узагальнень: локальний клас може бути узагальненим, якщо це потрібно.
  • Вкладеність: можна оголошувати локальні класи всередині інших локальних класів (але краще не зловживати цим, інакше код нагадуватиме ляльку-вкладанку).

6. Локальні класи в реальному застосунку

Продовжімо розвивати наш навчальний застосунок. Припустімо, у нас є клас Quiz, який ставить користувачеві запитання й перевіряє відповіді. Ми хочемо всередині методу перевірки створити тимчасовий клас, що зберігатиме відповідь користувача та статус перевірки.

Приклад: використання локального класу в застосунку

import java.util.Scanner;

public class Quiz {
    private String question = "Столиця Франції?";
    private String correctAnswer = "Париж";

    public void runQuiz() {
        Scanner scanner = new Scanner(System.in);

        System.out.println(question);
        String userAnswer = scanner.nextLine();

        // Локальний клас для зберігання результату
        class AnswerResult {
            String answer;
            boolean isCorrect;

            AnswerResult(String answer) {
                this.answer = answer;
                this.isCorrect = answer.equalsIgnoreCase(correctAnswer);
            }

            void printResult() {
                if (isCorrect) {
                    System.out.println("Правильно!");
                } else {
                    System.out.println("Неправильно, правильна відповідь: " + correctAnswer);
                }
            }
        }

        AnswerResult result = new AnswerResult(userAnswer);
        result.printResult();
    }
}

Тут клас AnswerResult існує лише всередині методу runQuiz і більше ніде не потрібен. Це чудовий приклад локального класу на практиці!

7. Типові помилки під час роботи з локальними класами

Помилка № 1: спроба звернутися до змінної методу, яка не final або не effectively final.
Якщо ви оголосили змінну в методі, а потім змінили її значення після використання в локальному класі, компілятор одразу видасть помилку. Завжди стежте, щоб такі змінні не змінювалися після ініціалізації.

Помилка № 2: бажання додати модифікатори доступу або static.
Локальний клас не можна оголошувати з модифікаторами public, private, protected або static. Якщо ви спробуєте це зробити — компілятор не дозволить.

Помилка № 3: спроба використовувати локальний клас поза методом.
Локальний клас живе лише в тому методі (або блоці), де його оголошено. З інших методів або ззовні класу він недоступний.

Помилка № 4: зловживання локальними класами для складної логіки.
Якщо ваш локальний клас стає надто великим, містить багато методів або полів, найімовірніше, його варто винести в окремий клас. Локальні класи доречні для компактних, допоміжних завдань.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ