혼공자바] 09-2 익명 객체
09-2 익명 객체
일반적으로 클래스를 선언하고 이를 여러 곳에서 클래스 이름을 호출하며 객체를 만들어 사용한다. 그러나 클래스 이름이 없는 객체가 있다. 바로 그것이 익명(anonymous) 객체.
익명 객체는 클래스를 상속하거나 인터페이스를 구현해야만 만들 수 있다.
일반적인 객체 생성 경우
// 상속
Class 클래스이름1 extends 부모클래스 {...}
부모클래스 변수 = new 클래스이름1();
// 구현
Class 클래스이름2 implements 인터페이스 {...}
인터페이스 변수 = new 클래스이름2();
익명 객체 생성 경우
// 상속
부모클래스 변수 = new 부모클래스() {...}
// 구현
인터페이스 변수 = new 인터페이스() {...}
와 같이 클래스 이름 없이 선언하며
부모클래스 경우는 이름이 없는 자식 객체를 참조하고, 인터페이스 변수는 이름이 없는 구현 객체를 참조한다.
익명 자식 객체 생성
자식 클래스를 명시적으로 선언하는 이유는 어디서건 이미 선언된 자식 클래스로 간단히 객체를 생성해 사용 할 수 있기 때문이다. 그러나 자식 클래스가 재사용 되지 않고, 오로지 특정 위치에서만 사용된다면 다음에 불일 일이 없는 이름없는 익명 자식 객체를 생성해서 사용하여 것이 좋은 방법이다. 익명 객체를 사용함으로써 클래스를 별도로 만들 필요 없이 코드에서 익명객체를 생성/정의해 단 한번 쓰고 소멸하는 것이 유지보수에 이점이 되기도 하지만 대신 이 경우 확장성은 줄어든다.
부모클래스 [필드|변수] = new 부모클래스(매개값, ...) {
// 필드
// 메소드
};
먼저 부모클래스를 선언하여 생성자를 호출하고 실행 블록 내부에서는 필드나 메소드를 선언하거나 부모클래스의 메소드를 재정의하는 내용을 작성한다. 일반 클래스와 달리 생성자는 선언할 수 없다.
- 필드 선언 초기값으로 익명 자식 객체 대입
// 익명 객체 생성해 필드에 대입
class A {
Parent field = new Parent(){
int childField;
void childMethod() { }
@Override
void parentMethod() { }
};
}
// 익명 객체 필드 사용
A a = new A();
A.field.parentMethod();
- 로컬 변수 선언 초기값으로 익명 자식 객체 대입
Class A {
void method(){
Parent localVar = new Parent(){
int childField;
void childMethod() { }
@Override
void ParentMethod() { }
};
}
}
// 익명 객체 로컬 변수 사용
A a = new A();
A.method();
- 메소드 매개 변수가 부모 타입 경우, 메소드 호출 시 익명 자식 객체를 매개값으로 대입
class A {
void method(Person person){ // 메소드의 매개 변수를 부모타입으로 주면
person.wake();
}
}
class B {
A a = new A();
a.method(
new Person(){ // 메소드를 호출하는 코드에서 익명 자식 객체를 생성
void work() {
System.out.println("공부해");
}
@Override // 부모클래스인 Person으로 부터 상속 받은 메소드 재정의
void wake() {
System.out.println("일어났으면");
work();
}
}
);
}
// 일어났으면
// 공부해
익명의 자식 객체에 새롭게 정의된 필드와 메소드는 익명 객체 내부에서만 사용되고, 외부에서는 접근 불가.
왜냐하면 익명 자식 객체는 부모 타입 변수에 대입되므로 부모 타입에 선언된 것만 사용할 수 있어, 자식 객체에 정의한건 사용할 수 없기 때문이다.
익명 구현 객체 생성
구현 클래스를 명시적으로 선언하는 이유 또한 자식 클래스 처럼 어디서건 이미 선언된 자식 클래스로 간단히 객체를 생성해 사용 할 수 있기 때문이다. 그러나 이 역시 재사용 되지 않고, 오로지 특정 위치에서만 사용된다면 다음에 불일 일이 없는 이름없는 익명 구현 객체를 생성해서 사용하여 것이 좋은 방법이다.
인터페이스 [필드|변수] = new 인터페이스 (){
// 인터페이스에 선언된 추상 메소드의 실체 메소드 선언
// 필드
// 메소드
};
위의 선언문은 인터페이스를 구현해 { }와 같이 클래스 선언하라는 뜻이며 이렇게 구현된 클래스를 new연산자가 객체로 생성한다.
+ { }안의 추상메소드는 모두 재정의 되어야하며 재정의되지 않으면 컴파일 에러가 발생한다.
+ { }안의 필드와 메소드를 선언할 수 있지만, 실체 메소드만 외부에서 사용가능하며 { } 안 필드와 메소드는 익명 구현 객체 안에서만 사용될 수 있다.
- 필드 선언 초기값으로 익명 구현 객체를 생성하고 대입
Class A {
RemoteControl field = new RemoteControl(){
@Override
void turnOn(){}
};
}
- 로컬 변수 선언 초기값으로 익명 구현 객체를 생성하고 대입
Class A {
void method() {
RemoteControl field = new RemoteControl(){
@Override
void turnOn(){}
};
}
}
- 메소드 매개 변수가 인터페이스 타입 경우, 메소드 호출 시 익명 구현 객체를 매개값으로 대입
class A {
void method(RemoteControl rc){ // 메소드의 매개 변수를 인터페이스 타입으로 주면
rc.turnOff();
}
}
class B {
A a = new A();
a.method(
new RemoteControl(){ // 메소드를 호출하는 코드에서 익명 자식 객체를 생성
void turnOn() {
System.out.println("어두워 불을 키자");
}
@Override // 부모클래스인 Person으로 부터 상속 받은 메소드 재정의
void turnOff() {
System.out.println("불끄자");
turnOn();
}
}
);
}
// 불끄자
// 어두워 불을 키자
UI프로그램에서 중첩 인터페이스와 익명 객체를 사용한 예제
UI클래스 Button.java
public class Button{
// --------- 필드
OnClickListener listener;
// --------- 메서드 START
// 다양한 구현 객체를 받기위해 인터페이스를 매개 변수로
void setOnClickListener (OnClickListener listener){
this.listener = listener;
}
void touch(){
listener.onClick(); // 구현 객체의 onClick(); 호출
}
// --------- 메소드 END
// --------- 중첩 인터페이스
static interface OnClickListener{
void onClick(); // 추상 메소드
}
}
UI클래스 Window.java
public class Window {
// --------- 필드 START
Button button1 = new Button();
Button button2 = new Button();
// 필드 초기값으로 익명 객체를 대입
Button.OnClickListener listener = new Button.OnClickListener(){
@Override
public void onClick(){
System.out.println("전화를 겁니다.");
}
};
// --------- 필드 END
// --------- 생성자 START
Window() {
button1.setOnClickListener(listener);
button2.setOnClickListener(new Button.OnClickListener() {
@Override
public void onClick(){
System.out.println("메세지 보냅니다.");
}
};
}
// --------- 생성자 END
}
실행클래스 Main.java
public class Main{
public static void main(String[] args){
Window w = new Window();
w.button1.touch();
w.button2.touch();
}
}
// 전화를 겁니다.
// 메세지를 보냅니다.
익명 객체의 로컬 변수 사용
익명 객체는 메소드가 종료되면 사라지는게 일반적이지만, 종료되어도 실행 상태로 존재할 수 있다.
중첩 클래스 편에서 로컬 클래스의 스레드 객체처럼 익명 스레드 객체도 또한 그렇다.
자바는 이런 문제를 해결하기 위해 컴파일 시 익명 객체 내부에서 사용하는 매개 변수나 로컬 번수의 값을 익명 객체 내부에 복사해두고 사용한다.
그러나 매개 변수나 로컬 변수가 수정되면 복사해둔 값과 달라지는 문제가 생겨 자바 8 이후 부터는 매개 변수나 로컬 변수를 익명 객체 내부에서 사용할 때 자동으로 매개 변수나 로컬 변수에 final 속성을 부여하게 된다.