[자바의 정석 기초편] 내부 클래스

JEREGIM·2023년 1월 24일
0

📌내부 클래스(Inner class)

클래스 안의 클래스

class A {
	...
    class B {
    	...
    }
}    
  • A 클래스는 B의 외부 클래스
  • B 클래스는 A의 내부 클래스

내부 클래스의 장점

  1. 내부 클래스에서 외부 클래스의 멤버들을 쉽게 접근할 수 있다.
    -> 객체 생성 없이 외부 클래스의 멤버 접근 가능
  1. 코드의 복잡성을 줄일 수 있다.(캡슐화)
    -> B 클래스가 A 클래스와 관련된 일만 수행한다면 굳이 A 클래스 밖에 있을 필요가 없다.

내부 클래스의 종류와 특징

내부 클래스의 종류와 유효범위(scope)는 변수와 동일

class Outer {
	class InstanceInner {}
    static class StaticInner {}
    
    void method() {
    	class LocalInner {}
    }
}
  1. class InstanceInner {} : 인스턴스 내부 클래스, 인스턴스 변수(iv)와 특징이 같다.

  2. static class StaticInner {} : 스태틱 내부 클래스, 스태틱 변수(cv)와 특징이 같다.

  3. class LocalInner {} : 지역 내부 클래스, 지역 변수(lv)와 특징이 같다.

  4. 익명 클래스 : 클래스의 선언과 객체 생성을 동시에 하는 이름 없는 클래스(일회용)
    -> 주로 이벤트를 처리하는데 사용

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

내부 클래스의 제어자는 변수에 사용 가능한 제어자와 동일

  • 원래 클래스의 접근 제어자는 public, default 2개만 사용 가능하지만, 내부 클래스는 private, protected 접근 제어자도 사용 가능하다.

예제 1

class Ex7_12 { 
	class InstanceInner { 
		int iv = 100; 
//		static int cv = 100; // 에러
		final static int CONST = 100;
	} 

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

	void myMethod() { 
		class LocalInner { 
			int iv = 300; 
//			static int cv = 300; // 에러
			final static int CONST = 300;
		}
        
        int i = LocalInner.CONST; // OK
	} 

	public static void main(String[] args) { 
		System.out.println(InstanceInner.CONST); 
		System.out.println(StaticInner.cv);
//      System.out.println(LocalInner.CONST); // 에러
	} 
}
  • static 변수는 static 내부 클래스에서만 정의할 수 있다.
  • final static은 상수이므로 내부 클래스 어디에서나 사용 가능하다.
  • static 내부 클래스에서는 외부 클래스의 인스턴스 멤버에 접근할 수 없다.
  • System.out.println(LocalInner.CONST); : 지역 내부 클래스의 static 상수는 메서드 밖에서 사용 불가, 메서드 내에서만 사용 가능

예제 2

class Ex7_13 {
	class InstanceInner {}
	static class StaticInner {}

	InstanceInner iv = new InstanceInner();
	static StaticInner cv = new StaticInner();

	static void staticMethod() {
//		InstanceInner obj1 = new InstanceInner(); // 에러
		StaticInner obj2 = new StaticInner();
	}

	void instanceMethod() {
		InstanceInner obj1 = new InstanceInner();
		StaticInner obj2 = new StaticInner();
//		LocalInner lv = new LocalInner(); // 에러
	}

	void myMethod() {
		class LocalInner {}
		LocalInner lv = new LocalInner();
	}
}
  • InstanceInner iv = new InstanceInner(); : 인스턴스 멤버끼리 직접 접근 가능
  • static StaticInner cv = new StaticInner(); : static 멤버끼리 직접 접근 가능
  • InstanceInner obj1 = new InstanceInner(); : static 메서드에서 인스턴스 멤버에 직접 접근 불가
	void instanceMethod() {
		InstanceInner obj1 = new InstanceInner();
		StaticInner obj2 = new StaticInner();
//		LocalInner lv = new LocalInner(); // 에러
	}
  • 인스턴스 메서드에서 인스턴스 멤버와 static 멤버 모두 접근 가능

  • LocalInner lv = new LocalInner(); : 지역 내부 클래스는 외부에서 접근 불가

예제 3

class Outer {
	private int outerIv = 0;
	private static  int outerCv = 0;

	class InstanceInner {
		int iiv  = outerIv;
		int iiv2 = outerCv;
	}

