본 글은 김영한 님의 『김영한의 실전 자바 - 중급 1편』 강의를 학습하며 정리한 내용입니다.
강의 자료에 포함된 일부 코드와 이미지를 참고하여 발췌·활용하였습니다.자바 기본기를 제대로 다지고 싶으시다면, 아래 링크에서 강의를 확인해 보세요
『김영한의 실전 자바 - 중급 1편』 보러 가기
본게시물은 파트너스 활동의 일환으로 작성되었으며, 구매 시 소정의 수수료를 받을 수 있습니다.
김영한의 실전 자바 - 중급 1편| 김영한 - 인프런 강의
현재 평점 5.0점 수강생 10,970명인 강의를 만나보세요. 실무에 필요한 자바의 다양한 중급 기능을 예제 코드로 깊이있게 학습합니다. 실무에 필요한 다양한 자바 중급 기능, Object, 불변 객체, String
www.inflearn.com
중첩 클래스
중첩 클래스란?
다음과 같이 클래스 안에 클래스를 중첩해서 정의할 수 있는데, 이것을 중첩 클래스라 한다.
class Outer {
...
//중첩 클래스
class Nested {
...
}
}
중첩 클래스의 분류
중첩 클래스는 클래스를 정의하는 위치에 따라 다음과 같이 분류한다.
- 정적 중첩 클래스
- 정적 변수와 같은 위치
static이 붙어있음
- 내부 클래스
- 내부 클래스
- 인스턴스 변수와 같은 위치
- 바깥 클래스의 인스턴스의 멤버에 접근
- 지역 클래스
- 지역 변수와 같은 위치, 코드 블럭 안에서 클래스를 정의
- 내부 클래스의 특징 + 지역 변수에 접근
- 익명 클래스
- 지역 클래스의 특징 + 클래스의 이름이 없는 특별한 클래스
- 내부 클래스
중첩, 내부 의 뜻
- 중첩 (Nested)
- 어떤 다른 것이 내부에 위치하거나 포함되는 구조적인 관계
- 바깥 클래스와 전혀 다른 클래스 → 바깥 클래스의 인스턴스에 소속되지 않음
- 예) 큰 나무 상자안에 전혀 다른 작은 나무 상자를 넣은 것
- 내부 (Innter)
- 나의 내부에 있는 나를 구성하는 요소
- 바깥 클래스를 구성하는 요소 → 바깥 클래스의 인스턴스에 소속됨
- 예) 나의 심장은 나의 내부에서 나를 구성하는 요소
중첩 클래스의 사용
언제 사용해야 하나?
내부 클래스를 포함한 모든 중첩 클래스는 특정 클래스가 다른 하나의 클래스 안에서만 사용되거나,
둘이 아주 긴밀하게 연결되어 있는 특별한 경우에만 사용해야 한다.
외부의 여러 클래스가 특정 중첩 클래스를 사용한다면 중첩 클래스로 만들면 안된다.
사용해야 하는 이유
- 논리적 그룹화
- 특정 클래스가 다른 하나의 클래스 안에서만 사용되는 경우 해당 클래스 안에 포함하는 것이 논리적으로 더 그룹화 됨
- 패키지를 열었을 때 다른 곳에서 사용될 필요가 없는 중첩 클래스가 외부에 노출되지 않는 장점
- 캡슐화
- 중첩 클래스는 바깥 클래스의
private멤버에 접근할 수 있다. → 불필요한publid메서드 제거
- 중첩 클래스는 바깥 클래스의
접근
- 나의 클래스에 포함 x
- 바깥클래스.중첩클래스
- 나의 클래스에 포함
- 바깥클래스 이름을 적지 않아도 됨
정적 중첩 클래스
예제를 통해 정적 중첩 클래스를 알아보자.
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);
}
}
}
- 정적 중첩 클래스 특징
- 클래스 앞에
static이 붙는다. - 자신의 멤버에 접근할 수 있다.
- 바깥 클래스의 인스턴스 멤버에는 접근할 수 없다.
- 바깥 클래스의 클래스 멤버에는 접근할 수 있다.
- 클래스 앞에
private접근 제어자- 중첩 클래스도 바깥 클래스와 같은 클래스 안에 있다.
- 따라서 바깥 클래스의
private접근 제어자에 접근할 수 있다.
public class NestedOuterMain {
public static void main(String[] args) {
NestedOuter outer = new NestedOuter();
NestedOuter.Nested nested = new NestedOuter.Nested();
nested.print();
System.out.println("nestedClass = " + nested.getClass());
}
}
--실행 결과--
1
3
nestedClass = class nested.nested.NestedOuter$Nested
- 정적 중첩 클래스의 생성
new 바깥 클래스.중첩클래스()로 생성할 수 있다.- 중첩 클래스는
바깥 클래스.중첩클래스로 접근할 수 있다.
- 실행 결과
- 중첩 클래스의 이름 → 바깥 클래스 $ 중첩클래스
인스턴스가 생성된 상태

