본문 바로가기

Programming/JAVA

인터페이스(5) - 인터페이스 구현 : 익명 객체의 구현

java logo image

 

 

 

앞선 아티클에서 우리는 RemoteControl이라는 인터페이스를 생성하고, 이를 구현하는 Television과 Audio라는 구현 클래스를 만들었습니다. 설명했다시피, 이 인터페이스를 구현하는 구현 클래스를 만들고 - 실행 클래스에서 구현 객체 인스턴스를 생성해 실행하는 형태를 취했습니다. 

 

 

 

인터페이스(4) - 인터페이스 구현 기본 원리

인터페이스는 앞서 설명했듯이, 개발 코드에서 인터페이스 메서드를 호출하고 그 인터페이스가 객체의 메서드를 호출하는 구조입니다. 인터페이스는 중개자의 역할을 하기 때문에, 실제 구현

nozeroslope.tistory.com

 

public class ExampleMain {	
	public static void main(String[] args) {
		RemoteControl rc;
		
		rc = new Television();
		rc = new Audio();
	}
}

 

위의 예시는 이미 만들어둔 인터페이스와 구현 클래스를 실행 클래스에서 활용하는 방식입니다. 이제 rc.turnOn( )과 같은 형태로 해당 방식으로 구현된 메서드를 사용하는 방식이지요.

 

지금까지 인터페이스의 선언, 구현 클래스의 선언까지 배웠으니 본격적으로 실행 클래스에서 사용하는 방식을 배워야 하겠지만 여기서 잠깐 하나 더 짚고 넘어갈 부분이 있습니다. 바로 익명 구현 객체를 사용하는 방식입니다. 

 

 

 


 

 

우리는 활용하고자 하는 인터페이스가 있을 경우, 당연히 Television이나 Audio같은 구현용 클래스를 만들어서 생성하고 이를 실행 클래스에서 인스턴스로 생성해 타입 변환을 통해서 사용한다고 배웠습니다. 하지만, 간혹 Television이나 Audio처럼 '굳이' 별도의 클래스를 만들지 않고 일회성으로 사용할 경우도 있습니다. 

 

물론 Television이나 Audio처럼 별도의 구현 클래스를 만들어 재사용하거나 설계상 클래스를 만들어야 하는 경우도 있지만, 우리가 인터페이스의 사용 필요성에 대해서 배운 것을 상기해 보면 인터페이스는 정해진 메서드만을 사용하는 경향이 강합니다. 그래서 일일이 수십 개의 구현 클래스를 만들어야 하는 상황이라면, 실행 코드 상에서 즉석으로 해당 구현 클래스를 구현할 수도 있습니다. 

 

우리가 만들어본 인터페이스의 구현 클래스를 되짚어보면, (디폴트/정적 메소드를 제외하고) 추상 메서드 세 개를 오버라이드 하는 것이 전부였습니다. 이런 경우 비슷한 내용의 클래스를 계속 만드는 것은 비생산적인 상황일 때도 있겠죠. 

 

이럴 때, 별도의 클래스 파일을 만들지 않고, 실행 코드에서 소스 파일 없이 이 인터페이스 구현 객체 인스턴스를 만들 수도 있습니다. 이 익명 구현 객체는 람다식, UI 이벤트 처리, 임시 작업 스레드 등에서 앞으로도 사용되는 패턴이니 잘 기억해 둡시다.

 

실행 클래스에서 익명 구현 인스턴스를 선언하는 방법은 다음과 같습니다. 

 

인터페이스타입 변수명 = new 인터페이스타입(){
	// 인터페이스 추상 메서드의 실체 메서드 선언
};

 

위의 선언문에서 주의할 것은 다음과 같습니다. 일단 중괄호 끝에 세미콜론을 잊으면 안 됩니다. 그리고 클래스를 선언한 다음 구현 객체 인스턴스를 생성할 때와 다르게 new에서 인터페이스 타입을 그대로 사용합니다. 

 

이 코드의 뜻은, [ 인터페이스타입() {...} ]을 구현해 하나의 클래스를 선언하라는 의미입니다. 그리고 new 연산자로 이 이름없는 클래스를 인스턴스로 생성하라는 의미가 됩니다. 이 안에 추가 필드와 메서드도 선언은 가능하지만, 이 익명 객체 안에서만 사용이 가능하다는 한계가 있습니다(인터페이스 변수로 추가 선언된 필드와 메서드를 접근할 수 없습니다).

 

그럼 실제로 실행 코드에서 이 익명 구현 객체를 사용해 보도록 하겠습니다. 

 

 

public interface RemoteControl {
    public int MAX_VOLUME = 10;
    public int MIN_VOLUME = 0;
    
    public void turnOn();
    public void turnOff();
    public void setVolume(int volume);
    
    default void setMuete(boolean mute) {
        if(mute) {
            System.out.println("무음");
        } else {
            System.out.println("무음 해제");
        }
    }
    
    static void changeBattery() {
        System.out.println("배터리 교환");
    }
}

 

 

public class ExampleMain {	
	public static void main(String[] args) {
		RemoteControl rc = new RemoteControl() {
			public void turnOn() {System.out.println("익명 turnOn");}
			public void turnOff() {System.out.println("익명 turnOff");}
			public void setVolume(int volume) {System.out.println("익명 setVolume" + volume);}
		};
		
		rc.turnOn();
		rc.turnOff();
		rc.setVolume(99);
		rc.setMuete(true);
	}
}


/* 출력
익명 turnOn
익명 turnOff
익명 setVolume99
무음
*/

 

보다시피 인터페이스 RemoteControl과 실행 클래스만으로 추상 메서드를 오버라이드하고, 이를 실행했습니다. 그리고 디폴트 메서드 역시 rc를 통해 정상적으로 실행되는 것을 볼 수 있습니다. 

 

참고로, 자바의 모든 인스턴스는 '클래스'로부터 생성되는 기본 원칙을 가지고 있습니다. 그런데, 여기서 생성된 [익명 객체]는 명시적인 '클래스'가 없는 상태라고 말씀드렸습니다. 그럼 이 rc라는 변수로 생성된 익명의 인스턴스는 어떻게 생성되는 것일까요? 

 

이 경우에는 실행 클래스를 통해서 생성됩니다. 정확하게는 실행 클래스의 부속 클래스 형태가 되죠. 실행 클래스의 클래스 이름은 [ExampleMain] 입니다. 그럼 ExampleMain.java가 컴파일될 때, 이 익명 객체가 존재하면 다음과 같은 클래스 파일이 추가로 생성됩니다. 

 

ExampleMain$1.class
ExampleMain$2.class
ExampleMain$3.class
...

 

해당 실행 클래스 명 뒤에 '$'를 기준으로 1, 2, 3...순서로 익명 객체의 개수만큼 바이트코드 파일이 추가로 만들어집니다. 익명 객체라고 해서 클래스 자체가 없는 것이 아닙니다.