중첩 클래스는 클래스 내부에 선언한 클래스입니다.
중첩 클래스를 사용하면 두 클래스의 멤버들을 서로 쉽게 사용할 수 있고 외부에는 불필요한 관계 클래스를 감춤으로써 코드의 복잡성을 줄일 수 있다는 장점이 있습니다.
인터페이스도 클래스 내부에 선언할 수 있는데 이를 중첩 인터페이스라 합니다.
인터페이스를 클래스 내부에 선언하는 이유는 해당 클래스와 긴밀한 관계를 맺는 구현 인터페이스를 만들기 위해서입니다.
class Outer{
class Inner{ // 중첩 클래스
}
}
class Outer{
interface Inner{ // 중첩 인터페이스
}
}
class Outer{
class Inner{ ... }
}
Outer 객체를 생성해야만 사용할 수 있는 Inner 클래스
class Outer{
static class Inner{ ... }
}
Outer 클래스로 바로 접근할 수 있는 Inner 클래스
class Outer{
void method(){
static class Inner{ ... }
}
}
method()가 실행할 때만 사용할 수 있는 Inner 클래스
멤버 클래스와 로컬 클래스도 하나의 클래스 이기 때문에 컴파일하면 바이트 코드 파일(.class)이 별도로 생성된다.
Class Outer{
Class Inner{
Inner(){ } // 생성자
int field; // 인스턴스 필드
void method(){ } // 인스턴스 메소드
// static int field2; 정적 필드(X)
// static void method2() { } 정적 메소드(X)
}
}
Outer outer = new Outer();
Outer.Inner inner = Outer.new Inner();
inner.field = 3;
inner.method();
Class Outer{
Class Inner{
Inner(){ }
int field;
void method(){ }
}
void methodOuter(){
Inner inner = new Inner();
inner.field = 3;
inner.method();
}
}
일반적으로 Outer클래스 외부에서 Inner 객체를 생성하는 일은 거의 없다.
Class Outer{
static Class Inner{
Inner(){ } // 생성자
int field; // 인스턴스 필드
void method(){ } // 인스턴스 메소드
static int field2; // 정적 필드
static void method2() { } // 정적 메소드
}
}
Outer.Inner inner = new Outer.Inner();
inner.field = 3; // 인스턴스 필드 사용
inner.method(); // 인스턴스 메소드 호출
Outer.Inner.field = 2; // 정적필드 호출
Outer.Inner.method2(); // 정적메소드 호출
정적 멤버 클래스는 Outer객체를 생성할 필요 없이 Inner 객체를 생성할 수 있다.
Class Outer{
void method(){
class Inner {
Inner() { }
int field1;
void method1() { }
// static int field2; 정적 필드 사용 불가
// static void method2() { } 정적 메소드 사용 불가
}
Inner inner = new Inner();
inner.field1 = 3;
inner.method1();
}
로컬클래스는 접근제한자(public, private) 및 static을 붙일 수 없다. 메소드 내부에서만 사용되므로 접근을 제한할 필요가 없기 때문에
void method() {
class Inner extends Thread { ... }
Inner thread = new Inner();
thread.start();
로컬 클래스는 메소드가 실행될 때 메소드 내에서 객체를 생성하고 사용해야 한다. 주로, 비동기 처리를 위해 스레드 객체를 만들 때 사용
인스턴스 멤버 클래스 안에서는 바깥 클래스의 모든 필드와 메서드에 접근할 수 있지만, 정적 멤버 클래스 안에서는 바깥 클래스의 정적 필드와 정적 메서드에만 접근 가능하다.
메서드의 매개 변수나 로컬 변수를 로컬 클래스에서 사용할 때 제한이 있다. 로컬 클래스의 객체는 메소드 실행이 종료되면 없어지는 것이 일반적이지만, 메소드가 종료되어도 계속 실행 상태로 존재할 수 있다. 예를 들어 로컬 스레드 객체를 사용할 때 이다. 이 문제를 해결하기 위해 컴파일 시 로컬 클래스에서 사용하는 매개 변수가 로컬 변수의 값을 로컬 클래스 내부에 복사해두고 사용한다. 그리고 이러한 매개 변수나 로컬 변수의 값이 수정되는 것을 방지하기 위해 final로 선언할 것을 요구한다.
public class Outer {
int field1;
void method1() { }
static int field2;
static void method2() { }
// 인스턴스 멤버 클래스
class Inner{
void method() {
field1 = 10; // 인스턴스 필드 사용 가능
method1(); // 인스턴스 메소드 호출 가능
field2 = 10; // 정적 필드 사용 가능
method2(); // 정적 메소드 호출 가능
}
}
// 정적 멤버 클래스
static class Inner2{
void method() {
// field1 = 10; 인스턴스 필드 사용 불가능
// method1(); 인스턴스 메소드 호출 불가능
field2 = 2;
method2();
}
}
}
public class Outter {
public void method2(int arg) {
// 로컬 변수
int localVariable = 10; // final로 취급됨
// arg = 100; final로 취급되기 때문에 수정X
// localVariable = 100; final로 취급되기 때문에 수정X
// 로컬 클래스
class Inner {
public void method() {
// 로컬변수와 매개변수에 컴파일할 때 자동으로 final이 붙음
int result = arg + localVariable;
}
}
}
}
💡 중첩 클래스에서 바깥 클래스 참조 얻기
- 바깥클래스.this.필드
- 바깥클래스.this.메소드();
public class Outter { // 바깥클래스 필드 String field = "Outter-field"; // 바깥클래스 메소드 void method() { System.out.println("Outter-method"); } // 중첩(내부)클래스 class Nested{ // 중첩 클래스 필드 String field = "중첨클래스-필드"; // 중첩 클래스 메소드 void method() { System.out.println("중첩클래스-메소드"); } // 내부클래스와 외부클래스 멤버 출력 void print() { // 중첩클래스 참조 System.out.println(this.field); this.method(); // 바깥(외부) 클래스 멤버 참조 System.out.println(Outter.this.field); Outter.this.method(); } } }
중첩 인터페이스는 인스턴스 멤버 인터페이스와 정적 멤버 인터페이스 모두 가능하다.
Class Outer{
[static] interface Inner {
void method();
}
}
인스턴스 멤버 인터페이스는 바깥 클래스의 객체가 있어야 사용 가능하고, 정적 멤버 인터페이스는 바깥 클래스만으로 바로 접근할 수 있다. 주로 정적 멤버 인터페이스를 많이 사용하며 UI프로그래밍에서 이벤트를 처리할 목적으로 많이 활용된다.
public class Button {
// 자바의 다중상속을 정의하는 메소드 선언
// 버튼 클래스와 이벤트가 발생할 때 자동호출(콜백)
// 되는 이벤트 핸들러의 연결코드
OnClickListener listener; // 인터페이스 타입 필드
// 인터페이스를 등록
// 매개변수를 이용한 다형성
void setOnClickListener(OnClickListener listener) {
this.listener = listener;
}
// 실제 하드웨어로 구현하나
// 지금은 시뮬레이션 화면이 있다고 보고 화면을 누르면
void touch() {
listener.onClick(); // 실제 위 인터페이스를 구현할 객체의 onClick() 메소드 호출
}
// 중첩 인터페이스
static interface OnClickListener{
void onClick();
}
}
// 터치를 이용하여 전화를 겁니다.
public class CallListener implements Button.OnClickListener {
@Override
public void onClick() {
System.out.println("전화를 겁니다.");
}
}
// 터치를 이용하여 메세지를 보낸다.
public class MessageListener implements Button.OnClickListener{
@Override
public void onClick() {
System.out.println("메세지를 보냅니다.");
}
}
public class ButtonExample {
public static void main(String[] args) {
Button button = new Button();
// OnClickListener listener = new CallListener()
button.setOnClickListener(new CallListener());
button.touch();
// OnClickListener listener = new MessageListener()
button.setOnClickListener(new MessageListener());
button.touch();
}
}
전화를 겁니다.
메세지를 보냅니다.
💡 이모저모
💬 로컬 클래스는 생성자 또는 메소드 내부에 선언된 클래스를 말한다.