CodeGym /행동 /JAVA 25 SELF /로컬 클래스: 메서드 내부 선언

로컬 클래스: 메서드 내부 선언

JAVA 25 SELF
레벨 16 , 레슨 3
사용 가능

1. 로컬 클래스

로컬 클래스는 메서드 내부(또는 코드 블록, 예: 반복문이나 if 블록)에서 선언된 클래스입니다. 이 클래스는 해당 메서드 안에서만 보이고 접근할 수 있으며, 그 밖에서는 존재하지 않는 것과 같습니다. 익명 클래스와 달리 로컬 클래스는 이름이 있고(비록 단순하더라도), 여러 메서드와 심지어 필드도 가질 수 있습니다.

비유하자면—여러분이 복잡한 요리를 레시피대로 만들고 있다고 생각해 보세요. 어떤 단계에서는 오직 이 레시피에서만 사용하는 특별한 소스를 만들어야 합니다. 그 소스 하나를 위해 별도의 요리책을 출간하기보다는, 메인 레시피 안에 바로 그 소스 만드는 방법을 적어 두면 됩니다. 로컬 클래스도 정확히 그렇게 동작합니다—“레시피 안의 레시피”로, 특정 메서드 하나에만 필요한 존재입니다.

로컬 클래스 선언 예

public class Outer {
    void someMethod() {
        class Local {
            void print() {
                System.out.println("Hello from Local!");
            }
        }
        Local local = new Local();
        local.print();
    }
}

여기서 Local 클래스는 OutersomeMethod 메서드 내부에 선언되었습니다. 이 메서드 안에서만 존재하며, 밖에서는 보이지 않습니다.

로컬 클래스의 문법

로컬 클래스 선언은 일반 클래스 선언과 비슷하지만, 메서드 내부에서 이루어집니다:

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 중첩 클래스(클래스 내부)
    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: 복잡한 로직에 로컬 클래스를 과도하게 사용하는 경우.
로컬 클래스가 너무 커지고, 메서드나 필드가 많아지면—별도의 클래스로 분리하는 편이 좋습니다. 로컬 클래스는 작고 보조적인 작업에 적합합니다.

1
과제
JAVA 25 SELF, 레벨 16, 레슨 3
잠금
인터랙티브 게시판: 임시 인사 👋
인터랙티브 게시판: 임시 인사 👋
1
과제
JAVA 25 SELF, 레벨 16, 레슨 3
잠금
비밀 지킴이: 통제된 비밀 공개 🔐
비밀 지킴이: 통제된 비밀 공개 🔐
1
과제
JAVA 25 SELF, 레벨 16, 레슨 3
잠금
전용 계산기: 합계 즉시 표시 ➕
전용 계산기: 합계 즉시 표시 ➕
1
과제
JAVA 25 SELF, 레벨 16, 레슨 3
잠금
텍스트 데이터 분석기: 이름에 대한 간단한 보고서 📊
텍스트 데이터 분석기: 이름에 대한 간단한 보고서 📊
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION