KoreanFoodie's Study

9-2. 자바 익명 객체 : 익명 자식 객체 생성과 구현 본문

Tutorials/Java

9-2. 자바 익명 객체 : 익명 자식 객체 생성과 구현

GoldGiver 2021. 9. 18. 17:48

익명 객체

익명(anonymous) 객체는 이름이 없는 객체를 말한다. 익명 객체는 단독으로 생성할 수 없고 클래스를 상속하거나 인터페이스를 구현해야만 생성할 수 있다. 익명 객체는 필드의 초기값이나 로컬 변수의 초기값, 매개 변수의 매개값으로 주로 대입된다. UI 이벤트 처리 객체나 스레드 객체를 간편하게 생성할 목적으로 익명 객체가 많이 활용된다.

 

익명 자식 객체 생성

부모 타입으로 필드나 변수를 선언하고, 자식 객체를 초가값으로 대입할 경우를 생각해보자. 우선 부모 클래스를 상속해서 자식 클래스를 선언하고, new 연산자를 이용해서 자식 객체를 생성한 후, 필드나 로컬 변수에 대입하는 것이 기본이다.

class Child extends Parent { } // 자식 클래스 선언

class A
{
    Parent field = new Child(); // 필드에 자식 객체를 대입
    void method()
    {
    	Parent localVar = new Child(); // 로컬 변수에 자식 객체를 대입
    }


}

그러나 자식 클래스가 재사용되지 않고, 오로지 해당 필드와 변수의 초기값으로만 사용할 경우라면 익명 자식 객체를 생성해서 초기값으로 대입하는 것이 좋은 방버이다. 익명 자식 객체를 생성하는 방법을 다음과 같다. 주의할 점은 하나의 실행문이므로 끝에는 세미콜론(;)을 반드시 붙여야 한다.

Class A
{
    Parent field = new Parent()
    {
    	int childField;
        void childMethod() {}
        @Override
        void parentMethod() {}
    };
}

즉, 이 경우에는 부모클래스인 Parent를 상속해서 중괄호 { }; 와 같이 자식 클래스를 선언하라는 뜻이고, new 연산자는 이렇게 선언된 자식클래스를 객체로 생성한다! 일반 클래스와의 차이점은 생성자를 선언할 수 없다는 것이다.

메소드 내에서 로컬 변수를 선언할 때 초기값으로 익명 자식 객체를 생성해서 대입할 수도 있다. 또한 메소드의 매개 변수가 부모 타입일 경우 메소드 호출 코드에서 익명 자식 객체를 생성해서 매개값으로 대입할 수도 있다.

public class B
{
	void method1(Parent parent) {}
	void method2()
	{
		method1(

		    new Parent()
		    {
		    	int childField;
		        void childMethod() {}
		        @Override
		        void parentMethod() {}
		    }
		);
		
	}
}

익명 자식 객체에 새롭게 정의된 필드와 메소드는 익명 자식 객체 내부에서만 사용되고, 외부에서는 필드와 메소드에 접근할 수 없다. 왜냐하면 익명 자식 객체는 부모 타입 변수에 대입되므로 부모 타입에 선언된 것만 사용할 수 있기 때문이다. 

public class B
{
    Parent field = new Parent()
    {
    	int childField;
        void childMethod() {}
        @Override
        void parentMethod() {}
    };
    
    void method()
    {
    	field.childField = 3; // 부모 타입에 선언되지 않아 사용 불가
    	field.childMethod(); // 부모 타입에 선언되지 않아 사용 불가
    	field.parentMethod(); // 부모 타입에 선언되어 사용 가능!
    }
}

다음은 예시를 보자.

public class Person {
	void wake()
	{
		System.out.println("7시에 일어납니다.");
	}
}
public class Anonymous {
	// 필드 초기값으로 대입
	Person field = new Person()
	{
		void work()
		{
			System.out.println("출근합니다.");
		}
		
		@Override
		void wake()
		{
			System.out.println("6시에 일어납니다.");
			work();
		};
	};
	
	void method1()
	{
		// 로컬 변수값으로 대입
		Person localVar = new Person()
		{
			void walk()
			{
				System.out.println("산책합니다.");
			}
			@Override
			void wake()
			{
				System.out.println("7시에 일어납니다.");
				walk();
			}
		};
		
		
		// 로컬 변수 사용
		localVar.wake();
	}

	void method2(Person person)
	{
		person.wake();
	}
	
}
public class AnonymousExample {
	
	public static void main(String[] args)
	{
		Anonymous anony = new Anonymous();
		
		// 익명 객체 필드 사용
		anony.field.wake();
		
		// 익명 객체 로컬 변수 사용
		anony.method1();
		
		// 익명 객체 매개값 사용
		anony.method2
		(
			new Person()
			{
				void study()
				{
					System.out.println("공부합니다.");
				}
				@Override
				void wake()
				{
					System.out.println("8시에 일어납니다.");
					study();
				}
			}
		);
	}

}

익명 구현 객체를 생성(interface를 이용해서)할 때에도, 익명 자식 객체를 생성할 때와 비슷하다. 구체적인 예시를 한 번 보자. 앞서 사용했던 Button 클래스를 재활용 하도록 하겠다.

public class Button {
	
	OnClickListener listener;
	
	// 매개 변수의 다형성
	void setOnClickListener(OnClickListener listener)
	{
		this.listener = listener;
	}
	
	// 구현 객체의 onClock() 메소드 호출
	void touch()
	{
		listener.onClick();
	}
	
	// Nested interface
	interface OnClickListener
	{
		void onClick();
	}
	
}
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("메세지를 보냅니다.");
			}
		});
	}
	
}
public class Main {

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

실행 결과는 아래와 같다!

 

익명 객체의 로컬 변수 사용

익명 객체 내부에서는 바깥 클래스의 필드나 메소드는 제한 없이 사용할 수 있다. 문제는 메소드의 매개 변수나 로컬 변수를 익명 개체에서 사용할 때이다. 메소드 내에서 생성된 익명 객체는 메소드 실행이 끝나도 힙 메모리에 존재해서 계속 사용할 수 있다. 매개 변수나 로컬 변수는 메소드 실행이 끝나면 스택 메모리에서 사라지기 때문에 익명 객체에서 사용할 수 없게 되므로 문제가 발생한다.

이 문제에 대한 해결 방법은 로컬클래스의 사용제한과 비슷하다. (final 키워드 관련) 우리는 익명 클래스의 내부 복사 위치에 신경 쓸 필요 없이 익명 객체에서 사용된 매개 변수와 로컬 변수는 모두 final 특성을 갖는다는 것만 알면 된다. 


해당 포스팅은 이것이 자바다 (신용권 저) 를 참고하였습니다

 

Comments