1. 로컬 클래스
로컬 클래스는 메서드 내부(또는 코드 블록, 예: 반복문이나 if 블록)에서 선언된 클래스입니다. 이 클래스는 해당 메서드 안에서만 보이고 접근할 수 있으며, 그 밖에서는 존재하지 않는 것과 같습니다. 익명 클래스와 달리 로컬 클래스는 이름이 있고(비록 단순하더라도), 여러 메서드와 심지어 필드도 가질 수 있습니다.
비유하자면—여러분이 복잡한 요리를 레시피대로 만들고 있다고 생각해 보세요. 어떤 단계에서는 오직 이 레시피에서만 사용하는 특별한 소스를 만들어야 합니다. 그 소스 하나를 위해 별도의 요리책을 출간하기보다는, 메인 레시피 안에 바로 그 소스 만드는 방법을 적어 두면 됩니다. 로컬 클래스도 정확히 그렇게 동작합니다—“레시피 안의 레시피”로, 특정 메서드 하나에만 필요한 존재입니다.
로컬 클래스 선언 예
public class Outer {
void someMethod() {
class Local {
void print() {
System.out.println("Hello from Local!");
}
}
Local local = new Local();
local.print();
}
}
여기서 Local 클래스는 Outer의 someMethod 메서드 내부에 선언되었습니다. 이 메서드 안에서만 존재하며, 밖에서는 보이지 않습니다.
로컬 클래스의 문법
로컬 클래스 선언은 일반 클래스 선언과 비슷하지만, 메서드 내부에서 이루어집니다:
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: 복잡한 로직에 로컬 클래스를 과도하게 사용하는 경우.
로컬 클래스가 너무 커지고, 메서드나 필드가 많아지면—별도의 클래스로 분리하는 편이 좋습니다. 로컬 클래스는 작고 보조적인 작업에 적합합니다.
GO TO FULL VERSION