[Java] 중첩 클래스(Nested class)
by Frinee1. 중첩 클래스, 내부 클래스란?
다음과 같이 클래스 안에 클래스를 중첩해서 정의한 것을 중첩 클래스(Nested Class)라 한다.
class Outer {
...
// 중첩 클래스
class Nested {
...
}
}
중첩 클래스는 총 4가지가 있고, 크게 2가지로 분류한다.
- 정적 중첩 클래스
- 내부 클래스 종류
- 내부 클래스
- 지역 클래스
- 익명 클래스
중첩 클래스의 선언 위치
- 정적 중첩 클래스 → 정적 변수와 같은 위치
- 내부 클래스 → 인스턴스 변수와 같은 위치
- 지역 클래스 → 지역 변수와 같은 위치
class Outer {
...
// 정적 중첩 클래스
static class StaticNested {
...
}
// 내부 클래스
class Inner {
...
}
}중첩 vs 내부
- 중첩(Nested): 어떤 다른 것이 내부에 위치하거나 포함되는 구조적인 관계 (엄연히 다른 관계)
- 내부(Inner): 나의 내부에 있는 나를 구성하는 요소
2. 정적 중첩 클래스
public class NestedOuter {
private static int outClassValue = 3;
private int outInstanceValue = 2;
static class Nested {
private int nestedInstanceValue = 1;
public void print() {
// 자신의 멤버에 접근
System.out.println(nestedInstanceValue);
// 바깥 클래스의 인스턴스 멤버에는 접근할 수 없다.
//System.out.println(outInstanceValue);
// 바깥 클래스의 클래스 멤버에는 접근할 수 있다. private도 접근 가능
System.out.println(NestedOuter.outClassValue);
}
}
}
- 정적 중첩 클래스는
- 자신의 멤버 접근 → O
- 바깥 클래스의 인스턴스 멤버 → X
- 바깥 클래스의 클래스 멤버 → O
public class NestedOuterMain {
public static void main(String[] args) {
NestedOuter outer = new NestedOuter(); // (1)
NestedOuter.Nested nested = new NestedOuter.Nested(); // (2)
nested.print();
System.out.println("nestedClass = " + nested.getClass());
}
}- 정적 중첩 클래스는
new 외부클래스.중첩클래스()로 생성 - 사실 (1) 과 (2)는 관련 없는 다른 인스턴스이다.
- 사실 정적 중첩 클래스와 외부 클래스는 아무런 관련이 없다.
3. 내부 클래스
정적 중첩 클래스는 바깥 클래스와 서로 관계가 없다. 하지만 내부 클래스는 바깥 클래스의 인스턴스를 이루는 요소가 된다.
package nested.inner.ex2;
public class Car {
private String model;
private int chargeLevel;
private Engine engine;
public Car(String model, int chargeLevel) {
this.model = model;
this.chargeLevel = chargeLevel;
this.engine = new Engine();
}
public void start() {
engine.start();
System.out.println(model + " 시작 완료");
}
private class Engine {
public void start() {
System.out.println("충전 레벨 확인: " + chargeLevel);
System.out.println(model + "의 엔진을 구동합니다.");
}
}
}- 내부 클래스는 앞에
static이 붙지 않는다. 쉽게 이야기해서 인스턴스 멤버가 된다. - 내부 클래스는
- 자신의 멤버 접근 → O
- 바깥 클래스의 인스턴스 멤버 → O
- 바깥 클래스의 클래스 멤버 → O
package nested.inner;
public class InnerOuterMain {
public static void main(String[] args) {
InnerOuter outer = new InnerOuter();
InnerOuter.Inner inner = outer.new Inner();
inner.print();
System.out.println("innerClass = " + inner.getClass());
}
}- 내부 클래스는 바깥 클래스 인스턴스의 소속되기 때문에 바깥 인스턴스 정보를 알아야 생성 가능
- 내부 클래스는
바깥클래스의 인스턴스 참조.new 내부클래스()로 생성할 수 있음. outer.new Inner()에서outer는 바깥 클래스의 인스턴스 참조를 가짐.

