Development/JavaEssential

java - 내부클래스(inner class)

linuxism 2012. 11. 7. 15:09


내부클래스의 마지막 !! 익명 클래스입니다. 말그대로 내부클래스의 이름이 존재하지 않는건데요.-ㅅ-; 다음과 같은 인터페이스가 있고, 그 인터페이스를 구현하는 클래스의 이용빈도수가 현저하게 적을 경우, 클래스를 새로 만드는 것은 낭비일 수 있습니다. 그밖에도 급하게 어떤 인터페이스의 인스턴스가 필요한데, 해당 인터페이스를 구현한 클래스도 만들어놓지 않은 경우 등등 익명클래스는 거의 일회성으로 많이 사용됩니다.
아래와 같은 인터페이스가 있고, 그 인터페이스를 구현하는 클래스를 즉석에서 만들면,

interface YunJu {
      
public void merong();
}
 

public class HoHo {
      
public static void main(String[] args) {
            YunJu yj = 
new YunJu(){
              
public void merong() {
                  System.
out.println("쥐님 바보");          
               }             
            };
            yj.merong();
      }
}

{};안에 YunJu인터페이스를 구현한 익명클래스를 만들었다고 볼 수 있습니다. 물론, 인터페이스 뿐만아니라 클래스를 상속하는 익명클래스도 만들 수 있습니다. 방법은 똑같습니다. 다음과 같은 코드가 가능합니다.


출처 - http://happystory.tistory.com/64






Chapter 10. 내부클래스(inner class)


1. 내부클래스(inner class)란?

내부클래스란, 클래스 내에 선언된 클래스이다. 클래스에 다른 클래스 선언하는 이유는 간단하다. 두 클래스가 서로 긴밀한 관계에 있기 때문이다. .
한 클래스를 다른 클래스의 내부클래스로 선언하면 두 클래스의 멤버들간에 서로 쉽게 접근할 수 있다는 것과 외부에는 불필요한 클래스를 감춤으로써 코드의 복잡성을 줄일 수 있다는 장점을 얻을 수 있다. 

.내부클래스의 장점
- 내부클래스에서 외부클래스의 멤버들을 쉽게 접근할 수 있다. 
- 코드의 복잡성을 줄일 수 있다.(캡슐화) 

[참고]내부 클래스는 JDK1.1버젼 이후에 추가된 개념이다. 


왼쪽의 A와 B 두 개의 독립적인 클래스를 오른쪽과 같이 바꾸면 B는 A의 내부클래스(inner class)가 되고 A는 B를 감싸고 있는 외부클래스(outer class)가 된다. 이 때 내부클래스인 B는 외부클래스인 A를 제외하고는 다른 클래스에서 사용되지 않아야한다.

내부클래스는 주로 AWT나 Swing과 같은 GUI어플리케이션의 이벤트처리 외에는 잘 사용하지 않을 정도로 사용빈도가 높지 않으므로 내부클래스의 기본 원리와 특징을 이해하는 정도까지만 학습해도 충분하다. 실제로는 발생하지 않을 경우까지 이론적으로 만들어 내서 고민하지말자.

내부클래스는 클래스 내에 선언된다는 점을 제외하고는 일반적인 클래스와 다르지 않다. 다만 앞으로 배우게 될 내부클래스의 몇 가지 특징만 잘 이해하면 실제로 활용하는데 어려움이 없을 것이다.



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

내부클래스의 종류는 변수의 선언위치에 따른 종류와 같다. 내부클래스는 마치 변수를 선언하는 것과 같은 위치에 선언할 수 있으며, 변수의 선언위치에 따라 인스턴스변수, 클래스변수(static변수), 지역변수로 구분되는 것과 같이 내부클래스도 선언위치에 따라 다음과 같이 구분되어 진다. 내부클래스의 유효범위와 성질이 변수와 유사하므로 서로 비교해보면 이해하는데 많은 도움이 된다.

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

익명클래스
(anonymous class)
클래스의 선언과 객체의 생성을 동시에 하는 이름없는 클래스(일회용)



[표10-1]내부클래스의 종류와 특징
[참고]초기화블럭 관련내용은 1권 p.167를 참고 



3. 내부클래스의 선언 

아래의 오른쪽 코드에는 외부클래스(Outer)에 3개의 서로 다른 종류의 내부클래스를 선언했다. 양쪽의 코드를 비교해 보면 내부클래스의 선언위치가 변수의 선언위치와 동일함을 알 수 있다.
변수가 선언된 위치에 따라 인스턴스변수, 스태틱변수(클래스변수), 지역변수로 나뉘듯이 내부클래스도 이와 마찬가지로 선언된 위치에 따라 나뉜다. 그리고, 각 내부클래스의 선언위치에 따라 같은 선언위치의 변수와 동일한 유효범위(scope)와 접근성(accessibility)을 갖는다.




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

