클래스, 인터페이스의 중첩 선언
클래스 내부에는 멤버 변수, 멤버 함수 등이 있으며, 그 중에는 다른 클래스에 대한 선언 역시 포함 할 수 있습니다. 이처럼 어떤 클래스 내부에 선언된 클래스를 중첩 클래스(Nested class)라고 부릅니다.
[접근 제한자 public|protected|default|private] class [클래스 이름] {
[접근 제한자 public|protected|default|private] class [중첩 클래스 이름] {
}
}
인터페이스 역시 클래스 내부에 선언 될 수 있습니다. 이를 중첩 인터페이스(Nested interface)라고 부릅니다.
[접근 제한자 public|protected|default|private] class [클래스 이름] {
[접근 제한자 public|protected|default|private] interface [중첩 인터페이스 이름] {
}
}
중첩 클래스 또는 중첩 인터페이스를 사용하면 두 클래스 또는 클래스와 인터페이스 간 멤버들에 상호 접근이 용이하고, 복잡한 클래스 관계를 내부에 감싸 외부에는 감출 수 있다는 장점이 있습니다.
중첩 클래스(Nested class)
중첩 클래스는 클래스 내부에서 선언되는 위치에 따라서 사용성 및 접근 방법에 차이가 있습니다.
구분 | 선언 위치 | 비고 | |
멤버 클래스 | 인스턴스 멤버 클래스 | 클래스의 멤버 | 객체를 생성해야만 사용 가능 |
정적 멤버 클래스 | 클래스의 멤버 | 외부에서도 직접 사용 가능 | |
로컬 클래스 | 클래스의 메소드 | 메소드가 실행되는 블록 내부에서만 사용 가능 |
중첩 클래스 내부에 선언된 클래스 역시 하나의 클래스이기 때문에 컴파일 시 별도의 바이트 코드 파일(.class)이 생성됩니다. 이때 바이트 코드 파일의 이름은 다음 규칙대로 명명됩니다.
A$B.class
A는 바깥 클래스의 이름을 의미하고, B는 멤버 클래스를 의미합니다. 클래스가 중첩되면 $로 바깥 클래스와 내부 클래스를 구분합니다.
인스턴스 멤버 클래스
멤버 클래스의 일종인 인스턴스 멤버 클래스는 static 키워드 없이 선언된 중첩 클래스를 의미합니다. 인스턴스 멤버 클래스의 내부에는 인스턴스 필드와 메소드는 선언이 가능하나, 정적 필드와 메소드는 선언 할 수 없습니다.
public class OuterClass {
public class InnerClass {
// 인스턴스 멤버 클래스는 인스턴스 필드와 인스턴스 메소드 선언 가능
public int some_instance_field;
public int some_instance_moethod() { return 0; }
// 인스턴스 멤버 클래스는 정적 필드와 정적 메소드의 선언 불가능
public static int some_static_field; // compile error
public static int some_static_method() { return 0; } // compile error
}
}
인스턴스 멤버 클래스를 외부에서 접근하고자 하는 경우, 먼저 외부 클래스를 인스턴싱해야 합니다. 그 다음 내부 클래스를 인스턴싱하여 접근합니다.
// 외부에서 직접 접근 불가능
// OuterClass.InnerClass innerClass = new OuterClass.InnerClass();
// 외부에서는 바깥 클래스를 먼저 인스턴싱 후 내부 클래스에 접근 가능
OuterClass outerClass = new OuterClass();
OuterClass.InnerClass innerClass = outerClass.new InnerClass();
정적 멤버 클래스
멤버 클래스의 또 다른 형태인 정적 멤버 클래스는 static 키워드로 선언된 내부 클래스를 의미합니다. 인스턴스 멤버 클래스와 달리 정적 멤버 클래스는 모든 종류의 필드와 메소드를 선언 할 수 있습니다.
public class OuterClass {
public static class InnerClass {
// 정적 멤버 클래스는 모든 종류의 필드와 메소드를 선언 가능
public int some_instance_field;
public int some_instance_moethod() { return 0; }
// 정적 멤버 클래스는 모든 종류의 필드와 메소드를 선언 가능
public static int some_static_field;
public static int some_static_method() { return 0; }
}
}
정적 멤버 클래스는 외부에서 직접 접근이 가능합니다. 마찬가지로 정적 멤버 클래스의 정적 멤버에 대해서도 외부에서 직접 접근 할 수 있습니다.
// 외부에서 직접 접근 가능
OuterClass.InnerClass innerClass = new OuterClass.InnerClass();
// 외부에서 내부 클래스의 정적 멤버에 직접 접근 가능
OuterClass.InnerClass.some_static_method();
로컬 클래스
중첩 클래스는 메소드 내부에서도 선언 될 수 있습니다. 이를 로컬 클래스라고 부릅니다. 로컬 클래스의 선언에는 접근 제한자(public, protected, default, private 키워드) 및 static 키워드를 사용 할 수 없습니다. 로컬 클래스는 외부에서의 접근이 완전히 불가능하고, 오직 메소드 내부에서만 사용되기 때문입니다.
로컬 클래스는 인스턴스 멤버 클래스처럼 인스턴스 필드와 인스턴스 메소드만 선언이 가능하며, 정적 멤버는 선언이 불가능합니다. 이 역시 로컬 클래스가 외부에서는 접근이 불가능하다는 점에서 기인합니다.
public class OuterClass {
public void some_outer_method() {
// 로컬 클래스 선언에는 접근 제한자와 static 키워드 사용 불가능
class InnerClass {
// 로컬 클래스는 인스턴스 필드와 인스턴스 메소드 선언 가능
public int some_instance_field;
public int some_instance_moethod() { return 0; }
// 로컬 클래스는 정적 필드와 정적 메소드 선언 불가능
public static int some_static_field; // compile error
public static int some_static_method() { return 0; } // compile error
}
}
}
로컬 클래스는 클래스의 선언을 포함하는 메소드 내부에서만 접근 가능합니다. 따라서 사용성이 극히 제한되며, 보통은 비동기 처리를 위해서 스레드 객체를 만들 때 사용됩니다. 이와 관련된 내용은 스레드와 함께 별도의 섹션으로 다루도록 하겠습니다.
public void some_outer_method() {
class InnerClass {
public int some_instance_field;
public int some_instance_moethod() { return 0; }
}
// 로컬 클래스는 메소드 내부에서만 접근 가능
InnerClass innerClass = new InnerClass();
innerClass.some_instance_moethod();
}
중첩 클래스에서 바깥 클래스의 멤버 접근
클래스 내부에서 자신의 멤버에 접근하는 키워드는 this 입니다. 중첩 클래스 구조에서 내부 클래스가 this 키워드를 사용하면 내부 클래스의 멤버에 접근이 가능합니다. 만약, 내부 클래스에서 외부 클래스의 멤버에 접근하고자 하는 경우는 어떻게 해야할까요?
예시 코드처럼 바깥 클래스 이름을 this 키워드에 선행되게끔 하여 외부 클래스의 멤버에 접근이 가능합니다.
public class OuterClass {
public int some_outer_field = 10;
public void some_outer_method() {
class InnerClass {
public int some_instance_field;
// 바깥 클래스의 멤버에 접근
public int some_instance_moethod() { return OuterClass.this.some_outer_field; }
}
}
}
중첩 인터페이스(Nested interface)
중첩 클래스처럼, 어떤 클래스의 내부에 선언된 인터페이스를 중첩 인터페이스라고 부릅니다. 인터페이스를 클래스 내부에서 선언하는 이유는 해당 클래스와의 긴밀한 관계를 맺는 클래스 구현을 위해서입니다.
특히 UI 프로그래밍에서 이벤트를 처리할 목적으로 많이 사용됩니다. 예를 들어, 버튼 클릭에 대한 처리를 어떤 객체로부터 수행되길 원하는 UI를 구현해야 하는 상황입니다. 이때 버튼 객체는 아무런 객체나 받아들이면 안되고, 반드시 버튼 클릭에 대한 콜백 처리가 구현되어 있는 객체이기를 바랄것입니다. 이런 상황에서 중첩 인터페이스를 사용하여 객체가 구현하는 내용을 강요 할 수 있습니다.
public class UIButton {
// 중첩 인터페이스 선언
interface OnClickListener {
void onClick();
}
// 버튼 클릭에 대한 이벤트 리스너
private OnClickListener listener;
// 이벤트 리스너를 버튼에 할당
public UIButton(OnClickListener _listener) {
listener = _listener;
}
// 버튼을 터치하면 이벤트 리스너를 호출
public void touch() {
listener.onClick();
}
}
'Java > Java SE, EE' 카테고리의 다른 글
[Java/Java SE, EE] final과 non-final, 그리고 effective-final의 차이 (0) | 2022.03.14 |
---|---|
[Java/Java SE, EE] 익명(Annnonymous) 객체 (0) | 2022.03.14 |
[Java/Java SE, EE] 인터페이스(Interface) (0) | 2022.02.21 |
[Java/Java SE, EE] 추상(Abstract) 클래스 (0) | 2022.02.20 |
[Java/Java SE, EE] 클래스 상속(Inheritance) (0) | 2022.02.20 |