Java 파트9-2. 익명 객체

1. 익명 객체


  • 클래스 이름이 없는 객체
  • 어떤 클래스를 상속하거나 인터페이스를 구현하여야 함

그림1

일반적인 상속의 경우 클래스 선언부에 extends를 사용하고 메인 클래스에서는 부모클래스 변수에 자식 클래스 객체를 대입하는 식으로 사용한다. 하지만 이러한 내용 없이 부모클래스 변수에 부모클래스 생성자 이후에 중괄호를 붙일 경우 이 중괄호는 익명 자식 클래스 선언부를 의미한다. 해석하자면 부모클래스를 상속한 중괄호 같은 익명 자식 클래스 객체를 생성한 뒤에 부모클래스 변수에 대입한다 는 의미이다.

그림2

원래 인터페이스는 객체를 생성하지 않으므로 생성자를 가지고 있지 않다. 하지만 위 그림을 보면 생성자 같은 형태를 취하고 있다. 이를 해석하면 새로운(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(){}
        }
    }   
}

매개 변수의 매개값으로 익명 자식 객체 생성하여 대입

그림3

method2에서 method1을 호출하고 있는데 이때 method1의 매개 변수로는 Parent의 객체 또는 Parent의 자식 객체가 와야한다. 여기에 Parent의 익명 자식 객체를 넣을 수 있다.

익명 자식 객체의 멤버 접근 제한


그림4

자식 객체가 부모클래스의 변수에 대입되었기 때문에 부모클래스에 있는 메소드, 필드만 사용할 수 있다. 그렇다면 부모클래스에 없고 자식클래스에 있는 필드와 메소드는 왜 선언했는가? -> 바깥에서는 사용 못하지만 내부에서 사용할 목적으로 선언한다. 즉, 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이 인터페이스타입이라고 가정

필드 선언 시 초기값으로 익명 구현 클래스 객체 생성하여 대입하는 경우

그림5

메소드 내에서 로컬 변수 선언 시 초기값으로 익명 구현 클래스 객체 생성하여 대입

그림6

매개 변수의 매개값으로 익명 구현 클래스 객체를 생성하여 대입

그림7

// 예시
// 버튼을 눌렀을 때 작동하는 이벤트를 설계
// 버튼 클래스 
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 특성을 가짐

그림8

메소드가 끝나면 안에서 선언된 로컬 변수들은 다 사라진다. 하지만 익명 객체는 힙 영역에 계속 남아있다. 그런데 메소드가 끝나고 나서 로컬 변수와 매개 변수 값이 사라져버리면 익명 객체 내부에서 사용했던 값들에서 오류가 발생 할 수 있다. 그래서 자바는 로컬 변수와 매개 변수값을 익명 객체 안에서 사용할 때는 그 값을 익명 객체 내부에 저장해두고 사용한다. 여기서 바깥의 값과 익명 객체 내부의 값은 같은 값을 가져야하는데 그것을 수정해도 안에 있는 값은 수정이 안된다고 자바는 가정하기 때문에 자바는 이 변수의 값들을 바꿀 수 없도록 final특성으로 관리한다. 따라서 메소드의 매개 변수와 로컬 변수는 final 특성을 가지므로 수정할 수 없다.


5. 정리하기


  • 익명 자식 객체 : 자식 클래스가 재사용되지 않고 오로지 특정한 위치에서 사용되는 경우에 사용하면 편리
    • 부모클래스 변수 = new 부모클래스() {}
  • 익명 구현 클래스 객체 : 구현 클래스 객체가 재사용되지 않고 오로지 특정 위치에서 사용되는 경우에 사용하면 편리
    • 인터페이스 변수 = new 인터페이스() {}



본 포스팅은 ‘혼자 공부하는 자바’를 읽고 공부한 내용을 바탕으로 작성하였습니다.


© 2021. By Backtony