아래코드에서 인스턴스클래스(InstanceInner)와 스태틱클래스(StaticInner)는 외부클래스(Outer)의 멤버변수(인스턴스변수와 클래스변수)와 같은 위치에 선언되며, 또한 멤버변수와 같은 성질을 갖는다.
따라서 내부클래스가 외부클래스의 멤버와 같이 간주되고, 인스턴스멤버와 static멤버간의 규칙이 내부클래스에도 똑같이 적용된다.


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

[예제10-1] InnerEx1.java
class InnerEx1 { 
      class InstanceInner { 
            int iv=100; 
//             static int cv=100;                   // 에러! static변수를 선언할 수 없다. 
            final static int CONST = 100;       // static final은 상수이므로 허용한다. 

      } 
      static class StaticInner { 
            int iv=200; 
            static int cv=200;       //       static클래스만 static멤버를 정의할 수 있다. 
      } 

      void myMethod() { 
            class LocalInner { 
                  int iv=300; 
//                   static int cv=300;                   // 에러! static변수를 선언할 수 없다. 
                  final static int CONST = 300;       // static final은 상수이므로 허용한다. 
            } 
      } 

      public static void main(String args[]) { 
            System.out.println(InstanceInner.CONST); 
            System.out.println(StaticInner.cv); 
      } 
}
[실행결과]
100
200

[참고]final이 붙은 변수는 상수(constant)이기 때문에 어떤 경우라도 static을 붙이는 것이 가능하다.

내부클래스 중에서 스태틱클래스(StaticInner)만 static멤버를 가질 수 있다. 드문 경우지만 내부클래스에 static변수를 선언해야한다면 스태틱클래스로 선언해야한다.
다만 final과 static이 동시에 붙은 변수는 상수이므로 모든 내부클래스에서 정의가 가능하다.


[예제10-2] InnerEx2.java
class InnerEx2 { 
      class InstanceInner {} 
      static class StaticInner {} 

      InstanceInner iv = new InstanceInner(); // 인스턴스 멤버간에는 서로 직접 접근이 가능하다. 
      static StaticInner cv = new StaticInner();    // static 멤버간에는 서로 직접 접근이 가능하다. 

      static void staticMethod() { 
//       static멤버는 인스턴스 멤버에 직접 접근할 수 없다. 
//             InstanceInner obj1 = new InstanceInner();       
            StaticInner obj2 = new StaticInner(); 

//       굳이 접근하려면 아래와 같이 객체를 생성해야한다. 
//       인스턴스 내부클래스는 외부클래스를 먼저 생성해야만 생성할 수 있다. 
            InnerEx2 outer = new InnerEx2(); 
            InstanceInner obj1 = 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(); 
      } 
}

인스턴스멤버는 같은 클래스에 있는 인스턴스멤버와 static멤버 모두 직접 호출이 가능하지만, static멤버는 인스턴스멤버를 직접 호출할 수 없는 것처럼, 인스턴스클래스는 외부클래스의 인스턴스멤버를 객체 생성없이 바로 사용할 수 있지만, 스태틱클래스는 외부클래스의 인스턴스멤버를 객체 생성없이 사용할 수 없다.

마찬가지로 인스턴스클래스는 스태틱클래스의 멤버들을 객체생성없이 사용할 수 있지만, 스태틱클래스에서는 인스턴스클래스의 멤버들을 객체생성없이 사용할 수 없다.


[예제10-3] InnerEx3.java
class InnerEx3 { 
      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; 

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


내부클래스에서 외부클래스의 변수들에 대한 접근성을 보여 주는 예제이다. 내부클래스의 전체 내용 중에서 제일 중요한 부분이므로 잘봐두도록 하자.

 

인스턴스클래스(InstanceInner)는 외부클래스(InnerEx3)의 인스턴스멤버이기 때문에 인스턴스변수 outerIv와 static변수 outerCv를 모두 사용할 수 있다. 심지어는 outerIv의 접근제어자가 private일지라도 사용가능하다. 

스태틱클래스(StaticInner)는 외부클래스(InnerEx3)의 static멤버이기 때문에 외부클래스의 인스턴스멤버인 outerIv와 InstanceInner를 사용할 수 없다. 단지 static멤버인 outerCv만을 사용할 수 있다. 

지역클래스(LocalInner)는 외부클래스의 인스턴스멤버와 static멤버를 모두 사용할 수 있으며, 지역클래스가 포함된 메서드에 정의된 지역변수도 사용할 수 있다.
 단, final이 붙은 지역변수만 접근가능한데 그 이유는 메서드가 수행을 마쳐서 지역변수가 소멸된 시점에도, 지역클래스의 인스턴스가 소멸된 지역변수를 참조하려는 경우가 발생할 수 있기 때문이다.


[예제10-4] InnerEx4.java
class Outer { 
      class InstanceInner { 
            int iv=100; 
      } 
      static class StaticInner { 
            int iv=200; 
            static int cv=300; 
      } 

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


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

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

            // Static내부클래스의 인스턴스는 외부클래스를 생성하지 않아도 된다. 
            Outer.StaticInner si = new Outer.StaticInner(); 

