상세 컨텐츠

본문 제목

중첩 인터페이스 (익명 객체 로컬 변수)

기록 - 프로그래밍/Java

by wjjun 2024. 1. 20. 00:29

본문

중첩 인터페이스

인터페이스를 클래스 내부에 선언하는 이유는 해당 클래스와 긴밀한 관계를 맺는 구현 클래스를 만들기 위해서입니다.

class A {
    interface I {
        void method();
    }
}

 

public class Button {
    OnClickListener listener;
    
    void setOnClickListener(OnclickListener listener) {
        this.listener = listener;
    }
    
    void touch() {
        listener.onClick();
    }
    
    interface OnClickListener {
        void onClick();
    }
}

Button 클래스를 보면 중첩 인터페이스 OnclickListener 타입으로 listener 필드를 선언하고 Setter 메서드를 이용해서 setOnClickListener()롤 구현 객체를 받아 필드에 대입합니다.

 

버튼 이벤트 touch()가 실행되면 onClick() 메서드가 호출되는데

이때 인터페이스의 onClick() 메서드가 실행됩니다.

 

// 구체 클래스
public class CallListener implements Button.OnClickListener {
    @Override
    public void onClick() {
        sout("전화를 겁니다");
    }
}

// 구체 클래스
public class MessageListener implements Button.OnClickListener {
    @Override
    public void onClick() {
        sout("메시지를 보냅니다");
    }
}

어떤 객체를 생성해서 Button 객체의 setOnClickListener() 메서드를 세팅하는지에 따라 Button의 touch() 결과가 다릅니다.

 

 

익명 객체

단독으로 생성할 수 없습니다.

클래스를 상속하거나 인터페이스를 구현해야만 생성이 가능합니다.

 

부모 타입으로 필드나 변수를 선언하고 자식 객체를 초기값으로 대입하는 경우

부모 클래스를 상속하여 자식 클래스를 선언하고 new 연산자로 자식 객체를 생성하여 필드나 로컬 변수에 대입하는 것이 기본입니다.

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

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

그러나 자식 클래스가 재사용되지 않고 필드와 변수 초기값으로만 사용할 경우 익명 자식 객체를 생성하여 초기값에 대입하는 것이 좋은 방법입니다.

 

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

    // 필드

    // 메서드

};

부모 클래스 (매개값..){} 은 부모 클래스를 상속해서 중괄호 { }와 같이 자식 클래스를 선언하라는 뜻입니다.

new 연산자는 이렇게 선언된 자식 클래스를 객체로 생성합니다.

 

부모 클래스 (매개값, ...)은 부모 생성자를 호출하는 코드입니다.

매개값은 부모 생성자의 매개 변수에 맞게 입력하면 됩니다.

 

필드 선언 (초기값에 익명 자식 객체 사용) 

중괄호{ } 내부에는 필드나 메서드를 선언하고 부모 클래스의 메서드를 재정의(오버라이딩)를 작성할 수 있습니다.

일반 클래스와 차이점은 생성자를 선언할 수 없다는 점과 필드를 선언할 때 초기값으로 익명 자식 객체를 생성해서 대입합니다.

class A {
    Parent field = new Parent() { // A 클래스의 필드 선언
        int childField;
        void childMethod() { }
        @Override
        void parentMethod() { }
    };
}

 

 

로컬 변수 선언 (초기값에 익명 자식 객체 사용) 

다음 코드는 메서드 내에서 로컬 변수를 선언할 때 초기값으로 익명 자식 객체를 생성해서 대입합니다.

class A {
    void method() {
        Parent localVar = new Parent() { // 로컬 변수 선언
            int childField;
            void childMethod() {  }
            @Override
            void parentMrthod() {  }
        };
    }
}

 

익명 자식 객체에 새롭게 정의된 필드와 메서드는 익명 자식 객체 내부에서만 사용되고, 외부에서는 필드와 메서드에 접근할 수 없습니다.

그 이유는 익명 자식 객체는 부모의 변수 타입에 대입되어 부모 타입에 선언한 것만 사용이 가능합니다.

 

