본문 바로가기
Language/Java

[Java] 내부 클래스(inner class),익명 클래스

by 계범 2022. 3. 5.

내부 클래스란?

내부 클래스는 클래스 내에 선언된 클래스이다.

내부 클래스를 쓰는 이유는 두 클래스가 긴밀한 관계기 때문이다.

 

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

 

class A{
	// ...
}

class B{
	// ...
}

// 내부클래스화

class A{ // 외부클래스
	// ...
    class B{ // 내부 클래스
    	//...
    }
    // ...
}

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

 

내부 클래스의 종류와 특징

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

 

내부 클래스의 선언

변수가 선언된 위치에 따라 인스턴스변수, 클래스변수, 지역변수로 나뉘듯,

내부 클래스도 선언위치에 따라 나뉜다.

 

선언위치에 따라 같은 선언위치의 변수와 동일한 유효범위와 접근성을 갖는다.

class Outer{
	class InstanceInner{}
    static class StaticInner{}
    
    void myMethod(){
    	class LocalInner{}
    }
}

 

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

내부 클래스도 클래스이기 때문에 abstract나 final 같은 제어자를 사용할 수 있고,

멤버변수들처럼 private, protected와 같은 접근제어자도 사용이 가능하다.

 

관련 예제 모음

더보기
class InnerEx1 { 
      class InstanceInner { 
            int iv = 100; 
//          static int cv = 100;            // 에러! static변수를 선언할 수 없다. 
            final static int CONST = 100;   // final static은 상수이므로 허용한다. 
      } 

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

      public static void main(String args[]) { 
            System.out.println(InstanceInner.CONST); 
            System.out.println(StaticInner.cv); 
      } 
}
class InnerEx2 {
	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();

        //	굳이 접근하려면 아래와 같이 객체를 생성해야 한다.
        //	인스턴스클래스는 외부 클래스를 먼저 생성해야만 생성할 수 있다.
		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();
	}
}
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 int LV = 0;  // JDK1.8부터 final 생략 가능
	
		class LocalInner {
			int liv = outerIv;
			int liv2 = outerCv;
//	외부 클래스의 지역변수는 final이 붙은 변수(상수)만 접근가능하다.
//			int liv3 = lv;	// 에러!!!(JDK1.8부터 에러 아님)
			int liv4 = LV;	// OK
		}
	}
}
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);

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

위의 경우 접근은 가능하지만, 외부클래스가 아닌 다른클래스에서 접근하므로 내부클래스로 선언하면 안되는 클래스이다. 컴파일 시 생성되는 파일명은 '외부 클래스명$내부 클래스명.class'형식이다.

동일이름으로 여러개일경우, 내부클래스명 앞에 숫자가 붙는다.

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'를 붙여서 서로 구분이 가능하다.

 

익명 클래스(anoymous class)

익명 클래스는 단 한번만 사용 될때 쓴다.

이름이 없고 클래스의 선언과 객체의 생성을 동시에 한다.

 

익명 클래스는 조상클래스의 이름이나 구현하고자 하는 인터페이스의 이름을 사용해서 정의하기 때문에

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

 

 

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

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

 

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

 

참조

'Java의 정석' 책

댓글