- 실제로 내부 인스턴스가 바깥 인스턴스 안에 생성되지는 않지만 개념상 안에 생성된다고 이해해도 된다.
- 내부 인스턴스는 바깥 인스턴스 참조를 보관하여 바깥 인스턴스 멤버에 접근 할 수 있다.
중첩 클래스 사용
- 언제 사용해야 하나?
- 중첩 클래스는 특정 클래스가 다른 하나의 클래스 안에서만 사용되거나, 둘이 아주 긴밀하게 연결되어 있는 특별한 경우에만 사용
- 사용하는 이유
- 논리적 그룹화: 특정 클래스가 다른 하나의 클래스 안에서만 사용되는 경우 해당 클래스 안에 포함하는 것이 논리적으로 그룹화가 된다.
- 캡슐화: 중첩 클래스는 바깥 클래스의
private멤버에 접근할 수 있다. 이렇게 해서 둘을 긴밀하게 연결하고 불필요한public메서드를 제거할 수 있음.
4. 같은 이름의 바깥 변수 접근
변수의 이름이 같은 경우, 더 가깝거나 구체적인 것에 우선권을 가진다.
인스턴스의 참조 this.value 나 바깥클래스이름.this 를 활용해서 바깥클래스 인스턴스에 접근 가능
package nested;
public class ShadowingMain {
public int value = 1;
class Inner {
public int value = 2;
void go() {
int value = 3;
System.out.println("value = " + value);
System.out.println("this.value = " + this.value);
System.out.println("ShadowingMain.value = " + ShadowingMain.this.value);
}
}
public static void main(String[] args) {
ShadowingMain main = new ShadowingMain();
Inner inner = main.new Inner();
inner.go();
}
}5. 지역 클래스
- 지역 클래스는 내부 클래스의 특별한 종류 중 하나이며 내부 클래스의 특징을 그대로 가진다.
- 지역 클래스는 지역 변수와 같이 코드 블럭 안에서 정의
class Outer {
public void process() {
//지역 변수
int localVar = 0;
//지역 클래스
class Local {...}
Local local = new Local();
}
}- 지역 클래스는 지역 변수처럼 코드 블럭 안에 클래스를 선언
- 지역 클래스는 지역 변수에 접근할 수 있음.
public class LocalOuterV1 {
private int outInstanceVar = 3;
public void process(int paramVar) {
int localVar = 1;
class LocalPrinter {
int value = 0;
public void printData() {
System.out.println("value=" + value);
System.out.println("localVar=" + localVar);
System.out.println("paramVar=" + paramVar);
System.out.println("outInstanceVar=" + outInstanceVar);
}
}
LocalPrinter printer = new LocalPrinter();
printer.printData();
}
public static void main(String[] args) {
LocalOuterV1 localOuter = new LocalOuterV1();
localOuter.process(2);
}
}지역 클래스의 접근 범위
- 자신의 인스턴스 변수인
value에 접근 가능 - 자신이 속한 코드 블럭의 지역변수
localVar에 접근 가능 - 자신이 속한 코드 블럭의 매개변수인
paramVar에 접근할 수 있다. 참고로 매개변수도 지역 변수의 한 종류이다. - 바깥 클래스의 인스턴스 멤버인
outInstanceVar에 접근할 수 있다. (지역 클래스도 내부 클래스의 한 종류이다.) - 지역 클래스는 지역 변수처럼 접근 제어자를 사용할 수 없음.
6. 지역 클래스 - 지역 변수 캡처
- 클래스 변수, 인스턴스 변수, 지역 변수는 각각의 생명주기가 다르다.
- 그렇기 때문에 메서드를 통해 지역 변수에 접근하려 할 때, 이미 생명 주기 상으로 지역변수가 제거되어 접근할 수 없는 경우도 생긴다.
- 그렇지만 우리는 지역변수의 값들을 모두 정상적으로 출력할 수 있다.
→ 이게 가능한 이유가 바로 자바의 "변수 캡처" 기능
- 지역 클래스의 인스턴스를 생성하는 시점에 필요한 지역 변수를 복사해서 생성한 인스턴스에 함께 넣어둠.
- 그렇기 때문에 지역변수는 절대로 중간에 값이 변해서는 안된다.
- 지역변수는
final로 선언하거나 사실상final(effectively final)이어야 한다.
- 지역변수는
7. 익명 클래스
- 익명 클래스는 지역 클래스인데 클래스의 이름이 없다는 특징이 있음.
public class AnonymousOuter {
private int outInstanceVar = 3;
public void process(int paramVar) {
int localVar = 1;
// 지역 클래스의 선언과 생성을 한번에 수행
Printer printer = new Printer() {
int value = 0;
@Override
public void print() {
System.out.println("value=" + value);
System.out.println("localVar=" + localVar);
System.out.println("paramVar=" + paramVar);
System.out.println("outInstanceVar=" + outInstanceVar);
}
};
printer.print();
System.out.println("printer.class=" + printer.getClass());
}
public static void main(String[] args) {
AnonymousOuter main = new AnonymousOuter();
main.process(2);
}
}- 익명 클래스는 부모 클래스를 상속받거나 또는 인터페이스를 구현해야 한다.
- 익명 클래스는 상위 클래스나 인터페이스가 필요함.
- 이름을 가지지 않기 때문에 생성자를 가질 수 없다.
장점
- 클래스를 별도로 정의하지 않고도 인터페이스나 추상 클래스를 즉석에서 구현할 수 있어 코드가 간결해짐.
조건
- 익명 클래스는 단 한 번만 인스턴스를 생성할 수 있음.
람다(lambda)
- 자바8 이전 메서드에 인수로 전달할 수 있는 것은 기본형 타입이나 참조형 타입이 다였음.
- 자바8 이후 메서드를 인수로 전달할 수 있게 되었음. 이것을 람다(Lambda)라 한다.
package nested.anonymous.ex;
import java.util.Random;
// 람다 사용
public class Ex1RefMainV5 {
public static void hello(Process process) {
System.out.println("프로그램 시작");
//코드 조각 시작
process.run();
//코드 조각 종료
System.out.println("프로그램 종료");
}
public static void main(String[] args) {
hello(() -> {
int randomValue = new Random().nextInt(6) + 1;
System.out.println("주사위 = " + randomValue);
});
hello(() -> {
for (int i = 1; i <= 3; i++) {
System.out.println("i = " + i);
}
});
}
}- 클래스나 인스턴스를 정의하지 않고, 메서드의 코드 블럭을 직접 전달함.
'Backend > Java' 카테고리의 다른 글
| [Java] Generics, Enum, Annotation (1) | 2025.02.18 |
|---|---|
| [Java] 예외 처리(exception handling) (0) | 2025.02.11 |
| [Java] 컬렉션 프레임워크(Collections Framework) (1) | 2025.02.09 |
| [Java] 객체지향 프로그래밍 (OOP) (0) | 2025.01.27 |
| 자바 기본 구조와 기본 문법 (0) | 2025.01.18 |
블로그의 정보
프리니의 코드저장소
Frinee