자바에서는 클래스를 선언하고 객체를 생성하는 것이 일반적이지만, 일회성으로만 사용할 객체라면 굳이 클래스를 따로 선언하는 것은 불필요할 수 있습니다.
이때 익명 클래스(Anonymous Class)를 사용하면 클래스를 선언하지 않고도 객체를 즉시 생성할 수 있어 코드를 간결하게 유지하고 유지보수를 쉽게 만들 수 있습니다.
이번 글에서는 익명 클래스의 개념, 특징, 활용 방법, 주의할 점, 그리고 람다 표현식과의 차이점까지 완벽하게 정리해보겠습니다.
1. 익명 클래스 (Anonymous Class)란?
익명 클래스(Anonymous Class)란 이름이 없는 내부 클래스로, 특정 인터페이스를 구현하거나 클래스를 상속하여 일회성으로 사용할 객체를 즉석에서 생성하는 데 사용됩니다.
일반적으로 클래스를 선언하고 객체를 생성하는 것이 일반적이지만, 단 한 번만 사용될 객체라면 굳이 클래스를 따로 선언하는 것이 비효율적일 수 있습니다. 이런 경우 익명 클래스를 사용하면 클래스를 선언하지 않고도 객체를 즉시 생성할 수 있어 코드가 간결해집니다.
즉, 익명 클래스는 인터페이스를 구현하거나 클래스를 상속하는 클래스를 즉석에서 선언하고 동시에 인스턴스를 생성하는 방식입니다.
1.1 익명 클래스의 특징
- 클래스 이름이 없음: 선언과 동시에 객체를 생성하기 때문
- 한 번만 사용 가능: 익명 클래스로 만든 객체는 재사용 불가
- 내부 클래스의 일종: 외부 클래스의 멤버 변수와 메서드에 접근 가능
- 생성자 정의 불가능: 클래스 이름이 없으므로 생성자를 가질 수 없음 (하지만 부모 클래스의 생성자는 호출 가능)
- 인터페이스 구현 가능:
new 인터페이스명() { 구현 코드 }
형태로 사용 - 클래스 상속 가능:
new 부모클래스() { 구현 코드 }
형태로 사용 - 메모리 효율성 증가: 불필요한 클래스를 생성할 필요 없이 바로 객체를 만들 수 있어 메모리를 절약할 수 있음
2. 익명 클래스의 사용 방법
익명 클래스는 주로 다음과 같은 상황에서 사용됩니다.
- 인터페이스 구현: 인터페이스를 구현하면서 동시에 객체를 생성할 때
- 추상 클래스 상속: 추상 클래스를 상속하면서 동시에 객체를 생성할 때
- 기존 클래스 확장: 기존 클래스를 확장하여 일부 메서드를 재정의하면서 객체를 생성할 때
2.1 인터페이스를 구현하는 익명 클래스
보통 인터페이스를 구현하려면 별도의 클래스를 만들어야 하지만, 익명 클래스를 사용하면 즉시 구현이 가능합니다.
interface Greeting {
void greet();
}
public class Main {
public static void main(String[] args) {
// 인터페이스를 구현하는 익명 클래스
Greeting greeting = new Greeting() {
@Override
public void greet() {
System.out.println("Hello, World!");
}
};
greeting.greet(); // 출력: Hello, World!
}
}
- 익명 클래스를 사용하면 인터페이스를 직접 구현하고 객체를 생성할 수 있습니다.
- 람다 표현식을 사용하면 더 간결하게 표현할 수 있습니다. (자세한 내용은 후반부에서 설명하겠습니다.)
2.2 추상 클래스를 상속하는 익명 클래스
추상 클래스는 직접 인스턴스화할 수 없지만, 익명 클래스를 사용하면 바로 객체를 만들 수 있습니다.
abstract class Animal {
abstract void makeSound();
}
public class Main {
public static void main(String[] args) {
// 추상 클래스를 상속하는 익명 클래스
Animal dog = new Animal() {
@Override
void makeSound() {
System.out.println("멍멍");
}
};
dog.makeSound(); // 출력: 멍멍
}
}
Animal
추상 클래스를 상속하는 익명 클래스를 생성하여makeSound
메서드를 즉시 구현했습니다.
2.3 기존 클래스를 확장하는 익명 클래스
기존 클래스를 확장하면서 특정 메서드만 재정의할 수도 있습니다.
class Person {
void introduce() {
System.out.println("저는 사람입니다.");
}
}
public class Main {
public static void main(String[] args) {
// Person 클래스를 확장하는 익명 클래스
Person korean = new Person() {
@Override
void introduce() {
System.out.println("저는 한국인입니다.");
}
};
korean.introduce(); // 출력: 저는 한국인입니다.
}
}
Person
클래스를 확장하여introduce
메서드를 오버라이딩한 익명 클래스를 생성했습니다.
3. 익명 클래스의 선언 위치
익명 클래스는 선언 위치에 따라 역할과 특징이 조금씩 달라지며, 크게 세 가지 방식으로 사용할 수 있습니다.
- 클래스 필드로 선언: 클래스 내부에서 멤버 변수로 익명 객체 선언
- 메서드 내부에서 지역 변수로 선언: 특정 메서드에서만 사용하는 익명 객체 선언
- 메서드의 매개변수(인자)로 익명 객체 전달: 메서드 호출 시 즉석에서 익명 객체 생성 및 전달
3.1 클래스 필드로 선언하는 익명 클래스
익명 클래스는 클래스의 필드(멤버 변수)로 선언될 수도 있습니다.
이렇게 하면 클래스 내부에서 해당 익명 클래스를 여러 번 사용할 수 있습니다.
class Animal {
public String bark() {
return "동물이 웁니다.";
}
}
class Creature {
// 필드에서 익명 클래스를 선언
Animal dog = new Animal() {
@Override
public String bark() {
return "멍멍!";
}
};
public void method1() {
System.out.println(dog.bark());
}
public void method2() {
System.out.println(dog.bark());
}
}
public class Main {
public static void main(String[] args) {
Creature creature = new Creature();
creature.method1(); // 출력: 멍멍!
creature.method2(); // 출력: 멍멍!
}
}
- 익명 클래스가
Creature
클래스 내부의 필드로 선언됨 method1()
,method2()
에서 같은 익명 객체(dog
)를 재사용 가능- 클래스 내부에서 동일한 동작을 여러 곳에서 호출해야 할 때 유용
3.2 지역 변수로 선언하는 익명 클래스
익명 클래스는 메서드 내부의 지역 변수로 선언할 수도 있습니다.
이 방식은 일회성으로 사용할 객체를 생성하는 데 유용합니다.
class Animal {
public String bark() {
return "동물이 웁니다.";
}
}
class Creature {
public void makeSound() {
// 지역 변수로 익명 클래스 선언
Animal cat = new Animal() {
@Override
public String bark() {
return "야옹!";
}
};
System.out.println(cat.bark());
}
}
public class Main {
public static void main(String[] args) {
Creature creature = new Creature();
creature.makeSound(); // 출력: 야옹!
}
}
makeSound()
메서드 내부에서 익명 클래스를 생성- 이 익명 객체(
cat
)는 해당 메서드 내에서만 사용 가능 - 클래스의 필드로 저장할 필요 없이, 특정 메서드에서만 필요한 경우 유용
3.3 메서드의 매개변수(인자)로 익명 객체를 전달하는 경우
익명 클래스를 메서드의 매개변수(인자)로 직접 전달할 수도 있습니다.
이 방식은 객체를 메서드로 전달할 때 임시로 동작을 정의하고 싶을 때 유용합니다.
class Animal {
public String bark() {
return "동물이 웁니다.";
}
}
class Creature {
public void makeAnimalSound(Animal animal) {
System.out.println(animal.bark());
}
}
public class Main {
public static void main(String[] args) {
Creature creature = new Creature();
// 메서드 호출 시 익명 객체를 즉석에서 전달
creature.makeAnimalSound(new Animal() {
@Override
public String bark() {
return "으르렁!";
}
});
}
}
makeAnimalSound()
메서드에 익명 클래스를 즉시 생성하여 전달Animal
을 상속받은 익명 클래스가 메서드 내부에서 동작- 코드를 더 간결하게 유지하면서 일회성 객체를 활용 가능
4. 익명 클래스의 활용 예시
익명 클래스는 실무에서 자주 사용되는 패턴중 하나입니다.
특히 이벤트 처리, 스레드 실행, 콜백 함수 구현등에 많이 사용됩니다.
4.1 이벤트 처리에서의 익명 클래스
GUI 프로그램에서는 버튼 클릭 등의 이벤트를 처리할 때 익명 클래스를 사용하면 편리합니다.
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class Main {
public static void main(String[] args) {
JFrame frame = new JFrame("익명 클래스 예제");
JButton button = new JButton("클릭하세요!");
// 버튼 클릭 이벤트 처리 (익명 클래스 사용)
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("버튼이 클릭되었습니다!");
}
});
frame.add(button);
frame.setSize(200, 200);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
ActionListener
인터페이스를 익명 클래스로 구현- 버튼 클릭 시
"버튼이 클릭되었습니다!"
출력 - 코드를 간결하게 유지하면서 UI 이벤트 처리 가능
4.2 스레드 생성에서의 익명 클래스
멀티스레딩이 필요한 경우, 익명 클래스를 사용하면 코드를 간결하게 유지하면서 스레드를 실행할 수 있습니다.
public class Main {
public static void main(String[] args) {
// 익명 클래스를 사용한 스레드 실행
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("스레드가 실행됩니다.");
}
});
thread.start();
}
}
Runnable
인터페이스를 익명 클래스로 구현하여 스레드 실행- 별도의
Runnable
구현 클래스를 만들 필요 없이 바로 실행 가능 - 비동기 작업을 간편하게 처리할 때 유용
5. 익명 클래스 사용 시 주의사항
익명 클래스는 편리하지만, 사용 시 몇 가지 주의할 점이 있습니다.
1. 재사용 불가능: 같은 기능을 여러 번 사용해야 한다면 별도의 클래스로 정의하는 것이 좋음
2. 가독성 저하 가능: 익명 클래스 내부에 많은 코드가 들어가면 오히려 가독성이 떨어질 수 있음
3. 외부 지역 변수 접근 제한: 익명 클래스 내부에서는 외부 지역 변수를 사용할 수 있지만, 해당 변수는 final
(혹은 사실상 final
)이어야 함
4. 디버깅이 어려울 수 있음: 익명 클래스는 이름이 없기 때문에, 스택 트레이스 분석이 어려울 수 있음
5. 람다 표현식으로 대체 가능: 함수형 인터페이스라면 람다 표현식을 사용하는 것이 더 좋음
6. 익명 클래스와 람다 표현식
자바 8부터는 람다 표현식을 도입하여 함수형 인터페이스를 구현할 때 더욱 간결한 문법을 제공합니다. 익명 클래스와 람다 표현식의 차이를 살펴보겠습니다.
6.1 익명 클래스를 사용한 구현
interface Calculator {
int add(int a, int b);
}
public class Main {
public static void main(String[] args) {
Calculator calc = new Calculator() {
@Override
public int add(int a, int b) {
return a + b;
}
};
System.out.println(calc.add(5, 3)); // 출력: 8
}
}
6.2 람다 표현식을 사용한 구현
interface Calculator {
int add(int a, int b);
}
public class Main {
public static void main(String[] args) {
Calculator calc = (a, b) -> a + b;
System.out.println(calc.add(5, 3)); // 출력: 8
}
}
람다 표현식을 사용하면 코드가 더욱 간결해집니다. 그러나 람다 표현식은 하나의 추상 메서드만을 가진 인터페이스(함수형 인터페이스)에서만 사용할 수 있습니다.
7. 결론
- 익명 클래스는 한 번만 사용되는 객체를 생성할 때 유용
- 불필요한 클래스를 만들지 않아 코드가 간결해짐
- 함수형 인터페이스라면 람다 표현식이 더 좋은 대안
- 여전히 GUI 이벤트 처리, 스레드 실행 등에서는 익명 클래스가 많이 사용됨
8. 참고
'BackEnd > JAVA' 카테고리의 다른 글
[JAVA] equals()와 hashCode() 완벽 정리 (1) | 2025.02.24 |
---|---|
[JAVA] Lombok의 @UtilityClass 어노테이션 정리 (1) | 2025.02.13 |
[JAVA] 얕은 복사 (Shallow Copy) VS 깊은 복사 (Deep Copy) (2) | 2025.02.06 |
[JAVA] 와일드카드 (Generic Wildcards) 정리 (1) | 2025.02.05 |
[JAVA] 제네릭 (Generic) 정리 (1) | 2025.01.26 |