[Java] 객체지향 프로그래밍 - 내부 클래스

최지수·2022년 4월 16일
0

Java

목록 보기
15/27
post-thumbnail

내부 클래스(Inner Class)

클래스 안에 클래스가 선언된다는 점을 제외하고는 일반적인 클래스와 다른건 없어요. 실제로 사용빈도가 높지 않아 기본 원리와 특징을 이해하는 정도까지만 알아둬도 좋아요.

내부 클래스Inner Class는 클래스 내에 선언된 클래스에요. 클래스 안에 다른 클래스를 넣는 이유는 두 크랠스가 상호 긴밀한 관계이기 때문이에요.

한 클래스를 다른 클래스의 내부 클래스로 선언하면 두 클래스의 멤버들 간에 서로 쉽게 접근할 수 있다는 장점과 외부에는 불필요한 클래스를 감추면서 코드의 복잡성을 줄일 수 있어요(캡슐화).

class A {
	class InnerA { }
}

종류와 특징

변수의 선언 위치에 따른 종류와도 같아요. 내부 클래스 선언 위치에 따라 구분을 지을 수 있어요.

1. 인스턴스 클래스
외부 클래스의 멤버 변수 선언 위치에 선언해요. 외부 클래스의 인스턴스 멤버처럼 다뤄져요. 주로 외부 클래스의 인스턴스 멤버들과 관련된 작업에 사용될 목적으로 선언되요.

2. 스태틱(static) 클래스
외부 클래스의 멤버 변수 선언 위치에 선언해요. 외부 클래스의 static 멤버처럼 다뤄져요. 주로 외부 클래스의 static 멤버, 특히 static 메서드에서 사용될 목적으로 선언되요.

3. 지역 클래스
외부 클래스의 메서드나 초기화 블럭 안에 선언해요. 선언된 영역 내부에서만 사용될 수 있어요.

4. 익명 클래스
클래스의 선언과 객체의 생성을 동시에 하는 이름 없는 일회용 클래스에요.

내부클래스도 기존 멤버 변수와 마찬가지로 접근 제어자 선언이 가능해요.

class App {
	private class InstanceInner {			// 인스턴스 클래스
    	// static int cv = 100;				// Error! 인스턴스 클래스는 
        									// static 변수 선언이 안되요
        final static int CONST = 100;		// final static은 상수이므로 허용해요
    }			
    protected static class StaticInner {	// 스태틱 클래스
		int iv = 200;
		static int cv = 200;				// static 클래스만 static 변수 선언 가능
    }
    void method(){
    	class LocalInner {					// 지역 클래스
        	// static int cv = 300;			// Error!
            
	        final static int CONST = 100;		// final static은 상수이므로 허용해요
        }			
    }
}

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

또한 지역 클래스는 외부 클래스의 인스턴스 멤버와 static 멤버를 모두 사용할 수 있어요. 또한 지역 클래스가 포함된 메서드에 정의된 지역 변수도 사용할 수 있어요.

단, final이 붙은 지역 변수만 접근이 가능해요. 그 이유는 메서드가 수행을 마쳐서 지역 변수가 소멸된 시점에도, 지역 클래스의 인스턴스가 소멸된 지역 변수를 참조하는 경우가 있기 때문이에요. JDK1.8부터 final 생략이 가능한데, 이는 암시적으로 final 타입으로 지정하기 때문이에요. 그래서 지역 클래스가 사용하는 지역 변수를 재초기화를 시도하면 컴파일 에러가 발생해요.


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

    class InstanceInner {
        int iiv = outerIv;      // private 멤버 접근 가능
        int iiv2 = outerCv;
    }

    static class StaticInner {
        // 스태틱 클래스는 외부 클래스의 인스턴스 멤버에 접근 못해요
        // int siv = outerIv;
        static int scv = outerCv;
    }

    void myMethod(){
        int lv = 0;			// 지역 클래스에서 사용하면
        					// 자동으로 뒤에 final이 붙어요
        // lv = 1;			// 초기화를 시도하면 컴파일 에러 발생해요
        final int LV = 0;   // 1.8부터 생략 가능해요
        
        
        class LocalInner {
            int liv = outerIv;
            int liv2 = outerCv;
            // 외부 클래스의 지역 변수는 final이 붙은 변수(상수)만 접근 가능해요
            // 는 1.8부터 안 붙어도 가능해요
            int liv3 = lv;		// 1.8 이전에선 안되요
            					// 1.8부터 final을 자동화해줘요
            int liv4 = LV;
        }
    }
}

어떤 클래스의 내부 클래스를 생성하거나 선언해서 사용하는 방법은 아래와 같아요.

package ch7;

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

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

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

public 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);

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

참고로 내부 클래스는 *.class외부 클래스명$내부 클래스명.class로 되어 있어요. 다만 서로 다른 메서드 내에서는 같은 이름의 지역 변수를 사용하는 것처럼, 지역 내부 클래스는 다른 메서드에 같은 이름의 내부 클래스가 있기 때문에 내부 클래스 명 앞에 숫자가 붙어요.

class Outer{
	void method1(){
		class LocalInner {}
    }
    
	void method2(){
		class LocalInner {}
    }
}

이 경우 컴파일하면 다음 같은 클래스 파일들이 생성되요.

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

익명 클래스(Anonymous Class)

익명 클래스Anonymous Class는 특이하게도 다른 내부 클래스들과는 달리 이름이 없어요. 클래스의 선언과 객체 생성을 동시에 하기 때문에 단 한번만 사용할 수 있고, 오직 하나의 객체만을 생성할 수 있는 일회용 클래스에요.

이름이 없기 때문에 생성자도 가질 수 없으며, 부모 클래스의 이름이나 구현하고자 하는 인터페이스의 이름을 사용해서 정의하기 때문에 하나의 클래스로 상속받는 동시에 인터페이스를 구현하거나 둘 이상의 인터페이스를 구현할 수 없어요.

익명 클래스 적용 전

class App {
	Button b = new Button("Start");
    // 익명 클래스를 통해 버튼 기능 설정!
    b.addActionListener(new EventHandler());
}

class EventHandler extends ActionListener {
	public void actionPerformed(ActionEvent e) {
    	System.out.println("ActionEvent occured!");
    }
}

익명 클래스 적용 후

class App {
	Button b = new Button("Start");
    // 익명 클래스를 통해 버튼 기능 설정!
    b.addActionListener(new ActionListener() {
    		public void actionPerformed(ActionEvent e) {
            	System.out.println("ActionEvent occured!");
            }
    	}
    );
}
profile
#행복 #도전 #지속성

0개의 댓글