[Java] 9-2 익명 객체

Woohyun Shin·2021년 8월 9일
0

Java

목록 보기
7/10

익명(Anonymous)객체는 이름이 없는 객체를 말한다.

익명 객체를 만들기 위해서는 어떤 클래스를 상속하거나 인터페이스를 구현해야만 한다.

일반적인 경우에는 다음과 같이 명시적으로 클래스 이름을 주고 선언한다.

class 클래스이름1 extends 부모클래스{...} //상속

부모클래스 변수 = new 클래스이름1();
class 클래스이름2 implements 인터페이스{...} //구현

인터페이스 변수 = new 클래스이름2();

이 경우 부모 클래스 변수는 클래스이름1의 객체를 참조하고, 인터페이스 변수는 클래스이름2의 객체를 참조한다.

그러나 익명 객체를 생성할 때에는 다음과 같이 클래스 이름이 없다.

부모클래스 변수 = new 부모클래스(){...}; //상속
인터페이스 변수 = new 인터페이스(){...}; //구현

이 경우 부모 클래스 변수는 이름이 없는 자식 객체를 참조하고, 인터페이스 변수는 이름이 없는 구현 객체를 참조하게 된다.

익명 자식 객체 생성

일반적으로 부모 클래스를 상속한 자식 클래스를 선언한 뒤, new 연산자를 이용해서 자식 객체를 생성하고 부모 타입의 필드 또는 변수에 대입하는 것이 일반적이다.

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

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

자식 클래스를 명시적으로 선언하는 이유는 어디서건 이미 선언된 자식 클래스로 간단하게 객체를 생성해서 사용할 수 있기 때문이다.(재사용성이 높다)

그러나 자식 클래스가 재사용되지 않고, 오로지 특정 위치에서 사용할 경우라면 자식 클래스를 명시적으로 선언하는 것보다 익명 자식 객체를 생성해서 사용하는 것이 더 좋은 방법이다.

부모 클래스[필드|변수]=new 부모 클래스(매개값, ...){
//필드
//메소드
};

익명 자식 객체를 생성할때에는 하나의 실행문이기 때문에 끝에는 세미콜론(;)을 꼭 붙여야 한다.

'부모 클래스(매개값, ...){...}'은 부모 클래스를 상속해서 중괄호 {}와 같은 자식 클래스를 선언하라는 뜻이고, new 연산자는 이렇게 선언된 익명 자식 클래스를 객체로 생성한다.

'부모 클래스(매개값, ...)'은 부모 생성자를 호출하는 코드로 매개값은 부모 생성자의 매개 변수에 맞게 입력한다.

중괄호 {}내부에는 필드나 메소드를 선언하거나, 부모 클래스의 메소드를 재정의(Overriding)하는 내용을 작성하는데 일반적으로 재정의 메소드가 많이 나온다.

일반 클래스와의 차이점은 생성자를 선언할 수 없다는 점이다.

class A{
//필드를 선언할 때 초기값으로 익명 자식 객체를 생성해서 대입하는 예
	Parent field = new Parent(){ //A클래스 필드 선언
		int childField;
		void childMethod(){}
		@Override //Parent의 메소드 재정의
		void parentMethod(){}
	};

}
class A{
//메소드 내에서 로컬 변수를 선언할 때 초기값으로 익명 자식 객체를 생성해서 대입하는 예
	void method(){
		Parent localVar = new Parent(){ //로컬 변수 선언
		int childField;
		void childMethod(){}
		@Override //Parent의 메소드 재정의
		void parentMethod(){}
		};
	}
}
class A{
//메소드의 매개 변수가 부모 타입일 경우 메소드를 호출하는 코드에서 익명 자식 객체를 생성해서 매개값으로 대입하는 예
void method1(Parent parent){}

void method2(){
method1( //method1() 메소드 호출
	new Parent(){ //method1()의 매개값으로 익명 자식 객체 대입
    	int childField;
    	void childMethod(){}
    	@Override
    	void parentMethod(){}
    	}
	 );

	}

}

익명 자식 객체에 새롭게 정의된 필드와 메소드는 익명 자식 객체 내부에서만 허용되고 외부에서는 접근할 수 없다.(익명 자식 객체가 부모 타입 변수에 대입되므로 부모 타입에 선언된 멤버만 사용할 수 있음)

