본문 바로가기

JAVA/혼공자

[혼공자] Chapter 09-2 익명 객체

▶ 익명 객체란
정의 : 이름이 없는 객체
조건 : 어떤 클래스를 상속하거나 인터페이스를 구현해야 한다.
종류 : 어떤 클래스를 상속하는 익명 상속 객체, 어떤 인터페이스를 구현하는 익명 구현 객체
사용하는 이유 : 일반적으로 클래스를 이름과 함께 정의하고 생성자로 해당 클래스의 객체를 만들어 사용한다. 
클래스에 이름을 정의하는 이유는 어디에서건 클래스의 이름과 동일한 생성자를 호출하여 객체를 만들기 위함이다. 
하지만, 특정 위치에서만 사용하는 클래스라면, 위의 기능이 필요하지 않으므로 
이름을 명시하는 것이 필요 없는 작업이 된다. 이런 경우 익명 객체를 생성하는 것이 효율적이다.
익명 객체를 사용하는 목적 : 한 번만 사용할 자식 객체, 구현 객체를 만들기 위해
사용 방법 : new와 생성자를 이용해 객체를 생성한 후, 부모 객체나 인터페이스 변수에 대입하여 사용한다.
=> 자동 형변환 이루어짐

▶ 익명 자식 객체 생성
문법 : 부모클래스 [필드 or 변수] = new 부모클래스 (매개값, ...) { ... }
새로운 부모 객체를 생성하는 명령문 뒤에 { ... }만 붙이면 된다.

부모 클래스를 상속해서 { ... } 내용으로 자식 클래스를 선언하라는 내용이다. 
중괄호 안에는 필드, 메소드, 부모 메소드를 오버라이딩하는 메소드이 나온다. 
익명 자식 객체의 필드나 메소드를 적고 내부에서 사용할 수는 있지만, 자동 형변환의 특성 때문에 외부에서는 사용할 수는 없다. 
(외부에서는 부모의 멤버 or 오버라이딩된 메소드만 사용할 수 있으므로!) 
또한, 이름이 없으므로 생성자도 선언할 수 없다.

정리 : 오버라이딩, 자동 형변환 등의 특성을 보면 '자식'의 개념은 맞지만, 
막상 사용할 때의 의미는 '부모의 일부 메소드가 수정된 객체를 만들 때 사용'한다고 이해하면 될 것 같다.


▶ 익명 구현 객체 생성
인터페이스를 구현하는 익명 구현 객체 또한, 위에서 배운 익명 상속 객체와 같은 문법으로 사용하면 된다.
문법 : 인터페이스 [필드 or 변수] = new 인터페이스 ( ) { ... }
인터페이스는 생성자도 없고 생성자의 매개변수도 없으므로 소괄호 안을 비워둬야 한다.
익명 구현 객체는 한 번만 사용하긴 하지만, '인터페이스의 구현 객체' 이므로, 
인터페이스 내부의 모든 추상 메소드를 중괄호 안에서 재정의 해줘야 한다.

Button : 클래스
OnClickListener : Button 안의 인터페이스 (중첩 인터페이스)
setOnClickListener : Button의 필드 listener에 구현 객체 저장
onClick : 인터페이스의 추상 메소드 / setOnClickListener에 구현객체 넘어가기 전에 or 넘어가면서 정의됨
touch : 인터페이스의 추상 메소드 onClick 호출

Button.java

public class Button {
	// 중첩 인터페이스
	static interface OnClickListener{
		// 추상메소드
		void onClick();
	}
	
	// 인터페이스 타입 필드
	OnClickListener listener;
	
	// 인터페이스 변수를 외부에서 받은 구현 객체로 초기화하는 메소드
	void setOnClickListener(OnClickListener listener) {
		this.listener = listener;
	}
	
	// 인터페이스의 메소드를 호출하는 함수
	void touch() {
		listener.onClick();
	}
}

 

Window.java

public class Window {
	// 필드
	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("메세지를 보냅니다.");
			}
		});
	}
	// 이전 챕터에서는 구현 클래스를 다른 파일로 분리해서 만들었었는데
	// 익명 구현객체를 사용하니 파일 구성이 더 단순해짐
}


Main.java

public class Main {
	public static void main(String[] args) {
		Window w = new Window();
		w.button1.touch();
		w.button2.touch();
	}
}




▶ 익명 객체의 로컬 변수 사용
메소드의 로컬 변수나 매개변수를 익명 객체에서 사용할 때 제한이 있다. 
배경 : 메소드가 종료되어도 익명객체는 실행 상태로 존재할 수 있다. (Ex. 스레드) 
문제 : 메소드가 종료되면, 로컬 변수나 매개변수가 스택에서 사라지는데, 이렇게 되면 익명 객체가 이를 사용할 수 없는 에러가 발생한다. 
해결 : 자바는 컴파일 시 익명 객체에서 사용하는 매개변수나 로컬 변수의 값을 익명 객체 내부에 복사해두어 사용한다.
해결'S 문제 : 컴파일 시 미리 복사해둔 값이 있는데, 실행하며 값이 달라지게 되면, 의도했던 것과 다른 결과가 나올 수 있다. (예를 들어, 매개변수에 4를 넘기고, 2를 추가하여 6이 익명 객체에 전달하는데, 컴파일할 때는 4를 저장한 상태이므로 개발자가 의도한 결과가 나오지 않는 오류 발생!) 
해결 : 익명 객체에서 사용하는 로컬 변수나 매개변수에 final 특성을 부여한다. 
즉, 익명 객체에서 사용하는 로컬 변수나 매개변수의 값을 수정하는 코드가 있으면 오류가 발생한다.