Java; Inner Class

🗓️

내부 클래스

  • 내부 클래스는 말 그대로 클래스 내부에 선언한 클래스다.
  • 내부 클래스를 선언하는 이유는 외부 클래스와 밀접한 관련이 있기 때문이다.
  • 다른 클래스와 협력할 일이 없을때도 내부 클래스로 선언해서 사용한다.
  • 인스턴스 내부 클래스, 정적 내부 클래스, 지역 내부 클래스가 있다.
  • 클래스 이름 없이 선언하고 바로 쓸수 있는 익명 클래스도 있다.
class A{ // 외부 클래스
    class B{ // 인스턴스 내부 클래스 
        static class C{ // 정적 내부 클래스

        }
    }
    public void a(){
        class D{ // 지역 내부 클래스
        }
    }
}

인스턴스 내부 클래스

  • 인스턴스 변수를 선언할때와 같은 위치에 선언하며, 외부 클래스 내부에서만 생성하여 사용하는 객체를 선언할때 사용한다.
  • 인스턴스 내부 클래스는 외부 클래스 생성 후 생성된다. 외부 클래스를 생성하지 않으면 내부 클래스를 사용할 수 없다.
public class OutClass {

    private int num = 10;
    private static int sNum = 20;

    private InClass inClass;

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

    class InClass {

        int inNum = 100;
        // static int sInNum = 200; 내부 클래스에서 정적변수 선언 불가능

        void inTest() {
            System.out.println("OutClass num: " + num);
            System.out.println("OutClass sNum: " + sNum);
        }

        // static void sTest(){} 마찬가지로 내부클래스에서 정적메소드 선언 불가능

    }

    public void usingClass() {
        inClass.inTest();
    }
}

public class InnerTest {

    public static void main(String[] args) {
        OutClass outClass = new OutClass();
        outClass.usingClass();
    }
}

인스턴스 내부 클래스에서 사용하는 변수와 메소드

  • 외부 클래스에서 private로 선언했더라도 내부 클래스에서 사용할 수 있다.
  • 내부 클래스에서는 정적선언을 할 수 없다.
  • 외부 클래스의 인스턴스를 만들어 내부클래스의 메소드를 호출할 수 있다.

다른 클래스에서 인스턴스 내부 클래스 생성하기

  • 내부클래스를 생성하는 이유는 그 클래스를 감싸고 있는 외부 클래스에서만 사용하기 위해서다.
  • 내부클래스를 밖의 다른 클래스를 생성하서 사용하는것은 용도에 맞지 않다.

정적 내부 클래스

  • 외부 클래스가 먼저 생성되고 나서 내부 클래스가 생성되는 환경에서는 정적선언을 할 수 없다고 했다.
  • 하지만 내부 클래스가 외부 클래스와 무관하게 사용가능해야 한다면 static예약어를 사용하면 된다.
  • 그러나 정적내부 클래스를 생성할경우 정적으로 선언된 변수만 참조할 수 있다.

지역 내부 클래스

  • 메소드 내부에 클래스를 정의해 사용하는것을 말한다.
  • Runnable인커페이스를 구현하는 클래스를 지역 내부 클래스로 만든 예제.
class Outer {

    int outNum = 100;
    static int sNum = 200;

    Runnable getRunnable(int i) {
        int num = 100;
        class MyRunnable implements Runnable {

            int localNum = 10;

            @Override
            public void run() {
                // num = 200; 지역변수는 상수로 바껴 불가능
                // i = 100; 매개변수 역시 상수화
                System.out.println("i: " + i);
                System.out.println("num: " + num);
                System.out.println("localNum: " + localNum);
                System.out.println("outNum: " + outNum);
                System.out.println("Outer.sNum: " + Outer.sNum);
            }
        }
        return new MyRunnable();
    }
}

public class LocalInnerTest {

    public static void main(String[] args) {
        Outer out = new Outer();
        Runnable runner = out.getRunnable(10);
        runner.run();
    }
}
  • 아래는 실행결과
i: 10
num: 100
localNum: 10
outNum: 100
Outer.sNum: 200
  • Outer클래스를 생성한 후 Runnable형 객체로 getRunnable()을 호출하는 식으로 MyRunnable 지역내부 클래스를 사용하고 있다.
  • MyRunnable을 사용하려면 이 클래스를 직접 생성하는 것이 아니라 getRunnable()메소드 호출을 통해 생성된 객체를 반환 받아야한다.

지역내부 클래스에서 지역 변수의 유효성

  • 변수의 유효성에 대해서 살펴보자. 지역변수는 메소드가 호출 될 때 스택 메모리에 생성되고 끝나면 사라진다. 그런데 지역 내부 클래스에 포함된 getRunnable()메소드를 호출해도 run()이 정상적으로 호출된다.
  • 이를 통해 메소드 호출이 끝나고 스택 메모리에 지워진 변수를 또 참조할 수 있다는 것을 알 수 있다. final 키워드가 자동으로 추가되기 때문이다.
  • 그래서 지역내부 클래스의 메소드를 바꾸려고 하면 오류가 발생한다.

익명 내부 클래스

  • 위의 지역내부 클래스에서 MyRunnable을 선언했지만 사용하는곳은 맨 마지막 리턴값에서 생성할 뿐이다.
  • 클래스 이름을 빼고 클래스를 바로 생성하는 방법과 인터페이스나 추상 클래스형 변수를 선언하고 클래스를 생성해 개입하는 방법 두가지가 있다.
class Outer2 {

    Runnable getRunnable(int i) {
        int num = 100;

        return new Runnable() {
            @Override
            public void run() {
                System.out.println(i);
                System.out.println(num);
            }
        };
    }

    Runnable runner = new Runnable() {
        @Override
        public void run() {
            System.out.println("Anonymous class variable");
        }
    };
}

public class AnonymousInnerTest {

    public static void main(String[] args) {
        Outer2 out = new Outer2();
        Runnable runnable = out.getRunnable(10);
        runnable.run();
        out.runner.run();
    }
}

-아래는 실행결과다

10
100
Anonymous class variable
  • 익명내부 클래스는 단 하나의 인터페이스 또는 단 한나의 추상 클래스를 바로 생성 할 수 있다.
  • Runnable인터페이스를 생성할수 있으려면 인터페이스 몸체가 필요하다. 위 코드를 보면 반드시 구현해야 하는 run()메소드가 포함된것을 알수 있다.
  • 그리고 마지막에 ;를 사용하여 익명내부클래스가 끝났다는것을 알려준다.
  • 인터페이스나 추상클래스 자료형으로 변수 선언을 한 다음 익명 내부 클래스를 생성해 다입하는 방법으로도 사용할 수 있다.