중첩 클래스(Nested Class)는 다른 클래스 내부에 정의된 클래스입니다. 자바에서는 중첩 클래스를 통해 외부 클래스와 연관된 내부 클래스를 쉽게 정의하고, 클래스 간 관계를 보다 명확히 표현할 수 있습니다. 중첩 클래스는 크게 정적(static) 중첩 클래스와 비정적(non-static) 중첩 클래스(내부 클래스)로 나뉩니다.
종류 | 구현 위치 | 사용할 수 있는 외부 클래스 변수 | 생성 방법 |
---|---|---|---|
인스턴스 내부 클래스 | 외부 클래스 멤버 변수와 동일 | 외부 인스턴스 변수, 외부 전역 변수 | 외부 클래스 인스턴스를 만든 후 생성 |
정적 내부 클래스 | 외부 클래스 멤버 변수와 동일 | 외부 전역 변수 | 외부 클래스 인스턴스 없이도 생성 가능 |
지역 내부 클래스 | 메서드 내부에 구현 | 외부 인스턴스 변수, 외부 전역 변수, 메서드 변수 | 메서드 호출 시 생성, 메서드 내에서만 사용 가능 |
익명 내부 클래스 | 메서드 내부에 구현 | 외부 인스턴스 변수, 외부 전역 변수 | 선언과 동시에 생성 |
package com.exam;
class Outer {
private int x1 = 100; // private 접근 제어자 사용 - 외부 클래스에서 접근 불가
public int x2 = 100; // public 접근 제어자 사용 - 외부 클래스에서 접근 가능
public Outer() {
System.out.println("Outer : " + this);
}
// 내부 클래스 Inner 정의
class Inner {
private int y1 = 200; // private 접근 제어자 사용 - 외부 클래스에서 접근 불가
public int y2 = 200; // public 접근 제어자 사용 - 외부 클래스에서 접근 가능
public Inner() {
// Inner 객체가 생성되었을 때 메시지 출력
System.out.println("Inner : " + this);
}
public void viewInner() {
// 내부 클래스의 메서드 - 내부와 외부 클래스의 멤버 변수 출력
System.out.println("Instance Member Class");
System.out.println("x1 = " + x1); // 외부 클래스의 private 멤버 변수에 접근 가능
System.out.println("x2 = " + x2); // 외부 클래스의 public 멤버 변수에 접근 가능
System.out.println("y1 = " + y1); // 내부 클래스의 private 멤버 변수
System.out.println("y2 = " + y2); // 내부 클래스의 public 멤버 변수
}
}
}
package com.exam;
public class InnerEx01 {
public static void main(String[] args) {
// Outer 클래스의 객체 생성
Outer outer = new Outer();
// Outer 클래스의 public 멤버 변수 접근 및 출력
//System.out.println(outer.x1); // private access
System.out.println(outer.x2);
// Outer 클래스의 Inner 인스턴스 멤버 클래스 객체 생성
Outer.Inner inner = outer.new Inner();
// Inner 클래스의 public 멤버 변수 접근 및 출력
//System.out.println(inner.y1); // private access
System.out.println(inner.y2);
// Inner 클래스의 메서드 호출하여 내부 정보 출력
inner.viewInner();
}
}
Outer : com.exam.Outer@6acbcfc0
100
Inner : com.exam.Outer$Inner@6f496d9f // Outer - Inner 다른 참조값
200
viewInner 호출
100
100
200
200
package com.exam2;
public class Outer {
private int x1 = 100; // private 접근 제어자 사용 - 외부 클래스에서 접근 불가
public int x2 = 100; // public 접근 제어자 사용 - 외부 클래스에서 접근 가능
// 정적(static) 중첩 클래스 Inner 정의
static class Inner {
private int y1 = 200; // private 접근 제어자 사용 - 외부 클래스에서 접근 불가
public int y2 = 200; // public 접근 제어자 사용 - 외부 클래스에서 접근 가능
public Inner() {
// Inner 객체가 생성되었을 때 메시지 출력
System.out.println("Inner : " + this);
}
void viewInner() {
// Inner 클래스의 메서드 - static 클래스이므로 외부 클래스 인스턴스 변수 x1, x2에는 접근 불가
System.out.println("viewInner 호출");
// System.out.println("x1 = " + x1); // 접근 불가
// System.out.println("x2 = " + x2); // 접근 불가
System.out.println("y1 = " + y1); // 내부 클래스의 private 멤버 변수
System.out.println("y2 = " + y2); // 내부 클래스의 public 멤버 변수
}
}
}
package com.exam2;
public class InnerEx01 {
public static void main(String[] args) {
// Outer 클래스의 정적(static) 중첩 클래스 Inner 객체 생성
Outer.Inner inner = new Outer.Inner();
// Inner 클래스의 public 멤버 변수 접근 및 출력
//System.out.println(inner.y1); // private access
System.out.println(inner.y2);
// Inner 클래스의 메서드 호출하여 내부 정보 출력
inner.viewInner();
}
}
package com.exam3;
public class InnerEx {
public static void main(String[] args) {
int x = 200; // 지역 변수, effectively final (readonly)
// 메서드 내부에 정의된 지역 클래스 Inner
class Inner {
private int y1 = 100; // private 접근 제어자 사용 - 외부에서 접근 불가
public int y2 = 100; // public 접근 제어자 사용 - 외부에서 접근 가능
public Inner() {
// Inner 객체가 생성되었을 때 메시지 출력
System.out.println("Inner : " + this);
}
void viewInner() {
// Inner 클래스의 메서드 - 내부 변수 출력
System.out.println("viewInner 호출");
System.out.println("y1 = " + y1); // 내부 클래스의 private 멤버 변수
System.out.println("y2 = " + y2); // 내부 클래스의 public 멤버 변수
System.out.println(x); // 지역변수 접근 가능
// x++; // 지역 변수는 effectively final이므로 변경 불가
}
}
// 지역 클래스 Inner의 객체 생성 및 메서드 호출
Inner inner = new Inner();
inner.viewInner();
}
}
effectively final은 자바 8부터 추가된 개념으로, 명시적으로 final 키워드로 선언하지 않았지만, 사실상 값을 변경하지 않고 사용하는 지역 변수를 의미합니다.
x
라는 지역 변수가 effectively final로 선언되어 있어, 지역 클래스에서도 접근할 수 있지만 값을 변경할 수는 없습니다. new
키워드를 사용하여 선언과 동시에 인스턴스를 생성하며, 해당 인스턴스는 일회성으로 사용됩니다.public interface InterA {
// 인터페이스 메서드 선언 (추상 메서드)
void viewInner();
}
public class InnerEx {
public static void main(String[] args) {
// 익명 이너 클래스 사용 (참조 변수 없음)
// 인스턴스화와 동시에 인터페이스의 추상 메서드를 오버라이드하여 구현
new InterA() {
@Override
public void viewInner() {
System.out.println("viewInner 호출");
}
}.viewInner(); // 익명 객체로 viewInner 호출
// 익명 이너 클래스 사용 (참조 변수 ia 사용)
// 인터페이스 구현과 동시에 메서드 오버라이드
InterA ia = new InterA() {
@Override
public void viewInner() {
System.out.println("viewInner 호출");
}
};
// 참조 변수를 통해 viewInner 호출
ia.viewInner();
}
}
package com.exam4;
public class InnerEx02 {
private int y = 200; // 외부 클래스의 인스턴스 변수
public void doInner() {
int x = 100; // 지역 변수
System.out.println("외부 this = " + this); // 외부 클래스의 this 출력
// 익명 내부 클래스 정의와 동시에 viewInner 메서드 구현
new InterA() {
@Override
public void viewInner() {
System.out.println("viewInner 호출 ");
// 내부 클래스의 this (익명 내부 클래스의 인스턴스 참조)
System.out.println("내부 this = " + this);
// 외부 클래스의 인스턴스를 가리키는 참조
System.out.println("외부 this = " + InnerEx02.this);
// 외부 클래스의 인스턴스 변수 y 출력
System.out.println("y = " + InnerEx02.this.y);
// System.out.println("y = " + this.y); // 컴파일 오류 발생
// 오류 이유: 익명 내부 클래스에서 `this`는 익명 클래스 자신을 가리키므로 외부 클래스의 변수 y에 접근할 수 없음.
// `this`를 사용해 외부 클래스 변수에 접근하려면 `OuterClassName.this` 형식을 사용해야 함 (InnerEx02.this.y)
}
}.viewInner(); // 익명 내부 클래스의 viewInner 메서드 호출
}
public static void main(String[] args) {
// InnerEx02 클래스의 인스턴스 생성 후 doInner 메서드 호출
InnerEx02 ie = new InnerEx02();
ie.doInner();
}
}