ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Java] 중첩 클래스와 중첩 인터페이스
    언어/Java 2021. 1. 16. 17:47

    중첩 클래스는 클래스 내부에 선언한 클래스입니다. 중첩 클래스를 사용하면 두 클래스의 멤버들을 서로 쉽게 사용할 수 있고 외부에는 불필요한 관계 클래스를 감춤으로써 코드의 복잡성을 줄일 수 있다는 장점이 있습니다. 

    public class ClassName {
        class NestedClass {
            
        }
    }


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

    public class ClassName {
        interface NestedInterface {
    
        }
    }

    중첩 클래스

    클래스의 멤버로 선언되는 중첩 클래스를 멤버 클래스라 하고 메소드 내부에서 선언되는 중첩 클래스를 로컬 클래스라 합니다. 멤버 클래스는 클래스나 객체가 사용 중이면 재사용이 가능하지만 로컬 클래스는 메소드를 실행할 때만 사용되고 메소드가 종료되면 사라집니다.

    public class ClassName {
        class A { } // 인스턴스 멤버 클래스 - A 객체를 생성해야만 사용할 수 있음
        static class B { }// 정적 멤버 클래스 - A 클래스로 바로 접근 가능
        
        void method() {
            class C { } // method()가 실행될 때만 사용할 수 있음
        }
    }


    중첩 클래스도 하나의 클래스이기 때문에 컴파일 시, 바이트 코드 파일(.class)이 별도로 생성됩니다. 멤버 클래스의 경우, 바이트 코드 파일의 이름은 다음과 같이 결정됩니다.

    ClassName $ B .class

    • ClassName: 바깥 클래스
    • B: 멤버 클래스


    ClassName $1 C .class

    • ClassName: 바깥 클래스
    • C: 로컬 클래스

    인스턴스 멤버 클래스

    인스턴스 멤버 클래스는 static 키워드 없이 중첩 선언된 클래스입니다. 인스턴스 멤버 클래스는 인스턴스 필드와 메소드만 선언이 가능하고 정적 필드와 메소드는 선언할 수 없습니다.

    public class ClassName {
        class A { 
            A() {}
            int field1;
            static int field2; // 정적 필드 X
            void method1() { }
            static void method2() { } // 정적 메소드 X 
        }
    }


    ClassName클래스 외부에서 B 객체를 생성하려면 ClassName객체를 생성하고 B 객체를 생성해야 합니다. ClassName클래스 내부의 생성자 및 인스턴스 메소드에서는 일반 클래스처럼 B 객체를 생성할 수 있습니다.

    public class ClassName {
        class A {
            A() {}
            int field1;
            void method1() { }
        }
    
        void method1() {
            A a = new A();
            a.field1 = 3;
            a.method1();
        }
    }


    일반적으론 ClassName클래스 외부에서 B 객체를 생성하는 일은 없습니다.

    정적 멤버 클래스

    public class ClassName {
        static class A {
            A() {}
            int field1;
            static int field2;
            void method1() { }
            static void method2() { }
        }
    }


    ClassName클래스 외부에서 정적 멤버 클래스 A의 객체를 생성하기 위해선 ClassName 객체를 생성할 필요가 없고 아래와 같이 생성합니다.

    ClassName.A a = new ClassName.A();
    a.field1 = 3; // 인스턴스 필드
    a.method1(); // 인스턴스 메소드
    ClassName.A.field2 = 11; // 정적 필드
    ClassName.A.method2(); // 정적 메소드

    로컬 클래스

    로컬클래스는 내부에서만 사용되므로 접근을 제한할 필요가 없기 때문에 접근 제한자를 붙일 수 없고 static을 붙일 수 없습니다. 

    void method1() {
        class A {
            A() {}
            int field1;
            static int field2; // 정적 필드 X
            void method1() { }
            static void method2() { } // 정적 메소드 X 
        }
        
        A a = new A();
        a.field1 = 123;
        a.method1();
    }


    로컬 클래스는 메소드가 실행될 때 메소드 내에서 객체를 생성하고 사용해야 합니다. 주로 이러한 코딩은 비동기 처리를 위해 스레드 객체를 만들 때 사용합니다.


    중첩 클래스의 접근 제한

    바깥 필드와 메소드에서 사용 제한

    public class ClassName {
        class B { }
        static class C { }
        
        // 인스턴스 필드
        B f1 = new B();
        C f2 = new C();
        
        // 인스턴스 메소드
        void m1() {
            B v1 = new B();
            C v2 = new C();
        }
        
        // 정적 필드 초기화
        static B f3 = new B(); // 오류
        static C f4 = new C();
        
        // 정적 메소드
        static void method2() {
            B v1 = new B(); // 오류
            C v2 = new C();
        }
    }


    정적 멤버 클래스인 C는 모든 필드의 초기값이나 메소드에서 객체를 생성할 수 있지만 인스턴스 멤버 클래스인 B는 정적 필드의 초기값이나 정적 메소드에선 객체를 생성할 수 없습니다.

    멤버 클래스에서 사용 제한

    멤버 클래스가 인스턴스 또는 정적으로 선엄됨에 따라 멤버 클래스 내부에서 바깥 클래스의 필드와 메소드에 접근할 때에도 제한이 따릅니다. 인스턴스 멤버 클래스 안에서는 바깥 클래스의 모든 필드와 메소드에 접근이 가능하지만 정적 멤버 클래스 안에서는 바깥 클래스의 정적 필드와 메소드만 접근이 가능합니다.

    로컬 클래스에서 사용 제한

    로컬 클래스의 객체는 메소드 실행이 종료되면 없어지는 것이 일반적이지만, 메소드가 종료되어도 계속 실행 상태로 존재할 수 있습니다. 로컬 스레드 객체를 사용할 때, 메소드를 실행하는 스레드와 다르므로 메소드가 종료된 후에도 로컬 스레드 객체는 실행 상태로 존재할 수 있습니다.


    자바는 이 문제를 해결하기 위해 컴파일 시 로컬 클래스에서 사용하는 매개 변수나 로컬 변수의 값을 로컬 클래스 내부에 복사해두고 사용합니다. 그리고 매개 변수나 로컬 변수가 수정되어 값이 변경되면 로컬 클래스의 복사해둔 값과 달라지므로 문제를 해결하기 위해 매개 변수나 로컬 변수를 final로 선언할 것을 요구합니다.

    • 자바 8 이전 버전은 final 키워드를 무조건 붙여야 하지만 8 이후부터는 생략을 해도 final 특성을 가집니다.
    void m1(int arg) {
        int i = 1;
        arg = 10; // Inner 클래스에서 접근하고자 해 final 속성을 가진 값인데 변경하려 해서 오류 발생
        i = 1; // Inner 클래스에서 접근하고자 해 final 속성을 가진 값인데 변경하려 해서 오류 발생
    
        class Inner {
            void m2() {
                int r = arg + i;
            }
        }
    }

    중첩 클래스에서 바깥 클래스 참조 얻기

    클래스 내부에서 this는 객체 자신의 참조입니다. 중첩 클래스에서 this 키워드를 사용하면 바깥 클래스의 객체 참조가 아니라, 중첩 클래스의 객체 참조가 됩니다. 따라서 중첩 클래스 내부에서 this.필드, this.메소드() 로 호출하면 중첩 클래스의 필드와 메소드가 사용됩니다.


    중첩 클래스 내부에서 바깥 클래스의 객체 참조를 얻으려면 바깥 클래스의 이름을 this 앞에 붙여주면 됩니다.

    • 바깥클래스.this.필드;
    • 바깥클래스.this.메소드();
    public class ClassName {
        String f1 = "f1";
        void m1() {
            System.out.println("OUTTER");
        }
        
        class A {
            String f1 = "inner f1";
            void m1() {
                System.out.println("INNER");
            }
            
            void print() {
                // 내부 객체 참조
                System.out.println(this.f1);
                this.m1();
                
                // 바깥 객체 참조
                System.out.println(ClassName.this.f1);
                ClassName.this.m1();
            }
        }
    }
    
    
    ClassName c = new ClassName();
    c.A innerC = c.new A();
    innerC.print();

    중첩 인터페이스

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

    public class ClassName {
        [static] interface I {
            void m1();
        }
    }


    중첩 인터페이스는 인스턴스 멤버 인스턴스와 정적 멤버 인터페이스 모두 가능합니다. 인스턴스 멤버 인터페이스는 바깥 클래스의 객체가 있어야 사용 가능하고 정적 멤버 인터페이스는 바깥 클래스의 객체 없이 바깥 클래스만으로 바로 접근할 수 있습니다. 주로 정적 멤버 인터페이스를 많이 사용하는데 UI 프로그래밍에서 이벤트를 처리할 목적으로 많이 활용됩니다.


    예를 들어, Button을 클릭했을 때, 이벤트를 처리하는 객체를 받고 싶다고 가정해봅니다. 아무 객체나 받음 안 되고 Button 내부에 선언된 중첩 인터페이스를 구현한 객체만 받아야 한다면 다음과 같이 Button 클래스를 선언하면 됩니다.

    // 중첩 인터페이스
    public class Button {
        OnClickListener listener; // 인터페이스 타입 필드
        
        // 매개 변수의 다형성
        void setOnClickListener(OnClickListener listener) {
            this.listener = listener;
        }
    
        void touch() {
            listener.onClick(); // 구현 객체의 onClick() 메소드 호출
        }
        // 중첩 인터페이스
        static interface OnClickListener {
            void onClick();
        }
    }
    
    
    
    // 구현 클래스
    public class CallListener implements Button.OnClickListener {
        @Override
        public void onClick() {
            System.out.println("전화 call");
        }
    }
    
    // 구현 클래스
    public class MessageListener implements Button.OnClickListener {
        @Override
        public void onClick() {
            System.out.println("메세지 call");
        }
    }
    
    
    // 버튼 이벤트 처리
    
    Button btn = new Button();
    btn.setOnClickListener(new CallListener());
    btn.touch();
    
    btn.setOnClickListener(new MessageListener());
    btn.touch();


    댓글