본문 바로가기

JAVA의 정석

내부 클래스 (inner class)

 

1. 내부 클래스 (inner class)

 

내부 클래스는 클래스 내에 선언된 클래스이다. 클래스에 다른 클래스를 선언하는 이유는 두 클래스가 서로 긴밀한 관계에 있기 때문이다.

 

한 클래스를 다른 클래스의 내부클래스로 선언하면 두 클래스의 멤버들 간에 서로 쉽게 접근할 수 있다는 장점과 외부에는 불필요한 클래스

 

를 감춤으로써 코드의 복잡성을 줄일 수 있다는 장점을 얻을 수 있다. 

 

내부 클래스는 클래스 내에 선언된다는 점을 제외하고는 일반적인 클래스와 다르지 않다. 

 

다만 내부 클래스는 외부 클래스에서 사용하기 위해 만들어진 클래스인 것을 알고 사용하는 것이 좋다 .

 

만약 내부 클래스를 main에서 쓰려고 하다 보면 객체 생성부터 멤버 사용까지 오히려 복잡한 경우 혹은 복잡한 코드를 사용해야 될 수 

 

있음으로 지양하는 것이 좋다. 

 

 

예제 1)

 

 

 

위 예제는 BBB 클래스에서 AAA 의 멤버를 사용하기 위해 AAA의 객체를 따로 생성해준 예제를 내부 클래스로 변경하여, 바로 접근할 수 

 

있게 끔 만든 예제이다.  여기서 주목해야할 점이 두 가지 있다.

 

(1) BBB 클래스에 접근하기 위해 AAA 클래스에서 B의 객체를 만들어 주었다는 것이고, BBB 클래스에서는 AAA의

 

객체를 만들지 않고도 변수에 접근하였다는 점이다. 

 

(2) 기존에 main 함수에서 생성하였던 BBB 객체의 컴파일 에러가 발생하였다. 그 이유는 AAA 클래스의 객체를 생성도 해주지 않았는데

 

BBB 클래스에 접근하였다는 점이다. 그래서 위 예제 코드에서는 주석 처리를 해주었다.  

 

 

위에서 강조한 바와 같이 내부 클래스를 사용함으로써 오히려 main 함수에 AAA의 객체를 만들고 AAA 클래스에서 BBB 객체를 만들어

 

기존보다 더욱 복잡하고 번거러운 코드가 만들어졌음을 알 수 있다. 

2. 내부 클래스의 종류와 특징

 

내부 클래스의 종류는 변수의 선언위치에 따른 종류와 같다. 내부 클래스는 마치 변수를 선언하는 것과 같은 위치에 선언할 수 있으며, 변수

 

의 선언위치에 따라 인스턴스 변수, 클래스 변수, 지역 변수로 나누어지 듯이 내부 클래스도 선언 위치에 따라 다음과 같이 구분되어진다.

 

 

내부 클래스 특징
인스턴스 클래스 (instance class) 외부 클래스의 멤버 변수 선언위치에 선언하며, 외부클래스의 인스턴스 멤버처럼 다루어진다. 주로 외부 클래스의 인스턴스멤버들과 관련된 작업에 사용될 목적으로 선언된다.
스태틱 클래스 (static class) 외부 클래스의 멤버변수 선언위치에 선언하며, 외부 클래스의 static멤버처럼 다루어진다. 주로 외부 클래스의 static 멤버, 특히 static 메서드에서 사용될 목적으로 선언된다. 
지역 클래스 (local class) 외부 클래스의 메서드나 초기화 블럭 안에서 선언하며, 선언된 영역 내부에서만 사용될 수 있다.
익명 클래스 (anonymous class) 클래스의 선언과 객체의 생성을 동시에 하는 이름 없는 클래스 (일회용)

 

 

 

3. 내부 클래스의 선언

public class Outer {

private class InstanceInner{} // 인스턴스 클래스

protected static class StaticInner {} // Static 클래스

 

void Mymethod() {

class LocalInner{} // 로컬 클래스, 메서드 내에서만 이용 가

}

}

 

변수가 선언된 위치에 따라 인스턴스 변수, 클래스(static) 변수, 지역 변수로 나뉘듯이 내부 클래스도 이와 마찬가지로 선언 위치에 따라

 

나뉜다. 그리고 , 각 내부 클래스의 선언 위치에 따라 같은 선언위치의 변수와 동일한 유효범위(scope)와 접근성을 갖는다.

 

위 코드에서 인스턴스 클래스와 클래스 (static) 는 외부 클래스 (Outer) 의 멤버 변수 (인스턴스 변수와 클래스 변수)와 같은 위치에

 

선언되며, 또한 멤버 변수와 같은 성질을 갖는다. 따라서 내부 클래스가 외부 클래스의 멤버와 같이 간주되고 인스턴스의 멤버와 

 

static 멤버 간의 규칙이 내부 클래스에서도 똑같이 적용된다.

 

원래 class 앞 접근 제어자는 default나 public만 가능함에도 불구하고 내부 class는 abstract, final, private,protected 등의 

 

접근제어자도 사용 가능하다.

 

4. 내부 클래스의 제어자와 접근성 예제

 

public class Ex7_12 {
    class InstanceInner{
        int iv = 100;
        static int cv = 100;
        final static int CONST = 100;
    }

    static class StaticInner { // static 내부클래스는 외부클래스의 인스턴스 멤버 사용이 불가능하다.
        int iv = 200;
        static int cv = 200; // static 클래스만 static 멤버를 정의할 수 있다.

    }

