Java - 내부 클래스(Inner Class)

 

본 내용은 자바의 정석 3rd Edition을 참고하여 작성되었습니다. 개인적으로 학습한 내용을 복습하기 목적이기 때문에 내용상 오류가 있을 수 있습니다.

1. 내부 클래스?

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

1.1 내부 클래스의 장점

  • 내부 클래스에서 외부 클래스의 멤버들을 쉽게 접근할 수 있다.
  • 코드의 복잡성을 줄일 수 있다.
class A {
  // ...
}

class B {
  // ...
}
// 외부클래스(outer class)
class A {

  // ...

  // 내부클래스(inner class)
  class B {
    // ...
  }
}

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

내부 클래스의 종류는 변수의 선언위치에 따른 종류와 같다. 내부 클래스는 마치 변수를 선언한 것과 같은 위치에 선언할 수 있으며, 변수의 선언위치에 따라 인스턴수 변수, 클래스 변수, 지역변수로 구분되는 것과 같이 내부 클래스도 선언위치에 따라 아래의 표와 같이 구분된다.

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

3. 내부 클래스의 선언

class Outer {
  int iv = 0; // 인스턴스 변수
  static int cv = 0; // 클래스 변수
  void myMethod() {
    int lv = 0; // 지역변수
  }
}
class Outer {
  class InstanceInnerClass {}
  static class StaticInnerClass {}
  void myMethod() {
    class LocalInnerClass {}
  }
}

위의 2번째 코드를 보면 외부클래스에 서로 다른 3개의 클래스가 선언되있다. 1번째 코드와 2번째 코드를 비교해보면 내부 클래스의 선언 위치가 변수의 선언위치가 동일하다는 것을 알 수 있다.변수가 선언된 위치에 따라 변수의 종류가 나뉘듯이 내부 클래스도 선언된 위치에 따라 종류가 나뉜다. 각 내부 클래스의 선언 위치에 따라 변수와 동일한 유효범위와 접근성을 갖게된다.

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

내부 클래스도 클래스이기 때문에 abstractfinal과 같은 제어자를 사용할 수 있을 뿐만아니라, 멤버변수들처럼 private, protected과 접근제어자도 사용이 가능하다.

4.1 내부 클래스 예제 1

public class InnerClassEx1 {

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

  class InstanceInner {
    int iv = 100;
    //static int cv = 100; // static 변수를 선언 불가
    final static int CONST = 100;   // 상수이기에 선언 가능
  }

  static class StaticInner {
    int iv = 100;
    static int cv = 200; // static클래스만 static변수 선언이 가능
  }

  void myMethod() {
    class LocalInner {
      int iv = 300;
      // static int cv = 300; // static 변수선언 불가
      final static int CONST = 300; // 상수이기에 가능
    }
  }

}
100
200

4.2 내부 클래스 예제 2

public class InnerClassEx2 {

  class InstanceInner {

  }

  static class StaticInner {

  }

  // 인스턴스 멤버 간에는 서로 직접 접근가능
  InstanceInner iv = new InstanceInner();

  // static 멤버 간에는 서로 직접 접근가능
  static StaticInner cv = new StaticInner();

  static void staticMethod() {

    // static 멤버는 인스턴스멤버에 직접 접근불가
    //InstanceInner obj1 = new InstanceInner();
    StaticInner obj2 = new StaticInner();

    // 굳이 접근하려한다면 객체를 먼저 생성해야 함,
    // 인스턴스클래스는 외부클래스를 먼저 생성해야만 생성이 가능
    InnerClassEx2 outer = new InnerClassEx2();
    InstanceInner obj = outer.new InstanceInner();

  }

  void instanceMethod() {

    // 인스턴스메서드에서는 인스턴스 멤버와 static 멤버 모두 접근가능
    InstanceInner obj1 = new InstanceInner();
    StaticInner obj2 = new StaticInner();

    // 메서드 내에 지역적으로 선언된 내부 클래스는 외부에서 접근불가
    //LocalInner lv = new LocalInner();

  }

