Java/객체지향

[객체지향][자바의 정석] - 7.8 내부 클래스(Inner class)

얄루몬 2021. 12. 14. 17:11

7.8 내부 클래스(Inner class)

 

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

  • 클래스 안에 선언된 클래스
  • 특정 클래스 내에서만 주로 사용되는 클래스를 내부 클래스로 선언한다.
  • GUI어플리케이션(AWT, Swing)의 이벤트처리에 많이 사용된다.

 

내부 클래스의 장점(A클래스(외부클래스) 안에 B클래스(내부 클래스)를 넣는 경우)

  • 내부 클래스에서 외부 클래스의 멤버들을 쉽게 접근할 수 있다.
  • 코드의 복잡성을 줄일 수 있다. (캡슐화)
package javaStandard;
class A{//외부 클래스
	
	
	class B{//내부 클래스
	/*1. 객체 생성 없이도 A의 멤버에 접근이 가능하고
	  2. B가 A의 외부에서 사용할 필요가 없다면 A 안에서만 사용하면 복잡성이 줄어듬*/
		
	}
}

 

 

<외부클래스인 B클래스에서 외부 클래스인 A의 객체를 사용하기 위한 방법>

package javaStandard;
class AAA{
	int i = 100;
	BBB b = new BBB(); // A 클래스에서 B을 사용하기 위해서	
}

class BBB{
	void method() {
		AAA a = new AAA(); //B 클래스 내에서 A의 멤버에 접근하기 위해서 
		System.out.println(a.i);
		}
}

class CCC{
	BBB b = new BBB();
}

public class InnerTest{
	public static void main(String[] args) {
		BBB b = new BBB();
		b.method();
	}
}

# B에서 A의 객체를 사용하기 위해서는 객체 생성을 해주어야 한다.

 

 

<내부 클래스로 만들어 A의 객체를 사용하는 방법 >

package javaStandard;
class AAA{ //AAA는 BBB클래스의 외부 클래스
	int i = 100;
	BBB b = new BBB(); // A 클래스에서 B을 사용하기 위해서	
	
	class BBB{ //BBB는 AAA 클래스의 내부 클래스
		void method() {
			System.out.println(i); // 객체 생성 없이 외부 클래스의 멤버 접근 가능
			}
	}
}


public class InnerTest{
	public static void main(String[] args) {
		/*BBB 클래스는 더이상 독립적인 클래스가 아니기 때문에
		  먼저 AAA클래스 객체를 생성한 뒤 사용해야 한다.
		  BBB 클래스는 AAA 클래스의 멤버처럼 다루어지게 된다!!
          사용법은 다음 시간에 알아보도록 ~ */
	}
}

#inner class로 만드는 이유는 외부 클래스에서만 그 내부 클래스를 사용하려 할 때임. 

 

 

 

 

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

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

 

 

3. 내부 클래스의 선언

public class Outer {
	int iv = 0;//인스턴스변수
	static int cv = 0; //클래스변수(static)
	void myMethod() {
		int lv = 0;//지역변수 --> 함수내에 존재하는 변수 !!
	}
}

↑↓

public class Outer2 {
	class InstanceInner{ }
	static class StaticInner { }
	
	void myMethod2() {
		class LocalInner{ }
	}
}

위의 코드는 외부 클래스(Outer)에 3개의 서로 다른 종류의 내부 클래스를 선언했다. 위 아래의 양쪽 코드를 비교해 보면 내부 클래스의 선언 위치가 변수의 선언위치와 동일함을 알 수 있다.

 

변수가 선언된 위치에 따라 인스턴스변수, 클래스변수(Static), 지역변수로 나뉘듯이 내부 클래스도 이와 마찬가지로 선언된 위치에 따라 나뉜다. 그리고 각 내부 클래스의 선언 위치에 따라 같은 선언위치의 변수와 동일한 유효범위(scope)와 접근성을 갖는다. 

 

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

  • 내부 클래스의 접근 제어자는 변수에 사용할 수 있는 접근제어자와 동일
  • static 클래스만 static 멤버를 정의할 수 있다.
  • 내부 클래스도 외부 클래스의 멤버로 간주되며, 동일한 접근성을 갖는다.
  • 외부 클래스의 지역변수 final이 붙은 변수(상수)만 접근 가능하다. 지역 클래스의 인스턴스가 소멸된 지역변수를 참조할 수 있기 때문이다.

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

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

 

 

public 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 LacalInner {
			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);
	}

}

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

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

👉 내부 클래스 중에 스태틱 클래스만 static멤버를 가질 수 있다. (드문 경우지만 내부 클래스에 static변수를 선언해야 한다면 스태틱 클래스로 선언해야 한다.)

👉 다만 final과 static이 동시에 붙은 변수는 상수(Constant)이기 때문에 모든 내부 클래스에서 정의가 가능하다. 

 

<출력 결과>
100
200

 

 

