Programming Language/Java

[Java] 11. OOP - 내부 클래스(Inner Class), 익명 클래스

lumana 2024. 7. 7. 22:41

내부 클래스(inner class)

  • 내부 클래스는 AWT나 Swing같은 GUI Application 이벤트 처리를 제외하고는 잘 쓰이지 않는다.

  • 이런 게 이렇게 작동하는구나 정도만 이해하고 넘어가자

내부 클래스란?

  • 내부 클래스: 클래스 내에 선언된 클래스

  • 클래스에 다른 클래스 선언하는 이유 : 두 클래스가 서로 긴밀한 관계에 있기 때문

    • 내부 클래스로 선언하면 두 클래스의 멤버들 간에 서로 쉽게 접근할 수 있다는 장점이 있고, 외부에는 불필요한 클래스를 감춰 코드 복잡성을 줄일 수 있다.
class A {
        //
        class B {
                //
        }
}
  • class A는 외부 클래스, class B는 내부 클래스이다.

    • 이 때 내부 클래스인 B는 외부 클래스인 A를 제외하고는 다른 클래스에서 잘 사용되지 않는 것이어야 한다.

내부 클래스의 종류와 특징

  • 내부 클래스의 종류는 변수의 선언위치에 따른 종류와 같다.

    • 인스턴스 클래스 : 외부 클래스의 멤버변수 선언위치에 선언하며, 외부 클래스의 인스턴스 멤버처럼 다루어진다.

    • 스태틱 클래스 : 외부 클래스의 멤버변수 선언위치에 선언하며, 외부 클래스의 static 인스턴스 멤버처럼 다루어진다.

    • 지역 클래스 : 외부 클래스의 메서드나 초기화 블럭 안에서 선언하며, 선언된 영역 내부에서만 사용될 수 있다.

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

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

  • 내부 클래스의 인스턴스 클래스와 스태틱 클래스는 멤버변수와 같은 성질을 갖는다

    • 즉, 내부 클래스가 외부 클래스의 멤버와 같이 간주된다
  • 내부 클래스도 클래스이기 때문에 abstract나 final과 같은 제어자를 사용할 수 있고, 멤버 변수처럼 private나 protected과 같은 접근 제어자도 사용이 가능하다

Example 7.31

class InnerEx1 {
    class InstanceInner {
        int iv = 100;
//        static int cv = 100;        // 컴파일 에러. staic변수를 선언할 수 없음
        final static int CONST = 100;        // final static은 상수이므로 허용
    }

    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;    // final static은 상수이므로 허용
        }
    }

    public static void main(String args[]) {
        System.out.println(InstanceInner.CONST);
        System.out.println(StaticInner.cv);
    }
}
  • 내부 클래스 중에서 스태틱 클래스만 static 멤버를 가질 수 있다.

    • 드물지만 내부 클래스에 static 변수를 선언해야 한다면 스태틱 클래스로 선언해야 한다
  • 다만 final과 static이 동시에 붙은 변수는 상수이므로 모든 내부 클래스에서 정의가 가능하다

Example 7.33

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;    // JDK1.8부터 final 생략 가능

        class LocalInner {
            int liv = outerIv;    // myMethod()가 static인 경우 컴파일 에러.
            int liv2 = outerCv;

            // 외부클래스의 지역변수는 final이 붙은 변수(상수)만 접근 가능
            int liv3 = lv;    // 컴파일 에러. (JDK1.8부터 에러 아님)
            int liv4 = LV;    // O
        }
    }
}
  • 인스턴스 클래스는 외부 클래스의 인스턴스 멤버이기 때문에 인스턴스 변수 outerIv와 static변수 outerCvf를 모두 사용할 수 있다.

    • 심지어 접근제어자가 private라도 가능하다
  • 스태틱 클래스는 단지 static 멤버인 outerCv만 사용 가능하다

  • 지역 클래스는 외부 클래스의 인스턴스멤버와 static멤버를 모두 사용할 수 있으며, 지역 클래스가 포함된 메서드에 정의된 지역 변수도 사용할 수 있다.

    • 단, final이 붙은 지역변수만 접근가능한데, 메서드가 수행을 마쳐서 지역변수가 소멸된 시점에도, 지역 클래스의 인스턴스가 소멸된 지역변수를 참조하려는 경우가 발생할 수 있기 때문이다.
    • 주의) JDK 1.8부터 지역 클래스에서 접근하는 지역 변수 앞에 final을 생략할 수 있게 바뀌었다. 이걸 모르고 해당 변수 값을 바꾸면 컴파일 에러가 발생한다

Example 7.34

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);
    }
}
  • 외부 클래스가 아닌 다른 클래스에서 내부 클래스를 생성하고 내부 클래스의 멤버에 접근하는 예제인데, 이런 경우가 발생하게 코드를 작성하면 안 됩니다!

Example 7.35

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 끝
  • 내부 클래스와 외부 클래스에 선언된 변수의 이름이 같을 때 변수 앞에 'this'또는 '외부 클래스명.this'를 붙여서 서로 구별할 수 있다.

익명 클래스(anonymous class)

  • 다른 내부 클래스들과 다르게 이름이 없다.

  • 클래스의 선언과 객체의 생성을 동시에 하기 때문에 단 한번만 사용될 수 있고, 오직 하나의 객체만을 생성할 수 있는 일회용 클래스이다.

new 조상클래스이름() {
    // 멤버 선언
}
or
new 구현인터페이스이름() {
    // 멤버 선언
}
  • 이름이 없기 때문에 생성자도 가질 수 없다.

  • 조상클래스의 이름이나 구현하고자 하는 인터페이스의 이름을 사용해서 정의하기 때문에 하나의 클래스로 상속받는 동시에 인터페이스를 구현하거나, 둘 이상의 인터페이스를 구현할 수 없다.

    • 오직 단 하나의 클래스를 상속받거나, 단 하나의 인터페이스만을 구현할 수 있다.

내부 클래스 -> 익명 클래스

내부 클래스

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("ActionEvent occurred!!!");
    }
}

익명 클래스

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("ActionEvent occured!!!");
                }
            }    // 익명클래스의 끝
        );
    }    // main의 끝
}

참조) Java의 정석 3rd edition(남궁성, 도우출판)