            System.out.println("si.iv : "+ si.iv); 
      } 
}
[실행결과]
ii.iv : 100
Outer.StaticInner.cv : 300
si.iv : 200


외부클래스가 아닌 다른 클래스에서 내부클래스를 생성하고 내부클래스의 멤버에 접근하는 예제이다. 실제로 이런 경우가 발생했다는 것은 내부클래스로 선언해서는 안되는 클래스를 내부클래스로 선언했다는 의미이다. 참고로만 봐두고 가볍게 넘어가도록 하자.

위 예제를 컴파일했을 때 생성되는 클래스 파일은 다음과 같다. 

InnerEx4.class
Outer.class
Outer$InstanceInner.class
Outer$StaticInner.class
Outer$1LocalInner.class


컴파일 했을 때 생성되는 파일명은 '외부클래스명$내부클래스명.class'형식으로 되어 있다. 다만 서로 다른 메서드 내에서는 같은 이름의 지역변수를 사용하는 것이 가능한 것처럼, 지역내부클래스는 다른 메서드에 같은 이름의 내부클래스가 존재할 수 있기 때문에 내부클래스명 앞에 숫자가 붙는다. 


만일 오른쪽의 코드를 컴파일 한다면 다음과 같은 클래스파일이 생성될 것이다.


Outer.class
Outer$1LocalInner.class
Outer$2LocalInner.class


[예제10-5] InnerEx5.java
class Outer { 
      int value=10;       // Outer.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("Outer.this.value :" + Outer.this.value); 
            } 
      } // Inner클래스의 끝 
// Outer클래스의 끝 

class InnerEx5 { 
      public static void main(String args[]) { 
            Outer outer = new Outer(); 
            Outer.Inner inner = outer.new Inner(); 
            inner.method1(); 
      } 
// InnerEx5 끝
[실행결과]
value :30 
this.value :20 
Outer.this.value :10 

위의 예제는 내부클래스와 외부클래스에 선언된 변수의 이름이 같을 때 변수 앞에 'this' 또는 '외부클래스명.this'를 붙여서 서로 구별할 수 있다는 것을 보여준다.



5. 익명클래스(anonymous class)

이제 마지막으로 익명 클래스에 대해서 알아보도록 하자. 익명클래스는 특이하게도 다른 내부클래스들과는 달리 이름이 없다. 클래스의 선언과 객체의 생성을 동시에 하기 때문에 한 단번만 사용될 수 있고 오직 하나의 객체만을 생성할 수 있는 일회용 클래스이다. 


이름이 없기 때문에 생성자도 가질 수 없으며, 조상클래스의 이름이나 구현하고자 하는 인터페이스의 이름을 사용해서 정의하기 때문에 하나의 클래스로 상속받는 동시에 인터페이스를 구현하거나 하나 이상의 인터페이스를 구현할 수 없다. 오로지 단 하나의 클래스를 상속받거나 단 하나의 인터페이스만을 구현할 수 있다.

익명클래스는 구문이 다소 생소하지만, 인스턴스클래스를 익명클래스로 바꾸는 연습을 몇 번만 해 보면 금새 익숙해 질 것이다.


[예제10-6] InnerEx6.java
class InnerEx6{ 
      Object iv = new Object(){ void method(){} };             // 익명클래스 
      static Object cv = new Object(){ void method(){} };       // 익명클래스 

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

위의 예제는 단순히 익명클래스의 사용 예를 보여 준 것이다. 이 예제를 컴파일 하면 다음과 같이 4개의 클래스파일이 생성된다.


InnerEx6.class
InnerEx6$1.class ← 익명클래스
InnerEx6$2.class ← 익명클래스
InnerEx6$3.class ← 익명클래스


익명클래스는 이름이 없기 때문에 '외부클래스명$숫자.class'의 형식으로 클래스파일명이 결정된다.


[예제10-7] InnerEx7.java
import java.awt.*; 
import java.awt.event.*; 

class InnerEx7 

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


class EventHandler implements ActionListener 

      public void actionPerformed(ActionEvent e) { 
            System.out.println("Action Event occured!!!"); 
      } 


[예제10-8] InnerEx8.java
import java.awt.*; 
import java.awt.event.*; 

class InnerEx8 

      public static void main(String[] args) 
      { 
            Button b = new Button("Start"); 
            b.addActionListener(new ActionListener() { 
                        public void actionPerformed(ActionEvent e) { 
                              System.out.println("Action Event occured!!!"); 
                        } 
                  }       // 익명 클래스의 끝 
            ); 
      }       // main메서드의 끝 
}       // InnerEx8클래스의 끝

예제 InnerEx7.java를 익명클래스를 이용해서 변경한 것이 예제 InnerEx8.java이다. 먼저 두 개의 독립된 클래스를 작성한 다음에, 다시 익명클래스를 이용하여 변경하면 보다 쉽게 코드를 작성할 수 있다. 






출처 - http://cafe.naver.com/javachobostudy