childField 필드와 childMethod() 메소드는 parentMethod() 메소드 내에서는 사용할 수 있지만 A 클래스의 필드인 field로는 접근할 수 없다.(외부에서 접근X)

package sec03.exam02;

public class Parson {
	
	void wake() {
		System.out.println("7시에 일어납니다.");
	}

}
package sec03.exam02;

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();
	}
}
package sec03.exam02;

public class AnonymousExample {

	public static void main(String[] args) {
		// TODO 자동 생성된 메소드 스텁
		
		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();
			}
		}
		
		);
	}
}

6시에 일어납니다.
출근합니다.
7시에 일어납니다.
산책합니다.
8시에 일어납니다.
공부합니다.

익명 구현 객체 생성

일반적으로 인터페이스 타입의 필드 또는 변수를 선언하고 구현 객체를 초기값으로 대입하는 경우, 먼저 구현 클래스를 선언하고 new 연산자를 이용해서 구현 객체를 생성한 후에 인터페이스 타입의 필드 또는 로컬 변수에 대입한다.

class TV implements RemoteControl{}

class A{

	RemoteControl field = new TV(); //필드에 구현 객체를 대입
	void method(){
    RemoteControl localVar = new TV(); //로컬 변수에 구현 객체를 대입
	}

}

이 또한 구현 클래스를 명시적으로 선언하는 이유는 어디서건 이미 선언된 구현 클래스로 가단히 객체를 생성할 수 있기 때문이다.(재사용성)

따라서 구현 클래스를 재사용하지 않고 특정 위치에서만 사용할 경우에는 익명 구현 객체를 생성해서 사용하는 것이 좋은 방법이다.

인터페이스 [필드|변수] = new 인터페이스() {
//인터페이스에 선언된 추상 메소드의 실체 메소드 선언
//필드
//메소드
};

'인터페이스(){...}'는 인터페이스를 구현해서 중괄호 {}와 같이 클래스를 선언하라는 뜻이고 new 연산자는 이렇게 선언된 구현 클래스를 객체로 생성한다.

중괄호 {}에는 인터페이스에 선언된 모든 추상 메소드의 실체 메소드를 작성(재정의)해야 한다.

추가로 필드와 메소드를 선언할 수 있지만, 실체 메소드에서만 사용이 가능하고 외부에서는 사용할 수 없다.

class A{
//필드를 선언할 때 초기값으로 익명 구현 객체를 생성해서 대입하는 예
	RemoteControl field = new RemoteControl(){ //클래스 A의 필드 선언
    	@Override //RemoteControl 인터페이스의 추상 메소드에 대한 실체 메소드
        void turnOn(){}
    };
}
void method(){
//메소드 내에서 로컬 변수를 선언할 때 초기값으로 익명 구현 객체를 생성해서 대입하는 예
	RemoteControl localVar = new RemoteControl(){ //로컬 변수 선언
    	@Override //RemoteControl 인터페이스의 추상 메소드에 대한 실체 메소드
        void turnOn(){}
    };
}
class A{
	void method1(RemoteControl rc){}
    
    void method2(){
    	method1( //method1() 메소드 호출
    		new RemoteControl(){ //method1()의 매개값으로 익명 구현 객체를 대입
            	@Override
                void turnOn(){}
            }
        );
    }
}
package sec03.exam02;

public interface RemoteControl {
	//추상 메소드
	public void turnOn();
	public void turnOff();

}
package sec03.exam02;

public class Anonymous {
	//필드 초기값으로 대입
	RemoteControl field = new RemoteControl() { //필드 선언과 초기값 대입
		@Override
		public void turnOn() {
			System.out.println("TV를 켭니다.");
		}
		@Override
		public void turnOff() {
			System.out.println("TV를 끕니다.");
		}
		
	};
	
	void method1() {
		//로컬 변수값으로 대입
		RemoteControl localVar = new RemoteControl() {
			@Override
			public void turnOn() {
				System.out.println("Audio를 켭니다.");
			}
			@Override
			public void turnOff() {
				System.out.println("Audio를 끕니다.");
			}
		};
		localVar.turnOn();
	}
	