바깥 클래스의 멤버에 접근

- 바깥 클래스의 인스턴스의 참조가 없기 때문에 바깥 클래스가 만든 인스턴스 필드에는 바로 접근할 수 없다.
내부 클래스
특징
static이 붙지 않는다.- 바깥 클래스의 인스턴스에 소속된다.
내부 클래스
예제를 통해 내부 클래스에 대해 알아보자.
public class InnerOuter {
private static int outClassValue = 3;
private int outInstanceValue = 2;
class Inner {
private int innerInstanceValue = 1;
public void print() {
// 자신의 멤버에 접근
System.out.println(innerInstanceValue);
// 외부 클래스의 인스턴스 멤버에 접근 가능, private도 접근 가능
System.out.println(outInstanceValue);
// 외부 클래스의 클래스 멤버에는 접근 가능. private도 접근 가능
System.out.println(InnerOuter.outClassValue);
}
}
}
- 내부 클래스 특징
- 자신의 멤버에 접근할 수 있다.
- 바깥 클래스의 인스턴스 멤버에 접근할 수 있다.
- 바깥 클래스의 클래스 멤버에 접근할 수 있다.
private접근 제어자- 내부 클래스도 바깥 클래스와 같은 클래스 안에 있다.
- 내부 클래스는 바깥 클래스의
private접근 제어자에 접근할 수 있다.
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](http://참조.new) 내부클래스()로 생성할 수 있다.
내부 클래스 생성

- (실제로는 아니고 개념상)바깥 클래스의 인스턴스 내부에서 내부 클래스의 인스턴스가 생성된다.
- 실제로는 내부 인스턴스는 바깥 인스턴스의 참조를 보관한다.
- 내부 인스턴스는 바깥 인스턴스를 알기 때문에 바깥 인스턴스의 멤버에 접근할 수 있다.
지역 클래스
지역 클래스 란?
지역 클래스(Local class) 는 내부 클래스의 특별한 종류의 하나이다.
따라서 내부 클래스의 특징을 그대로 가진다.
또한 지역변수와 같이 코드 블럭 안에서 정의된다.
특징
- 지역 클래스는 지역 변수처럼 코드 블럭 안에 클래스를 선언한다.
- 지역 변수에 접근할 수 있다.
- 일반 클래스처럼 인터페이스를 구현하거나, 상속할 수 있다.
코드를 통해 알아보자.
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=0
localVar=1
paramVar=2
outInstanceVar=3
- 접근 범위
- 인스턴스 변수, 자신이 속한 코드 블럭의 지역변수/매개변수 , 바깥 클래스의 인스턴스 멤버에 접근할 수 있다.
지역 변수 캡처
예제를 하나 보자.
public class LocalOuterV3 {
private int outInstanceVar = 3;
public Printer process(int paramVar) {
int localVar = 1; //지역 변수는 스택 프레임이 종료되는 순간 함께 제거된다.
class LocalPrinter implements 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 printer = new LocalPrinter();
//printer.print()를 여기서 실행하지 않고 Printer 인스턴스만 반환한다.
return printer;
}
public static void main(String[] args) {
LocalOuterV3 localOuter = new LocalOuterV3();
Printer printer = localOuter.process(2);
//printer.print()를 나중에 실행한다. process()의 스택 프레임이 사라진 이후에 실행
printer.print();
//추가
System.out.println("필드 확인");
Field[] fields = printer.getClass().getDeclaredFields();
for (Field field : fields) {
System.out.println("field = " + field);
}
}
}
--실행결과--
value=0
localVar=1
paramVar=2
outInstanceVar=3
예제 코드를 메모리 그림과 함께 분석해보자.

- 지역 클래스 인스턴스의 생존 범위
- 지역 클래스로 만든 객체도 인스턴스이기 때문에 힙 영역에 존재한다.(GC 전까지 생존)
LocalPrint인스턴스는process()메서드 안에서 생성된다.process()에서main()으로 생성한LocalPrinter인스턴스는main()이 종료될 때까지 생존한다.
paramVar,localVar와 같은 지역 변수는process()메서드를 실행하는 동안에만 스택 영역에서 생존한다.
- 지역 클래스로 만든 객체도 인스턴스이기 때문에 힙 영역에 존재한다.(GC 전까지 생존)
- LocalPrinter.print() 접근 메모리 그림
LocalPrinter인스턴스는print()메서드를 통해 힙 영역에 존재하는 바깥 인스턴스의 변수인outInstanceVar에 접근한다.- 인스턴스의 필드를 참조하는 것이기 때문에 문제가 없다.
LocalPrinter인스턴스는print()메서드를 통해 스택 영역에 존재하는 지역 변수도 접근하는 것 처럼 보인다.- 하지만 스택 영역에 존재하는 지역 변수를 힙 영역에 있는 인스턴스가 접근하는 것은 단순하지 않다.
- process() 메서드의 종료
process()메서드가 종료되면 스택프레임이 제거되면서 두 지역 변수도 제거된다.- 여기서 문제는 메서드가 종료되어도
LocalPrinter인스턴스는 계속 생존할 수 있다는 점이다.
- process() 메서드가 종료된 이후에 지역 변수 접근
process()메서드가 종료된 이후에main()메서드 안에서LocalPrinter.print()메서드를 호출한다.print()메서드는 지역 변수인paramVar,localVar에 접근해야 한다. 하지만process()메서드가 이미 종료되었으므로 해당 지역 변수들도 이미 제거된 상태이다.- 그런데 실행 결과를 보면 지역 변수의 값들이 모두 정상적으로 출력되는 것을 확인할 수 있다.
어떻게 제거된 지역 변수들에 접근할 수 있을까?
자바는 이런 문제를 해결하기 위해 지역 클래스의 인스턴스를 생성하는 시점에 필요한 지역 변수를 복사해서 생성한 인스턴스에 함게 넣어둔다.
이 과정을 변수 캡처 라 한다.
모든 지역 변수를 캡처하는 것이 아니라 접근이 필요한 지역 변수만 캡처한다.
캡처 과정
- LocalPrint 인스턴스 생성 시도
- 지역 클래스의 인스턴스를 생성할 때 지역 클래스가 접근하는 지역 변수를 확인한다.
- 사용하는 지역 변수 복사
- 지역 클래스가 사용하는 지역 변수를 복사한다.
- 지역 변수 복사 완료
- 복사한 지역 변수를 인스턴스에 포함한다.
- 인스턴스 생성 완료
- 복사한 지역 변수를 포함해서 인스턴스 생성이 완료 된다.
지역 클래스가 접근하는 지역변수는 불변
지역 클래스가 접근하는 지역 변수는 중간에 값이 변하면 안된다.
따라서 final 로 선언하거나 사실상 final 이어야 한다.
그렇다면 왜 중간에 값이 변하면 안될까?
지역 클래스의 인스턴스를 생성하는 시점에 필요한 지역 변수를 캡처한다.
이후에 캡처한 지역 변수의 값을 변경하면
스택 영역에 존재하는 지역변수의 값과 인스턴스에 캡처한 캡처 변수의 값이 서로 달라지는 문제가 발생한다.
이를 동기화 문제 라고 한다.
익명 클래스
익명 클래스란?
익명 클래스는 지역 클래스의 특별한 종류의 하나이다.
클래스의 이름이 없다는 특징이 있다.
익명 클래스를 클래스의 이름을 생략하고, 선언과 생성을 한번에 처리할 수 있다.
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);
}
}
특징
- 익명 클래스는 이름 없는 지역 클래스를 선언하면서 동시에 생성한다.
- 익명 클래스는 부모 클래스를 상속 받거나, 인터페이스를 구현해야 한다.
- 이름을 가지지 않으므로, 생성자를 가질 수 없다. (기본 생성자만 사용됨)
- 자바 내부에서 바깥 클래스 이름 + $ + 숫자 로 정의된다.
장점
- 인터페이스나 추상 클래스를 즉석에서 구현할 수 있어 코드가 간결해진다.
단점
- 단 한 번만 인스턴스를 생성할 수 있다.
- 여러 번 생성이 필요하다면 지역 클래스를 선언하고 사용하면 된다.
'인프런 > 김영한 자바' 카테고리의 다른 글
| [김영한의 실전 자바 중급 1편] - 8. 예외 처리 (1) | 2025.12.16 |
|---|---|
| [김영한의 실전 자바 중급 1편] - 6. 날짜와 시간 (0) | 2025.10.16 |
| [김영한의 실전 자바 중급 1편] - 5. 열거형,ENUM (0) | 2025.10.14 |
| [김영한의 실전 자바 중급 1편] - 4. 래퍼,Class 클래스 (0) | 2025.10.12 |
| [김영한의 실전 자바 중급 1편] - 3. String 클래스 (0) | 2025.10.10 |