package javaStandard;

public class InnerEx2 {
	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();	
		
		// 굳이 스태틱 멤버에서 인스턴스 멤버에 접근을 하려면 아래와 같이 해야 한다.
		InnerEx2 outer = new InnerEx2(); //인스턴스 클래스 외부 클래스를 먼저 생성해야 한다.
		InstanceInner obj1 = outer.new InstanceInner();
	}
	
	void instanceMethode() {
		//인스턴스메소드는 스태틱멤버, 인스턴스멤버 모두 접근 가능.
		InstanceInner obj1 = new InstanceInner();
		StaticInner obj2 = new StaticInner();
		
		//메서드 내에 지역적으로 선언된 내부 클래스는 외부에서 접근할 수 없다. 
//		LocalInner lv = new LocalInner();에러!! --> 지역 클래스
	}
	
	void myMethod() {
		class LocalInner{}
		LocalInner lv = new LocalInner();
	}
}

👉 인스턴스멤버는 같은 클래스에 있는 인스턴스멤버와 static멤버 모두 직접 호출이 가능하다.

👉 하지만, static 멤버는 인스턴스멤버를 직접 호출할 수 없다.

👉 인스턴스클래스는 외부클래스의 인스턴스멤버를 객체생성 없이 바로 사용할 수 있다.

👉 스태틱클래스는 외부 클래스의 인스턴스 멤버를 객체 생성 없이 사용할 수 없다.

 

package javaStandard;

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 int LV = 0;//JDK1.8부터 final 생략 가능
		
		class LocalInner{
			int liv = outerIv;
			int liv2 = outerCv;
			//외부 클래스의 지역변수는 final이 붙은 변수(상수)만 접근이 가능다.
			int liv3 = lv; //JDK1.8부터는 에러가 아님 원래는 에러임 
			int liv4 = LV;
		}
	}
	
}

👉 내부 클래스에서 외부 클래스의 변수들에 대한 접근성을 보여주는 예제

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

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

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

👉 JDK1.8에서 편의상 지역클래스에서 접근하는 지역 변수 앞에 final을 생략할 수 있게 바뀌었다. 대신 컴파일러가 자동으로 붙여주며 해당 변수의 값이 바뀌는 문장이 있을 땐 컴파일 에러가 발생한다.  

 

 

package javaStandard;

class Outer3 {
	class InstanceInner{
		int iv = 100;
	}
	
	static class StaticInner {
		int iv = 200;
		static int cv = 300;
	}
	
	void myMethod() {
		class LocalInner{
			int iv = 400;
		}
	}
}

public class InnerEx4 {

	public static void main(String[] args) {
		/* 인스턴스 클래스의 인스턴스를 생성하려면
		외부 클래스의 인스턴스를 먼저 생성해야 한다. */
		Outer3 oc = new Outer3(); //외부클래스의 인스턴트 생성
		Outer3.InstanceInner ii = oc.new InstanceInner(); //인스턴스 클래스의 인스턴스 생성
		
		System.out.println("ii.iv : "+ ii.iv);
		System.out.println("Outer.StaticInner.cv : "+Outer3.StaticInner.cv);
	
		//스태틱 내부 클래스의 인스턴스는 외부 클래스를 먼저 생성하지 않아도 된다.
		Outer3.StaticInner si = new Outer3.StaticInner();
		System.out.println("si,iv : " + si.iv);
	}

}
<출력 결과>
ii.iv : 100
Outer.StaticInner.cv : 300
si,iv : 200

👉 외부 클래스가 아닌 다른 클래스에서 내부 클래스를 생성하고 내부 클래스의 멤버 접근하는 예제이다.

👉 이런 경우 내부 클래스로 선언해서는 안 되는 클래스를 내부 클래스로 선언한 경우다.

 

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


public class InnerEx5 {
	public static void main(String[] args) {
		Outer5 outer5 = new Outer5();
		Outer5.Inner inner = outer5.new Inner();
		inner.method1();
	}
}//InnerEx5  끝

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

 

 

5. 익명 클래스(Anonymous class)

  • 이름이 없는 일회용 클래스, 선언과 동시에 하나의 객체만을 생성 가능하다.
new 조상클래스이름() {
	//멤버 선언
}

또는

new 구현인터페이스이름() {
	//멤버 선언
}
public class InnerEx6 {
	Object iv = new Object() { //익명 클래스
		void method() {};
	};
	static Object cv = new Object() { //익명 클래스
		void method() {};
	};
	void method() { 
		Object lv = new Object() { //익명 클래스
			void method() {}; 
		};
	}
}

 

👉 익명 클래스는 다른 내부 클래스들과 다르게 이름이 없다. 클래스의 선언과 객체의 생성을 동시에 하기 때문에 단 한번만 사용될 수 있고 오직 하나의 객체만을 생성할 수 있는 일회용 클래스이다.