	void method2(RemoteControl rc) {
		rc.turnOn();
	}
}
package sec03.exam02;

public class AnonymousExample {

	public static void main(String[] args) {
		// TODO 자동 생성된 메소드 스텁
		
		Anonymous anony = new Anonymous();
		//익명 객체 필드 사용
		anony.field.turnOn();
		//익명 객체 로컬 변수 사용
		anony.method1();
		//익명 객체 매개값 사용
		anony.method2(
		new RemoteControl() { //매개값
			@Override
			public void turnOn() {
				System.out.println("SmartTV를 켭니다.");
			}
			@Override
			public void turnOff() {
				System.out.println("SmartTV를 끕니다.");
			}
		}
		
		);
	}
}

TV를 켭니다.
Audio를 켭니다.
SmartTV를 켭니다.

다음은 윈도우 및 안드로이드 등의 UI프로그램에서 버튼의 클릭 이벤트를 처리하기 위해 익명 구현 객체를 이용하는 방법이다.

package sec03.exam02;

public class Button {

	OnClickListener listener; //인터페이스 타입 필드
	
	void setOnClickListener(OnClickListener listener) { //매개 변수의 다형성
		this.listener=listener;
	}
	
	void touch() {
		listener.onClick(); //구현 객체의 onClick() 메소드 호출
	}
	
	static interface OnClickListener{ //중첩 인터페이스
		void onClick();
	}
}

Button 클래스의 내용을 보면 중첩 인터페이스(OnClickListener) 타입으로 필드(listener)를 선언하고 Setter 메소드(setOnClickListener())로 외부에서 구현 객체를 받아 필드에 대입한다.

버튼 이벤트가 발생했을 때(touch() 메소드가 실행되었을 때) 인터페이스를 통해 구현 객체의 메소드를 호출(listener.onClick())한다.

package sec03.exam02;

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("메세지를 보냅니다.");
			}
		});
	}
}
package sec03.exam02;

public class Main {

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

}

전화를 겁니다.
메세지를 보냅니다.

익명 객체의 로컬 변수 사용

메소드의 매개 변수나 로컬 변수를 익명 객체 내부에서 사용할 때도 제한이 있다.

익명 객체는 메소드 실행이 종료되면 없어지는 것이 일반적이지만, 메소드가 종료되어도 계속 실행 상태로 존재할 수 있다.

메소드의 매개 변수나 로컬 변수를 익명 객체 내부에서 사용할 때 매개 변수나 로컬 변수는 메소드 실행이 끝나면 스택 메모리에서 사라지기 때문에 익명 객체에서 지속적으로 사용할 수 없다.

결론 : 매개 변수와 로컬 변수는 익명 객체 내부에서 사용할 때 final 특성을 갖고 있다.

package sec03.exam02;

public interface Calculatable {

	public int sum();
	
}
package sec03.exam02;

public class Anonymous {
	private int field;
	
	public void method(final int arg1,int arg2) {
		final int var1=0;
		int var2=0;
		
		field=10;
		
		//arg1=20; (X)
		//arg2=20; (X)
		
		//var1=30; (X)
		//var2=30; (X)
		
		Calculatable calc = new Calculatable() {
			@Override
			public int sum() {
				int result = field+arg1+arg2+var1+var2;
				return result;
			}
		};
		
		System.out.println(calc.sum());
	}
}
package sec03.exam02;

public class AnonymousExample {

	public static void main(String[] args) {
		Anonymous anony = new Anonymous();
		anony.method(0,0);
	}
}

10

< 이번 파트 문제를 풀면서 고칠 점 or 아쉬웠던 점 >

1.익명 객체를 생성할 때 마지막에 세미콜론(;) 붙여주는 것 잊지말자.
2.인터페이스의 모든 메소드는 무조껀 public 키워드를 붙여주자.(자꾸 까먹는데 잊어버리지말자)
3.중첩 인터페이스를 구현할 때는 인터페이스 이름 앞에 바깥클래스. 를 붙여주자.(자꾸 까먹는데 잊어버리지말자)

profile
조급함보다는 꾸준하게

0개의 댓글