1. 익명 클래스와의 첫 만남
Animal이라는 클래스가 있고, 동물이 어떻게 행동하는지를 설명한다고 해 보세요. 이 클래스에는 say() 메서드가 있고 "동물이 소리를 낸다"를 출력합니다. 개처럼 행동하며 "멍!"을 출력하는 객체가 필요하지만, 이를 위해 별도의 파일 Dog.java를 만들고 싶지는 않습니다.
바로 여기서 익명 클래스가 도움이 됩니다. 이름이 없는 클래스이며 사용 위치에서 곧바로 선언하고 생성됩니다. 🚀 별도 파일을 만들지 않고도 즉석에서 클래스를 상속할 수 있게 해 줍니다. 이곳저곳에서 한 번만 쓰이는 작은 클래스 파일을 잔뜩 만드는 대신, 필요한 자리에서 바로 로직을 작성할 수 있습니다.
문법은 어떻게 생겼나요?
익명 클래스의 문법은 낯설어 보일 수 있지만 실제로는 꽤 단순합니다:
TipPeremennoy imya = new TipDlyaNasledovaniya() {
// 여기서 익명 클래스의 본문을 작성합니다
// 부모 클래스의 메서드를 오버라이드합니다
};
이 문법을 살펴봅시다:
- TipPeremennoy imya: 새 객체를 저장할 일반 변수 선언입니다.
- new TipDlyaNasledovaniya(): 새 객체를 생성하는 것처럼 보입니다. 하지만 클래스 이름 대신 상속하려는 클래스의 이름을 사용합니다. 괄호 () 뒤에는 세미콜론 ;이 없습니다!
- { ... }: 여기서 중괄호를 열고 익명 클래스의 모든 로직을 작성합니다. 부모 클래스의 메서드를 오버라이드하거나 자체 로직을 추가할 수 있습니다.
일반 클래스를 상속하는 예:
동물 예제로 돌아가 봅시다.
class Animal {
void say() {
System.out.println("동물이 소리를 낸다");
}
}
// Animal을 상속하는 익명 클래스 생성
Animal dog = new Animal() {
// say() 메서드 오버라이드
@Override
void say() {
System.out.println("멍멍! 🐶");
}
};
Animal cat = new Animal() {
@Override
void say() {
System.out.println("야옹야옹! 🐱");
}
};
dog.say(); // 출력: 멍멍! 🐶
cat.say(); // 출력: 야옹야옹! 🐱
이 예제에서 우리는 dog과 cat 두 객체를 만들었는데, 본질적으로 Animal을 상속하는 익명 클래스입니다. 이때 별도의 파일은 하나도 만들지 않았습니다.
접근 방식 비교
// 일반적인 방법(별도 클래스를 만듦)
class Dog extends Animal {
@Override
void say() { System.out.println("멍!"); }
}
Animal dog = new Dog();
// VS
// 익명 클래스(모두 한 곳에서)
Animal dog = new Animal() {
@Override
void say() { System.out.println("멍!"); }
};
결과는 같지만 익명 클래스가 더 간결하고 프로젝트를 어지럽히지 않습니다!
2. 컴파일 후 익명 클래스의 이름
익명 클래스는 소스 코드에 이름이 없지만, Java 컴파일러는 .class 파일을 만들려면 어쨌든 이름을 붙여야 합니다. 이를 위해 엄격한 규칙을 따릅니다:
- 익명 클래스의 파일 이름은 그 클래스가 선언된 외부 클래스의 이름으로 시작합니다.
- 외부 클래스 이름 뒤에 달러 기호 $가 붙습니다.
- 그 뒤에 이 파일 내에서의 익명 클래스의 순번이 옵니다. 시작 값은 1입니다.
따라서 동물 예제가 Main.java에 있다면, 컴파일 후 다음 세 파일이 생성됩니다:
- Main.class
- Main$1.class (개용 익명 클래스)
- Main$2.class (고양이용 익명 클래스)
만약 익명 클래스가 내부 클래스 안에 있는 메서드 안에서 선언되었다면 이름은 더 복잡해져서 OuterClass$InnerClass$1.class처럼 보일 수 있습니다.
이 규칙은 컴파일러의 내부 규약으로 알아두면 유용하지만, 일상적인 개발에서는 큰 의미가 없습니다. 중요한 점은 — 익명 클래스는 소스에 이름이 없을 뿐 여전히 완전한 클래스라는 것입니다.
3. 중요한 특징과 제약
익명 클래스는 강력한 도구이지만, 몇 가지 규칙이 있습니다.
변수 접근. 익명 클래스는 자신을 둘러싼 메서드의 변수를 사용할 수 있습니다. 다만 그 변수는 final이거나 effectively final이어야 합니다(즉, 초기화 이후 값이 변경되지 않아야 합니다).
public void doSomething() {
String greeting = "안녕!"; // 이 변수는 effectively final
class OuterClass {
void greet() {
// 메서드 내부에서 익명 클래스 생성
new Object() {
void sayHello() {
System.out.println(greeting); // 허용됨
// greeting = "잘 가!"; // 이는 컴파일 오류!
}
}.sayHello();
}
}
new OuterClass().greet();
}
왜일까요? 익명 클래스는 메서드보다 더 오래 ‘살’ 수 있고, 변수를 변경할 수 있었다면 문제가 생길 수 있기 때문입니다.
생성자가 없음. 익명 클래스는 이름이 없으므로 생성자도 가질 수 없습니다. 하지만 객체 생성 시 코드를 실행하기 위해 초기화 블록을 사용할 수는 있습니다:
Animal dog = new Animal() {
// 초기화 블록
{
System.out.println("익명 클래스 초기화 🐶");
}
@Override
void say() {
System.out.println("멍멍!");
}
};
제약. 익명 클래스는 정적 필드(상수 제외)나 메서드를 선언할 수 없습니다. 항상 다른 객체의 일부로 생성되므로 static, public, protected, private가 될 수 없습니다.
4. 유용한 팁
익명 클래스를 사용할 때
- 클래스(또는 인터페이스)를 단 한 번만 상속(구현)하면 됩니다.
- 구현이 작습니다 — 메서드 1–2개, 길어도 수십 줄 이내.
- 클래스가 한 곳에서만 필요하고 이름을 부여할 의미가 없습니다.
- 일회성의 작은 클래스들로 패키지가 ‘복잡해지는’ 것을 피하고 싶습니다.
전형적인 사용 시나리오:
- 이벤트 핸들러(GUI, Swing, Android 등).
- 메서드에 콜백(callback) 전달.
- 컬렉션 정렬을 위한 Comparator를 빠르게 구현.
- 임시로 수정된 객체(예: 테스트용).
5. 외부 클래스와의 상호작용
익명 클래스가 외부 클래스의 비정적 메서드나 블록 안에서 선언되었다면, 외부 클래스의 필드와 메서드(심지어 private도!)에 접근할 수 있습니다.
public class Outer {
private String secret = "비밀 텍스트";
// 기본 클래스
class Printer {
public void print() {
System.out.println("일반 출력");
}
}
public void revealSecret() {
Printer p = new Printer() {
@Override
public void print() {
System.out.println("private 멤버 접근: " + secret);
}
};
p.print();
}
public static void main(String[] args) {
new Outer().revealSecret();
}
}
6. 익명 클래스를 사용할 때 흔한 실수
오류 №1: 둘러싼 메서드의 변수를 변경하려는 시도.
익명 클래스 밖에서 선언한 변수를 익명 클래스 안에서 사용한 뒤, 그 변수를 변경하려 하면 컴파일 오류가 발생합니다. 변수는 final이거나 effectively final(초기화 이후 변경되지 않음)이어야 합니다.
오류 №2: 지나치게 큰 익명 클래스.
익명 클래스가 수십 줄까지 커지고 여러 메서드를 갖게 되었다면, 별도의 이름 있는 클래스로 분리해야 한다는 신호입니다. 그렇지 않으면 코드는 읽기 어려워집니다.
오류 №3: 정적 메서드나 필드를 사용하려는 시도.
익명 클래스에는 정적 메서드나 필드(상수 제외)를 선언할 수 없습니다. 정말 필요하다면 일반 중첩 클래스로 만드세요.
오류 №4: 가시성 범위를 잊어버림.
익명 클래스는 선언된 그 위치에서만 보이며 이름이 없습니다. 여러 번 사용해야 한다면 일반 클래스로 선언하세요.
GO TO FULL VERSION