	static class StaticInner {
//		int siv = outerIv; // 에러
		static int scv = outerCv;
	}
}
  • int iiv = outerIv; : 외부 클래스의 private 멤버도 접근 가능

  • int siv = outerIv; : static 내부 클래스는 외부 클래스의 인스턴스 멤버에 접근 불가

class Outer {
	void myMethod() {
		int lv = 0; // 값이 바뀌지 않는 변수는 상수로 간주
		final int LV = 0; // 상수 OK

		class LocalInner {
			int liv  = outerIv;
			int liv2 = outerCv;
			int liv3 = lv;  // 에러(JDK1.8부터 에러 아님)
			int liv4 = LV;	// OK
		}
	}
}
  • int liv4 = LV; : 외부 클래스의 지역변수는 final이 붙은 변수(상수)만 접근 가능

  • int liv3 = lv; : JDK1.8부터 에러가 아니다. 원래는 지역 내부 클래스에서 메서드의 지역변수에 접근 불가하지만, JDK1.8부터 변수인데 값이 바뀌지 않으면 상수로 간주하기 때문이다.

class Outer {
	void myMethod() {
		int lv = 0; // 값이 바뀌지 않는 변수는 상수로 간주
		lv = 10;
        
		class LocalInner {
			int liv3 = lv;  // 에러
		}
	}
}
  • lv의 값이 바뀌면 int liv3 = lv; 는 에러가 뜬다.
class Outer {
	void myMethod() {
		int lv = 0; 
		final int LV = 0; 
        lv = 10;

		class LocalInner {
			void LiMethod() {
            System.out.println(lv);
		}
	}
}
  • 지역변수 lv는 myMethod() 종료와 함께 소멸된다. 그러나 지역 내부 클래스 안에 있는 LiMethod() 메서드는 myMethod() 메서드와 상관 없이 더 오래 존재할수도 있기 때문에 지역변수 lv를 사용할 수 없는 것이다.

  • 상수 LV는 constant pool 에서 따로 관리하기 때문에 메서드 종료와 상관 없이 계속 쓸 수 있다. 따라서, 지역 내부 클래스에서 접근이 가능한 것이다.

예제 4

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

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

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

class Ex7_15 {
	public static void main(String[] args) {
		Outer2 oc = new Outer2(); // 외부 클래스의 객체를 먼저 생성
		Outer2.InstanceInner ii = oc.new InstanceInner(); // 내부 클래스 객체 생성

		System.out.println("ii.iv : "+ ii.iv);
		System.out.println("Outer2.StaticInner.cv : "+Outer2.StaticInner.cv);
                                     
		Outer2.StaticInner si = new Outer2.StaticInner();
		System.out.println("si.iv : "+ si.iv);
	}
}
  • Outer2 oc = new Outer2(); : 내부 클래스의 객체를 생성하려면 먼저 외부 클래스의 객체를 생성해야 한다.
    -> 내부 클래스 InstanceInner 또한 인스턴스 멤버이기 때문에

  • Outer2.StaticInner si = new Outer2.StaticInner(); : static 내부 클래스의 인스턴스는 외부 클래스를 먼저 생성하지 않아도 된다.

예제 5

class Outer3 {
	int value = 10;	// Outer3.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("Outer3.this.value : " + Outer3.this.value);
		}
	} // Inner 클래스의 끝
} // Outer3 클래스의 끝

class Ex7_16 {
	public static void main(String args[]) {
		Outer3 outer = new Outer3();
		Outer3.Inner inner = outer.new Inner();
		inner.method1();
	}
}

결과
value : 30
this.value : 20
Outer3.this.value : 10

익명 클래스(Anonymous class)

이름이 없는 일회용 클래스. 정의와 생성을 동시에

new 조상클래스이름() {
	/*클래스 내용*/
}

new 구현 인터페이스이름() {
	/*클래스 내용*/
}

예제

import java.awt.*;
import java.awt.event.*;

class Ex7_18 {
	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!!!");
	}
}

EventHandler 클래스를 익명 클래스로 처리

import java.awt.*;
import java.awt.event.*;

class Ex7_18 {
	public static void main(String[] args) {
		Button b = new Button("Start");
		b.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				System.out.println("ActionEvent occurred!!!");
			}
		});
	}
}
  • 클래스의 정의와 객체 생성을 동시에

0개의 댓글