본문 바로가기

JAVA/혼공자

[혼공자] Chapter 09-1 중첩 클래스와 중첩 인터페이스 소개

▶ 정의
중첩 클래스 : 클래스 내부에서 선언한 클래스
중첩 인터페이스 : 클래스 내부에서 선언한 인터페이스

▶ 중첩 클래스의 장점
외부에서 필드에 접근하려 하면, 항상 getter, setter을 사용해야 하지만
중첩 클래스나 중첩 인터페이스를 사용하면, 필드에 쉽게 접근할 수 있음
(아무 제한 없이 접근할 수 있는건 아니고 this 키워드 이용해야 함)
외부에 불필요한 클래스를 감춤으로써 코드의 복잡성을 줄일 수 있음

▶ 중첩 클래스의 종류
중첩 클래스는 그 역할에 따라
클래스 멤버로 선언되는 멤버 클래스, 생성자 또는 메소드 내부에서 선언되는 로컬 클래스로 구분되며
멤버 클래스는 인스턴스 멤버 클래스, 정적(static) 멤버 클래스로 구분된다.
* 편의상 바깥 클래스를 A, 중첩 클래스를 B로 둠

출처 https://yuna96.tistory.com/75

① 멤버 클래스
1) 인스턴스 멤버 클래스
- 사용
  A 외부 : A의 객체를 만들어야 사용할 수 있음
  A 내부 : 일반 클래스처럼 바로 사용 가능
  일반적으로 A 외부에서 A 객체를 통해 B의 객체를 만드는 경우는 거의 없음 (그렇게 따로따로 갈거면 중첩을 할 필요가 없으므로)
  대부분 A 내부에서 B를 사용함
- 멤버
  인스턴스 멤버만 선언할 수 있고, 정적 멤버는 선언할 수 없음

2) 정적 멤버 클래스 
- 사용
  A 외부 : A 객체 생성할 필요 없이 A클래스에 dot 연산자로 접근하여(ex A.C) 바로 사용할 수 있음
  A 내부 : 일반 클래스처럼 바로 사용 가능
- 멤버
  인스턴스 멤버, 정적 멤버 모두 선언할 수 있음

② 로컬 클래스
- 사용
  모든 지역 변수가 그러하듯, 로컬 클래스는 속하는 중괄호를 벗어나면 사용할 수 없음
  따라서 메소드가 종료되면 로컬 클래스는 사라짐 (스레드는 예외)
  당연하게도, A의 밖에서 사용 불가능 - 함수가 종료되면 사라지는 클래스 이므로
  주로 비동기 처리를 위해 스레드 객체를 만들 때 사용함
- 멤버
  인스턴스 멤버만 선언할 수 있고, 정적 멤버는 선언할 수 없음
  중괄호 안에서만 사용하고, 함수 호출이 끝나면 메모리에서 지워지므로
  접근 제한자 및 static을 붙일 필요 없음 (붙이면 오류 발생)

정리
중첩 클래스는 멤버로 선언되는지, 메소드 내부에서 선언되는지에 따라 멤버 클래스 / 로컬 클래스로 나뉜다.
특히 멤버 클래스는 인스턴스 / 정적으로 또 나뉜다.
대부분 자바에서 상식적인 내용들이다. 
ex. static 멤버 클래스는 외부에서 객체 없이 사용 가능 / 로컬은 중괄호 벗어나면 사라짐 등등
👉🏻 멤버 클래스 안의 멤버는 '인스턴스 멤버클래스는 인스턴스 멤버만, 정적 멤버 클래스는 둘 다 담을 수 있다'로 기억해두자.

 


▶ 중첩 클래스의 접근 제한
중첩 클래스를 안, 바깥 클래스를 밖으로 생각해보면, 
안에 있는걸 밖에서 사용할 때 & 밖에 있는걸 안에서 사용할 때 각각 제한이 따른다.

밖에서 안에 있는걸 사용할 때 ))
바깥 클래스의 정적 필드, 정적 메소드에서 중첩된 인스턴스 멤버 클래스를 사용하지 못함
인스턴스 필드, 인스턴스 메소드에서는 인스턴스 멤버 클래스가 사용될 수 있지만,
정적 필드, 정적 메소드에서는 인스턴스 멤버 클래스가 사용되지 못함
※ 반면, 바깥 클래스에서 중첩된 static 멤버 클래스를 사용할 때 어디에든 사용할 수 있음
정리 : 밖에서 안에 있는 것 쓸 때, 밖의 static 멤버에서는 안의 인스턴스 클래스 사용할 수 없다.

안에서 밖에 있는걸 사용할 때 ))
중첩된 static 멤버 클래스는 바깥 클래스의 인스턴스 멤버를 사용할 수 없음
바깥 클래스의 정적 멤버는 이용 가능하지만, 인스턴스 멤버를 사용할 수 없음
반면, 중첩된 인스턴스 멤버 클래스는 바깥 클래스의 모든 멤버를 사용할 수 있음
정리 : 안에서 밖에 있는 것 쓸 때, 안의 static클래스에서는 밖의 인스턴스 멤버 사용할 수 없다.

👉🏻 외부 클래스와 멤버 클래스의 관계에서 instance는 둘 다 사용할 수 있지만, static은 static만 사용할 수 있다.

