익명 클래스(익명 객체)
이번 포스팅에서는 익명 클래스/객체에 대해 알아볼 것입니다. 목차는 아래와 같습니다.
목차
- 익명 클래스/객체의 정의와 사용하는 이유
- 익명 클래스/객체를 어떻게 사용하는가?
- 익명 클래스/객체를 사용하는 두가지 상황(Interface 구현, 추상 클래스 상속)과 주의 사항
- 일회성으로 사용되는 기능을 '일반클래스의 인스턴스로 생성한 것' => '익명 클래스/객체로 변환'하는 예제
익명 클래스/객체?
익명 클래스는 내부 클래스이자 이름이 없는 "익명" 클래스입니다.
즉, 이름이 없는 일회용 클래스로 클래스 정의와 인스턴스(객체) 생성을 동시에 할 수 있습니다.
익명 클래스(익명 객체)는
인터페이스를 구현하는 클래스 혹은 부모 클래스를 상속받는 클래스 중 일회성에 그치는 클래스를
이름이 없는 클래스로 생성하고 동시에 객체 정의까지 할 수 있는 클래스이자 객체를 의미합니다.
(그래서 익명 객체라고도 부르고, 익명 클래스라고도 부르는 것이라 생각합니다.)
익명 클래스/객체를 사용하는 이유?
일시적으로 한번만 사용되는 객체를 위해 클래스를 생성하고 인스턴스를 생성하는 것은 보기에도 "비효율적"인 것 같습니다.
재사용성도 떨어지죠!
이렇게 일시적으로만 사용하는 기능의 경우, 클래스 생성과 동시에 객체 정의를 하는 익명 클래스/객체를 사용합니다.
이를 통해 유지보수에도 더 유리해집니다.
Runnable이나 Event Listener 객체를 생성하는데 주로 사용된다고 합니다.
익명 클래스/객체를 어떻게 생성할까요?
우선 기존 클래스를 정의하고 인스턴스를 생성하는 방법은 아래와 같습니다.
class MyClass extends Object {
// 생략
}
MyClass myClass = new MyClass();
그런데 여기서 클래스 자신의 이름이 없는 거지요. 그래서 자신의 이름 대신 조상의 이름을 적어줍니다.
그리고 재정의해야할 클래스 내용을 { } 안에 적습니다.
new 조상클래스이름() {
// 구현 및 재정의
}
new 구현인터페이스이름() {
// 구현 및 재정의
}
익명 클래스/객체는 1. 인터페이스를 구현받아서도, 2.클래스를 상속받아서 생성할 수 있습니다. 하나씩 살펴보겠습니다.
1. Interface 구현과 객체 생성 동시에
예제로 확인해보겠습니다. 다음과 같은 인터페이스가 있습니다.
public interface MyInterface {
public abstract void doSomething();
}
위 인터페이스를 구현해서 일회성의 기능이 필요할 때,
인터페이스 MyInterface를 구현하는 익명 객체를 정의해줍시다.
이 객체를 한번 더 사용하려면 다시 아래 코드를 작성해주어야합니다.
public class AnonymousClassTest {
public static void main(String[] args) {
MyInterface anonymousClass = new MyInterface() {
@Override
public void doSomething() {
System.out.println("MyInterface를 구현한 익명 객체에서 doSomething을 오버라이딩!");
}
};
}
}
2. 추상 클래스 구현과 객체 생성 동시에
public abstract class MyClass {
public abstract void doSomething();
}
클래스를 상속하는 익명 객체를 정의해줍시다. 이 객체를 한번 더 사용하려면 다시 아래 코드를 작성해주어야합니다.
MyClass anonymousFromClass = new MyClass() {
public void innerMethod() {
System.out.println("MyClass를 상속한 익명 객체에서 innerMethod 추가도 가능합니다!");
}
@Override
public void doSomething() {
System.out.println("MyClass를 상속한 익명 객체에서 doSomething을 오버로딩!");
}
};
여기서, 주의사항 몇가지를 코드와 함께 보겠습니다.
public class AnonymousObject {
static MyClass anonymousFromClass = new MyClass() {
int innerField = 0; // OK
static final int OUTER_NUM = 0; // OK
static int innerStaticNum = 0; // Failed
public void innerMethod() {
System.out.println("MyClass를 상속한 익명 객체에서 innerMethod 생성!");
}
@Override
public void doSomething() {
System.out.println("MyClass를 상속한 익명 객체에서 doSomething을 오버로딩!");
}
};
public static void main(String[] args) {
anonymousFromClass.doSomething(); // 접근 가능
// anonymousFromClass.innerMethod(); // 접근 불가
// anonymousFromClass.innerField = 2; // 접근 불가
}
}
알아둘 점
1. 익명 클래스도 inner class이기 때문에 익명 클래스 내에서 정적 변수를 선언할 수 없습니다.
Static declarations in inner classes are not supported at language level '11'
2. 멤버 변수나 상수(static final)는 선언 가능합니다.
3. 익명 객체에서 새롭게 정의한 필드와 메소드는 해당 객체 레벨에서만 사용되기 때문에 익명 클래스 밖에서는 해당 멤버 변수에 접근할 수 없습니다. ex) innerMethod(), innerField
이제 일회성의 기능을 위해 클래스와 인스턴스를 생성하는 코드를 익명 클래스로 바꿔봅시다.
일회성의 일반 클래스를 익명 클래스/객체로 바꾸는 예제
특정 인터페이스를 구현하는 클래스가 일회성으로만 사용된다했을 때, 이를 익명 클래스로 바꿔주는 것이 유지보수에도 더 좋다고 위에서 언급했었죠!
여기서 ActionListener 인터페이스를 구현하는 EventHandler는 일회성으로 사용됩니다. 이를 익명 클래스로 바꿔봅시다.
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
// 일회성으로 쓰이는 클래스
class EventHandler implements ActionListener {
public void actionPerformed(ActionEvent e) {
System.out.println("ActionEvent occurred!!");
}
}
public class Test {
public static void main(String[] args) {
Button b = new Button("Start");
// 1. EventHandler클래스 객체 생성 (익명 클래스로 변환하기 전)
b.addActionListener(new EventHandler());
// 2. 인터페이스 ActionListener 구현과 동시에 내부에서 메서드를 오버라이드
b.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("익명클래스로 ActionListener인터페이스의 메서드 actionPerformed 실행!!");
}
});
}
}
추가로, 위 클래스를 컴파일했을 때 어떤 파일이 생성될까요?
익명 클래스에 대한 파일이 각각 생성됩니다. 익명 클래스를 컴파일한 파일명은 아래와 같이 순번으로 나타납니다.
- AnonymousObject.class
- AnonymousObject$1.class (외부클래스$내부클래스.class)
- AnonymousObject$2.class