Java 파트9-2. 익명 객체
1. 익명 객체
- 클래스 이름이 없는 객체
- 어떤 클래스를 상속하거나 인터페이스를 구현하여야 함
일반적인 상속의 경우 클래스 선언부에 extends를 사용하고 메인 클래스에서는 부모클래스 변수에 자식 클래스 객체를 대입하는 식으로 사용한다. 하지만 이러한 내용 없이 부모클래스 변수에 부모클래스 생성자 이후에 중괄호를 붙일 경우 이 중괄호는 익명 자식 클래스 선언부를 의미한다. 해석하자면 부모클래스를 상속한 중괄호 같은 익명 자식 클래스 객체를 생성한 뒤에 부모클래스 변수에 대입한다 는 의미이다.
원래 인터페이스는 객체를 생성하지 않으므로 생성자를 가지고 있지 않다. 하지만 위 그림을 보면 생성자 같은 형태를 취하고 있다. 이를 해석하면 새로운(new) 객체를 만드는데 이 객체는 해당 인터페이스를 구현하는 익명 구현 클래스 객체이고 이 객체를 인터페이스 변수에 대입한다는 의미이다.
2. 익명 자식 객체 생성
필드 선언할 때 초기값으로 익명 자식 객체를 생성하여 대입
class A{
// field에는 Parent의 객체 또는 Parent의 자식 객체를 대입 가능하다.
Parent field = new Parent(){
int childField;
void childMethod(){}
@Override // Parent 메소드 재정의
void parentMethod(){}
}
}
메소드 내에서 로컬 변수 선언 시 초기값으로 대입
class A{
void method(){
// field에는 Parent의 객체 또는 Parent의 자식 객체를 대입 가능하다.
// 로컬 변수의 초기값으로 익명 자식 객체 대입
Parent field = new Parent(){
int childField;
void childMethod(){}
@Override // Parent 메소드 재정의
void parentMethod(){}
}
}
}
매개 변수의 매개값으로 익명 자식 객체 생성하여 대입
method2에서 method1을 호출하고 있는데 이때 method1의 매개 변수로는 Parent의 객체 또는 Parent의 자식 객체가 와야한다. 여기에 Parent의 익명 자식 객체를 넣을 수 있다.
익명 자식 객체의 멤버 접근 제한
자식 객체가 부모클래스의 변수에 대입되었기 때문에 부모클래스에 있는 메소드, 필드만 사용할 수 있다. 그렇다면 부모클래스에 없고 자식클래스에 있는 필드와 메소드는 왜 선언했는가? -> 바깥에서는 사용 못하지만 내부에서 사용할 목적으로 선언한다. 즉, new Parent() { } 에서 중괄호에서 사용하게 되는데 대부분 재정의함수 내부에서 호출해서 사용한다.
// 예시
// 부모 클래스
package sec01.exam01;
public class Person {
void wake() {
System.out.println("7시에 기상");
}
}
// 클래스
package sec01.exam01;
public class Anonymous {
// 필드 초기값으로 익명 객체
Person field = new Person() {
void work() {
System.out.println("출근이요");
}
@Override
void wake() {
System.out.println("8시에 기상");
work();
}
};
// 로컬 변수에 익명 객체
void method1() {
Person localVar = new Person() {
void walk() {
System.out.println("산책이요");
}
@Override
void wake() {
System.out.println("7시에 일어남");
walk();
}
};
// localVar은 Person 타입이므로 바로 walk를 호출할수 없다.
// wake는 재정의된 내용이 호출되고 내부에 있는 walk가 호출된다.
localVar.wake();
}
void method2(Person person) {
person.wake();
}
}
// 메인 실행 클래스
package sec01.exam01;
public class AnonymousExam {
public static void main(String[] args) {
Anonymous anony = new Anonymous();
// field는 Person 타입이므로 work는 사용 불가능
// 자식 객체에서 재정의된 wake가 호출됨
anony.field.wake();
anony.method1();
// 익명 자식 객체를 매개 변수로 대입
anony.method2(new Person() {
void study() {
System.out.println("공부중");
}
@Override
void wake() {
System.out.println("8시에 기상");
study();
}
});
}
}
3. 익명 구현 객체 생성
RemoteControl이 인터페이스타입이라고 가정
필드 선언 시 초기값으로 익명 구현 클래스 객체 생성하여 대입하는 경우
메소드 내에서 로컬 변수 선언 시 초기값으로 익명 구현 클래스 객체 생성하여 대입
매개 변수의 매개값으로 익명 구현 클래스 객체를 생성하여 대입
// 예시
// 버튼을 눌렀을 때 작동하는 이벤트를 설계
// 버튼 클래스
package sec01.exam02;
public class Button {
// 버튼안에 중첩 인터페이스를 선언하는 이유는
// 버튼에서 발생하는 어떤 이벤트를 처리하기 위해서
// 구현객체를 만들기때문에 버튼과 매우 밀접하므로
static interface onClickListener{
void onClick();
}
onClickListener listener;
void setonClickListener(onClickListener listener) {
this.listener = listener;
}
// 버튼이 사용자에 의해 클릭되었을때
void touch() {
// 무엇을 할 것인지
listener.onClick();
}
}
// 윈도우 클래스
package sec01.exam02;
public class Window {
// 윈도우에 버튼이 2개 있다
Button button1 = new Button();
Button button2 = new Button();
// 인자가 될 익명 객체
Button.onClickListener listener = new Button.onClickListener() {
// 인터페이스 메소드 재정의
@Override
public void onClick() {
System.out.println("전화를 건다");
}
};
// 버튼이 클릭되면 무엇을 할 것인가
Window(){
button1.setonClickListener(listener);
// 인자로 익명 객체 바로 대입
button2.setonClickListener(new Button.onClickListener() {
// 인터페이스 메소드 재정의
@Override
public void onClick() {
System.out.println("메시지를 보낸다.");
}
});
}
}
// 실행 클래스
package sec01.exam02;
public class WindowExam {
public static void main(String[] args) {
Window w = new Window();
w.button1.touch();
w.button2.touch();
}
}
어떤 이벤트를 처리할 목적으로 설계할 경우, 어떤 객체의 이벤트를 처리할 때 사용할 인터페이스를 선언하고 인터페이스 타입의 필드를 선언하고(보통 private) 이 필드에 각각 대입을 위해 setter메소드(보통 public) 선언과 어떤 사건,실행(클릭)이 되었을 때 해결할 처리 메소드를 선언한다. 그리고 이 클래스를 사용할 다른 클래스를 만들어 객체를 생성하고 실질적으로 어떤 일이 발생할지에 대한 내용을 채워준다. 실행 메인 클래스에서는 그 상황이 발생하도록 해주면 된다.
4. 익명 객체의 로컬 변수 사용
- 메소드의 매개 변수나 로컬 변수를 익명 객체 내부에서 사용할 때의 제한
- 메소드가 종료되어도 익명 객체가 계속 실행 상태로 존재할 수 있음
- 컴파일 시 익명 객체에서 사용하는 매개 변수나 로컬 변수는 final 특성을 가짐
메소드가 끝나면 안에서 선언된 로컬 변수들은 다 사라진다. 하지만 익명 객체는 힙 영역에 계속 남아있다. 그런데 메소드가 끝나고 나서 로컬 변수와 매개 변수 값이 사라져버리면 익명 객체 내부에서 사용했던 값들에서 오류가 발생 할 수 있다. 그래서 자바는 로컬 변수와 매개 변수값을 익명 객체 안에서 사용할 때는 그 값을 익명 객체 내부에 저장해두고 사용한다. 여기서 바깥의 값과 익명 객체 내부의 값은 같은 값을 가져야하는데 그것을 수정해도 안에 있는 값은 수정이 안된다고 자바는 가정하기 때문에 자바는 이 변수의 값들을 바꿀 수 없도록 final특성으로 관리한다. 따라서 메소드의 매개 변수와 로컬 변수는 final 특성을 가지므로 수정할 수 없다.
5. 정리하기
- 익명 자식 객체 : 자식 클래스가 재사용되지 않고 오로지 특정한 위치에서 사용되는 경우에 사용하면 편리
- 부모클래스 변수 = new 부모클래스() {}
- 익명 구현 클래스 객체 : 구현 클래스 객체가 재사용되지 않고 오로지 특정 위치에서 사용되는 경우에 사용하면 편리
- 인터페이스 변수 = new 인터페이스() {}
본 포스팅은 ‘혼자 공부하는 자바’를 읽고 공부한 내용을 바탕으로 작성하였습니다.