Langauge/Java-basic

[Java] 이너 클래스

강잇 2022. 7. 22. 02:50

이너 클래스(Inner Class)

  • 이너 클래스는 클래스 내부에 포함되는 클래스로, 외부 클래스와 내부 클래스가 서로 연관되어 있을 때 사용한다.
  • 이너 클래스를 사용하면 접근 지정자와 관계없이 외부 클래스의 멤버들에 쉽게 접근할 수 있고, 코드의 복잡성을 줄일 수 있다.
  • 또한 외부적으로 불필요한 데이터를 감출 수 있어 캡슐화를 달성하는데 유용하다.
  • 이너 클래스는 인스턴스 멤버 이너 클래스, 정적 멤버 이너 클래스, 지역 이너 클래스로 나뉜다.
  • 이너 클래스도 클래스이기 때문에 바이트 코드(.class) 파일이 생성된다.
// 외부 클래스
class Outer {

    // 인스턴스 멤버 이너 클래스
    class Inner {    
    }
    
    // 정적 멤버 이너 클래스
    static class StaticInner {
    }
    
    void run() {
        // 지역 이너 클래스
        class LocalInner {
        }
    }
}

인스턴스 멤버 이너 클래스와 정적 멤버 이너 클래스

  • 클래스 내부에 멤버의 형태로 존재함
  • 외부 클래스의 모든 접근 지정자의 멤버에 접근 가능
  • 바이트 코드 파일명 : '아우터클래스명$이너클래스명.class'
  • 이너 클래스 내부에서 아우터 클래스의 멤버를 참조하려면 '아우터 클래스명.this.'를 이용하여 참조할 수 있다.
  • 정적 멤버 이너 클래스는 인스턴스 멤버 이너 클래스에 static 키워드가 붙은 것으로 static의 특성을 따른다.(정적 멤버, 객체 생성 없이 사용 등)

인스턴스 이너 클래스 객체 생성

  • 인스턴스 이너 클래스는 아우터 클래스 내부에 있는 멤버처럼 활용된다.
  • 따라서 이너 클래스의 인스턴스를 생성하기 위해서는 아우터 클래스의 인스턴스를 먼저 생성한 후 참조하여야 생성할 수 있다.
// 인스턴스 이너 클래스 객체 생성
아우터 클래스 참조 변수 = new 아우터 클래스(); // 아우터 클래스 인스턴스 생성
아우터 클래스.이너 클래스 참조 변수 = 아우터 클래스 참조 변수.new 이너 클래스();

// 예시
public class Main {
    public static void main(String[] args) {
        A a = new A();
        A.B ab = A.new B(); // A 클래스 내부에 있는 B 생성자 호출
    }
}
class A {
    class B {
        ...
    }
}
public class Main {
    public static void main(String[] args) {
        System.out.println("외부 클래스를 사용하여 이너 클래스 기능 호출");
        Outer outer = new Outer();
        outer.testClass();

        System.out.println();

        System.out.println("이너 클래스 객체 생성하여 이너 클래스 기능 호출");
        Outer.InClass inner = outer.new InClass();
        inner.Test();
        inner.print();
    }
}

class Outer {
    private int num = 1;
    private static int sNum = 2;

    private InClass inClass;

    public Outer() {
        inClass = new InClass();
    }

    class InClass {
        int inNum = 10;
        void Test() {
            System.out.println("Outer num = " + num + "(외부 클래스의 인스턴스 변수)");
            System.out.println("Outer sNum = " + sNum + "(외부 클래스의 정적 변수)");
        }
        void print() {
            System.out.println("Inner inNum = " + inNum + "(이너 클래스의 인스턴스 변수)");
        }
    }
    public void testClass() {
        inClass.Test();
        inClass.print();
    }
}

실행 결과

정적 이너 클래스 객체 생성

  • 인스턴스 생성 방법은 인스턴스 이너 클래스와 동일
  • 추가적으로 static 키워드로 인해 인스턴스 생성 없이 이너 클래스의 멤버를 사용할 수 있다.
// 정적 이너 클래스 객체 생성
아우터 클래스.이너 클래스 참조 변수 = new 아우터 클래스.이너 클래스();
// 객체 생성 없이 멤버 사용
아우터 클래스.이너 클래스.멤버;

// 예시
public class Main {
    public static void main(String[] args) {
        // 객체 생성 후 메서드 호출
        A.B ab = new A.B();
        ab.print();
        // 객체 생성 없이 메서드 호출
        A.B.print();
    }
}

class Outer { // 아우터 클래스
    static class StaticInner { // 정적 이너 클래스
        static void print() {
            System.out.println("정적 이너 클래스");
        }
    }
}

지역 이너 클래스

  • 지역 이너 클래스는 메서드 내에서 정의되는 클래스다.
  • 지역 이너 클래스는 선언 이후 바로 인스턴스를 생성하여 사용하며, 메서드가 호출될 때만 메모리에 로딩된다. -> 정적 클래스 선언 불가
  • 바이트 코드 파일명 : '아우터 클래스$+숫자+지역 이너클래스.class'와 같이 생성된다.
    숫자가 붙는 이유 : 지역 이너 클래스는 메서드 내에서만 정의하여 메서드 호출을 통해 인스턴스 생성 및 사용하므로 여러 개의 메서드에서 동일한 클래스명을 가진 지역 이너 클래스를 생성할 수 있다.
    따라서 동일한 클래스명을 가진 바이트 코드가 생성되었을 때 구분하기 위해 숫자로 마킹하는 것.

동일한 클래스명을 생성해도 예외 발생 X

지역 이너 클래스 객체 생성

  • 아우터 클래스 멤버 + 자신이 정의된 메서드 지역 변수 사용 가능
  • 지역 이너 클래스에서 지역 변수를 사용하기 위해서는 해당 지역 변수가 final로 선언돼 있어야 하며, final로 선언되지 않은 지역 변수는 지역 이너 클래스에서 사용할 때 컴파일러가 강제로 final로 선언한다.
  • -> 클래스 메모리와 스택 메모리의 차이로, 메서드의 지역 변수는 메서드가 종료되면 소멸되는 특징을 가지고 있다.
    따라서 소멸된 지역 이너 클래스의 인스턴스가 소멸된 지역 변수를 참조하려는 경우가 발생할 수 있기 때문에 final로 선언하여 변수를 따로 관리하게 하는 것.
// 지역 이너 클래스의 객체 생성
지역 이너 클래스 참조 변수 = new 지역 이너 클래스();

class A {
    void abc() {
        class B {
        }
        B b = new B();
    }
}
class Outer { //외부 클래스
    int num = 5;
    void test() {
        int num2 = 6;
        class LocalInClass { //지역 내부 클래스
            void getPrint() {
                System.out.println(num);
                System.out.println(num2);
            }
        }
        LocalInClass localInClass = new LocalInClass();
        localInClass.getPrint();
    }
}
public class Main {
    public static void main(String[] args) {
        Outer outer = new Outer();
        outer.test();
    }
}