  void myMethod() {
    class LocalInner {

    }
    LocalInner lv = new LocalInner();
  }
}

4.3 내부 클래스 예제 3

public class InnerClassEx3 {

  private int outerIv = 0;
  static int outerCv = 0;

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

  static class StaticInner {
    // static 클래스는 외부 클래스의 인스턴스 멤버에 접근불가
    //int siv = outerIv;
    static int scv = outerCv;
  }


  void myMethod() {
    int lv = 0;
    final int LV = 0;   // JDK 1.8 부터 final 생략가능

    class LocalInner {
      int liv = outerIv;
      int liv2 = outerCv;
      int liv3 = lv;  // JDK 1.8 부터는 에러가 아님
      int liv4 = LV;
    }
  }

}

4.4 내부 클래스 예제 4

public class InnerClassEx4 {

  public static void main(String[] args) {
    Outer outer = new Outer();
    Outer.InstanceInner instanceInner = outer.new InstanceInner();
    System.out.println("instanceInner.iv : " + instanceInner.iv);
    System.out.println("Outer.StaticInner.cv : " + Outer.StaticInner.cv);

    Outer.StaticInner staticInner = new Outer.StaticInner();
    System.out.println("staticInner.iv : " + staticInner.iv);
  }

}

class Outer {

  class InstanceInner {
    int iv = 100;
  }

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

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

}

instanceInner.iv : 100
Outer.StaticInner.cv : 300
staticInner.iv : 200

4.5 내부 클래스 예제 5

public class InnerClassEx5 {

  public static void main(String[] args) {
    Outer2 outer2 = new Outer2();
    Outer2.Inner inner = outer2.new Inner();
    inner.method1();
  }

}

class Outer2 {

  int value = 10;

  class Inner {
    int value = 20;
    void method1() {
      int value = 30;
      System.out.println("value : " + value);
      System.out.println("this.value : " + this.value);
      System.out.println("Outer2.this.value : " + Outer2.this.value);
    }
  }

}
value : 30
this.value : 20
Outer2.this.value : 10

5. 익명 클래스(anonymous class)

익명 클래스는 다른 내부 클래스와 달리 이름이 없다. 클래스의 선언과 객체의 생성을 동시에 하기 때문에 단 한번만 사용할 수 있고 오직 하나의 객체만 생성할 수 있는 일회용 클래스 이다. 이름이 없기 때문에 생성자도 가질 수 없고, 조상클래스의 이름이나 구현하고자하는 인터페이스의 이름을 사용하여 정의하기 때문에 하나의 클래스를 상속받는 동시에 인터페이스를 구현하거나 둘 이상의 인터페이스를 구현할 수 없다. 오로지 단 하나의 클래스를 상속받거나 단 하나의 인터페이스를 구현 해야만 한다.

new SuperClassName() {
  // 멤버 선언
};

new InterfaceName() {
  // 멤버 선언
};

5.1 익명 클래스 예제 1

public class InnerClassEx6 {

  Object iv = new Object() { void method() {} };  // 익명 클래스

  static Object cv = new Object() { void method() {} };   // 익명 클래스

  void myMethod() {
    Object lv = new Object() { void method() {} };  // 익명 클래스
  }
}

5.2 익명 클래스 예제 2, 3

public class InnerClassEx7 {
  public static void main(String[] args) {
    Button button = new Button("Start");
    button.addActionListener(new EventHandler());
  }
}

class EventHandler implements ActionListener {
  public void actionPerformed(ActionEvent e) {
    System.out.println("ActionEvent occurred!");
  }
}
public class InnerClassEx8 {
  public static void main(String[] args) {
    Button button = new Button("start");
    button.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        System.out.println("ActionEvent occurred!");
      }
    });
  }
}