⭐ 총정리 ⭐
중첩클래스의 멤버에 있어서, instance 클래스는 instance 멤버만 담을 수 있다.
중첩클래스와 외부 클래스의 상호작용에 있어서,
static 외부 필드/static 멤버클래스는  static 멤버클래스/static 외부 필드만 사용할 수 있다.

class {
    // field
   instance field can use all member class
   static field can only use static member class
    // member class
   instance class { instance O, static X } // instance member class can use every field
   static class { instance O, static O } // static member class can only use static field
   method( ) {
      instatnce class {instance O, static X}
      static class X
   }
}


▶ 로컬 클래스의 사용 제한
(복습) 로컬 클래스 : 메소드 내부에서 선언된 클래스
로컬 클래스는 메소드 실행이 종료되면 없어지는게 일반적이지만, 메소드가 종료되어도 계속 실행 상태로 존재할 수 있다.
하지만 메소드가 종료될 때 메소드의 로컬 변수와 매개변수도 사라지므로
중첩 클래스가 로컬 변수와 매개변수를 사용할 수 없게되는 문제가 발생한다. 
자바는 이 상황을 해결하기 위해 로컬 클래스가 사용하는 로컬 변수와 매개변수를 컴파일 시점에 복사해둔다.
값을 복사하는 시점이 컴파일 시점이므로 컴파일 이후 실행 시점에서 값이 달라지면,
로컬 클래스에서 사용하기로 처음 의도한 값과 실제로 들어가는 값이 달라지는 에러가 발생한다.
Ex. 컴파일 시점에서 로컬 클래스에 매개변수 2를 복사해뒀는데
이후 매개변수를 0으로 수정하는 코드가 있다면 개발자의 의도인 0과 달리 2가 전달되는 에러가 발생한다. 
따라서 자바에서는 로컬 클래스에서 사용하는 로컬 변수와 매개변수에 수정되지 않는 final의 특성을 부여한다.

public class Outter {
	public void method(int arg) {
		int localVariable = 0;
		
		// 중첩 클래스에서 사용하는 매개변수와 필드는 final이 되므로 수정되지 못한다.
		// arg = 10;
		// localVariable = 10;
		
		class Inner{
			public void method2() {
				int result = arg + localVariable;
			}
		}
	}
}



▶ 바깥 클래스의 참조 얻기
클래스 내부에서 this는 자기 자신의 참조이다.
따라서 자신을 참조하려면 this.필드 or this.메소드( )를 사용하면 된다.
바깥 클래스를 참조하려면 바깥클래스.this.필드 or 바깥클래스.this.메소드( )를 사용하면 된다.

▶ 중첩 인터페이스
해당 클래스와 긴밀한 관계를 맺는 구현 클래스를 만들기 위해 
클래스 내부에 인터페이스를 선언하는 것을 중첩 인터페이스라고 한다.
인터페이스도 클래스와 마찬가지로 인스턴스 / static으로 선언할 수 있다. 
주로 정적 멤버 인스턴스를 UI 프로그래밍에서 이벤트를 처리할 목적으로 사용한다. (예제를 통해 이해하는게 빠름!)
중첩 인터페이스에는 static, instance 간의 접근에 대해 책에서 설명하지 않았는데, 
이는 인터페이스 자체의 특성 때문인 것 같다.

일단 인터페이스에서는 인스턴스 필드를 선언하지 않고 상수 필드(static final)만 선언하고,
인스턴스 메소드라고 할 수 있는 추상 메소드는 내부를 구현하지 않고, 드물게 default 메소드를 선언하지만
이외에는 static 메소드만 선언하기 때문이다. (애초에 인스턴스 내부에 있는 것을 사용할 것 같진 않을 것 같은 느낌)

Button.java - Button 안에서 정의된 인스턴스를 구현하는 객체를 처리하려 함

public class Button {
	// 정적 멤버 인터페이스
	static interface OnClickListener{ 
		// 추상 메소드
		void onClick();
	}

	// 인터페이스 타입 필드
	OnClickListener listener; 
	
	// 인터페이스 타입 필드를 구현 객체로 초기화하는 함수
	void setOnClickListener(OnClickListener listener) { // 매개변수 다형성
		this.listener = listener;
	}
	
	void touch() {
		// 구현 객체의 onClick() 메소드 호출
		listener.onClick(); 
	}
}


CallListener.java - 중첩 인터페이스 구현 클래스 1

public class CallListener implements Button.OnClickListener{
	@Override
	public void onClick() {
		System.out.println("전화를 겁니다.");
	}
}


MessageListener.java - 중첩 인터페이스 구현 클래스 2

public class MessageListener implements Button.OnClickListener{
	@Override
	public void onClick() {
		System.out.println("메세지를 보냅니다.");
	}
	// cf. public 안하면 인터페이스를 default로 받은게 되므로 오류 발생
}


ButtonExample.java - 구현 객체를 전달해서 다형성 구현

public class ButtonExample {

	public static void main(String[] args) {
		Button button = new Button();
		button.setOnClickListener(new CallListener());
		button.touch();
		button.setOnClickListener(new MessageListener());
		button.touch();
	}

}

cf. 위 코드들은 모두 하나의 패키지 안에 담겼으므로 import 하지 않아도 된다.

 


public static void main(String[] args) 함수 안에서
static이 아닌 함수를 호출할 수 없는 것도 위 규칙과 연관이 되는걸까?