예를 들어 childField와 메서드 childMethod()는 parentMethod() 메서드 내에서 사용이 가능합니다.

하지만, A 클래스의 필드인 field로는 접근이 불가합니다.

 

 

익명 구현 객체 생성

인터페이스 타입으로 필드나 변수를 선언하고 구현 객체를 초기값으로 대입합니다.

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
        void turnOn() { } // RemoteControl 인터페이스의 추상 메서드에 대한 실체 메서드
    };
}


void method() {
    RemoteControl localVar = new RemoteControl { // 로컬 변수 선언
        @Override
        void turnOn() { } // RemoteControl 인터페이스의 추상 메서드에 대한 실체 메서드
    }
}

// 메서드의 매개변수가 인터페이스 타입일 경우
// 메서드 호출 코드에서 익명 구현 객체를 생성해서 매개값으로 대입도 가능하다
class A {
   void method1(RemoteControl rc) { }
   void method2() {
       method1( // method1() 메서드 호출
           new RemoteControl() {
               @Override
               void turnOn() { }
           }
       };
   }
}

 

class Button {
    OnClickListener listener;
    
    void setOnClickListener(OnclickListener listener) {
        this.listener = listener;
    }
    
    void touch() {
        listener.onClick();
    }
    
    interface OnclickListener {
        void onClick();
    }
}

Button 클래스를 보면 중첩 인터페이스 OnClickListener 타입으로 필드인 listener를 선언했습니다.

그리고 Setter 메서드를 이용해 외부에서 구현 객체를 받아 필드로 대입합니다.

 

버튼 이벤트가 발생되면 touch() 메서드가 실행되는데 인터페이스를 통해 구현 객체의 메서드를 호출합니다. listener.onClick()

setOnClickListener()를 호출할 때 매개값으로 준 익명 구현 객체가 담당하도록 처리했습니다.

class Window {
    Button b1 = new Button();
    Button b2 = new Button();
    
    // 필드 초기값으로 대입
    Button.OnClickListener listener = new Button.OnClickListener() {
        @Override
        public void onClick() {
            sout("전화를 걸다");
        }
    };
    
    Window() {
        b1.setOnClickListener(listener); // 매개값으로 필드 대입
        b2.setOnClickListener(new Button.OnClickListener() { // 매개값으로 익명 구현객체 대입
            @Override
            public void onClick() {
                sout("메시지 전송하다");
            }
        });
    }

 

 

익명 객체 로컬 변수 사용

익명 객체 내부에서 외부 클래스의 필드나 메서드는 제한 없이 사용 가능합니다. 문제는 메소드의 매개 변수나 로컬 변수를 익명 객체에서 사용할 때 입니다.

 

메서드 내에서 생성된 익명 객체를 메서드 실행이 끝나도 메모리에 존재해서 계속 사용이 가능합니다.

 

매개변수나 로컬 변수는 메서드 실행이 끝나면 스택 메모리에서 사라지기 때문에 익명 객체에서 사용할 수 없게 되어 문제가 발생됩니다.

 

익명 객체 내부에서 메서드의 매개 변수나 로컬 변수를 사용할 경우 이 변수들은 final 특성을 가져야 합니다.

 

자바 7이전까지는 반드시 final 키워드로 선언해야 했지만, 자바 8부터는 없이 선언 가능합니다.

void outMethod(final int arg1, int arg2) {
   final int var1 = 1;
   int var2 = 2;
   
   인터페이스 변수 = new 인터페이스() {
       void method() {
           int result = arg1 + arg2 + var1 + var2;
       }
   });
}

인터페이스 변수 = new 인터페이스() {

  int arg2 = 매개값;

  int var2 = 2;

 

  void method() {

    int arg1 = 매개값;

    int var1 = 1;

    int result = arg1 + arg2 + var1 + var2;

  }

}

 

익명 클래스의 내부 복사 위치에 신경쓸 필요 없이

익명 객체에서 사용된 매개 변수와 로컬 변수는 모두 fianl 특성을 갖는다는 것만 알고 있으면 됩니다.

관련글 더보기

댓글 영역