    void myMethond() {
        class Localinner{ // 로컬 내부 클래스
            int iv = 300;
            static int cv = 300;
            final static int CONST = 300;

        }
    }
    public static void main(String[] args) {
        System.out.println(InstanceInner.CONST);
        System.out.println(InstanceInner.cv);
        System.out.println(StaticInner.cv);

    }

}

 

 

위와 같은 코드는 정상적으로 프로그램이 실행되지만 자바 규칙에 위배된다. 드물지만 내부 클래스에 static 변수를 선언해야하는

 

경우가 생긴다면 스태틱 클래스로 선언해야하며, final과 static이 동시에 붙은 변수는 상수 이므로 모든 내부클래스에서 정의가 

 

가능하다.

 

 

public class Outer {
    private int outerIv = 0;
    static int outerCv = 0;

    class InstanceInner {
        int iiv = outerIv; // 외부 클래스의 private 멤버도 접근 가능하다.
        int iiv2 = outerCv;
    }

    static class StaticInner{
        // 스태틱 클래스는 외부 클래스의 인스턴스 멤버에 접근할 수 없다.
        // int siv = outerIv; ----- (X) 컴파일 에러
        int iiv2 = outerCv;
    }

    void myMethod(){
        int lv = 0 ;
        final int LV = 0 ;

    class LocalInner {
            int liv = outerIv;
            int liv2 = outerCv;
    // 외부클래스의 지역변수는 final이 붙은 변수(상수)만 접근 가능하다.
        //  int liv3 = lv;
            int liv4 = LV;
        }

    }

}


 

내부 클래스에서 외부 클래스의 변수들에 대한 접근성을 보여주는 예제이다. 인스턴스 클래스(InstanceInner)는 외부 클래스의 인스턴

 

스 멤버이기 때문에 인스턴스 변수 outerIv와 static변수 outerCv를 모두 사용할 수 있다. 심지어 outerIv의 접근 제어가 private 일지어

 

도 말이다. 스태틱 클래스(StaticInner)는 외부 클래스(Outer)의 static멤버이기 때문에 외부클래스의 인스터스멤버인 outerIv와

 

InstatnceInner를 사용할 수 없다. 단지 static 멤버인 outerCv만 사용할 수 있을 뿐이다.

 

지역 클래스(LocalInner)는 외부 클래스(Outer)의 인스턴스멤버와 static멤버를 모두 사용할 수 있으며, 지역 클래스가 포함된

 

메서드에 정의된 지역 변수도 사용할 수 있다. 단, final이 붙은 지역변수만 접근 가능한데 그 이유는 메서드가 수행을 마쳐서 지역 변수가

 

소멸된 시점에서도, 지역 클래스의 인스턴스가 소멸된 지역변수를 참조하려는 경우가 발생할 수 있기 때문이다. 

 

JDK1.8 부터 지역 클래스에서 접근하는 지역 변수 앞에 final을 생략할 수 있게 바뀌었지만 컴파일러가 자동으로 붙여준다.

 

즉, 편의상 final을 생략할 수 있게 한 것일 뿐 해당 변수의 값이 바뀌는 문장이 있으면 컴파일 에러가 발생한다. 

 

 class Outer2 {
    class InstanceInner {
        int iv = 100;
    }

    static class StaticInner {
        int iv = 200;
        static int cv = 300;
    }

    void myMethod(){
        class LocalInner {
            int iv = 400;
        }
    }

   public class Ex7_15 {
        public static void main(String[]args){
            // 인스턴스클래스의 인스턴스를 생성하려면
            // 외부 클래스의 인스턴스를 먼저 생성해야 한다.
            Outer2 oc = new Outer2();
            Outer2.InstanceInner ii = oc.new InstanceInner();

            System.out.println("ii.iv : " + ii.iv);
            System.out.println("Outer2.StaticInner.cv : " + Outer2.StaticInner.cv);

            //스태틱 내부 클래스의 인스턴스는 외부 클래스를 먼저 생성하지 않아도 된다.
            Outer2.StaticInner si = new Outer2.StaticInner();
            System.out.println("si.iv : " + si.iv);
        }
   }
   
}

 

외부 클래스가 아닌 다른 클래스에서 내부 클래스를 생성하고 내부 클래스의 멤버에 접근하는 예제이다.

 

실제로 이런 경우가 발생했다는 것은 내부 클래스로 선언해서는 안되는 클래스를 내부 클래스로 선언했다는 의미이지만

 

참고로 봐두고 넘어가자. 

 

 

class Outer3 {
    int value = 10; // Outer3.this.value

    class Inner {
        int value = 20; // this. value

        void method1() {
            int value = 30;
            System.out.println("        value :" + value);
            System.out.println("    this.value :" + this.value);
            System.out.println("Outer3.this.value :" + Outer3.this.value);
        }

    } // Inner 클래스의 끝
} // Outer 클래스의 끝
    class Ex7_16 {
            public static void main(String args[]){
            Outer3 outer = new Outer3();
            Outer3.Inner inner = outer.new Inner();
            inner.method1();
        }
    }

실행 결과

 

위 예제는 내부 클래스와 외부 클래스에 선언된 변수의 이름이 같을 때 변수 앞에 'this' 또는 '외부 클래스명.this'를 붙여서

 

서로 구별할수 있다는 것을 보여준다.