7. 중첩 클래스, 내부 클래스1

중첩 클래스, 내부 클래스란?

중첩 클래스의 분류

중첩 클래스는 총 4가지가 있고, 크게 2가지로 분류할 수 있다.

  • 정적 중첩 클래스

  • 내부 클래스 종류

    • 내부 클래스

    • 지역 클래스

    • 익명 클래스

중첩 클래스를 정의하는 위치는 변수의 선언 위치와 같다.

변수의 선언 위치

  • 정적 변수(클래스 변수)

  • 인스턴스 변수

  • 지역 변수

중첩 클래스의 선언 위치

  • 정적 중첩 클래스 -> 정적 변수와 같은 위치

  • 내부 클래스 -> 인스턴스 변수와 같은 위치

  • 지역 클래스 -> 지역변수와 같은 위치

그럼 중첩이라는 단어와 내부라는 단어는 무슨 차이가 있는 것일까?

  • 중첩(Nested) : 어떤 다른 것이 내부에 위치하거나, 포함되는 구조적인 관계

    • 구조적인 의미

    • static 이 붙는다.

  • 내부(Inner) : 나의 내부에 있는 나를 구성하는 요소

    • 실제적인 의미 (인스턴스)

    • 바깥 클래스의 인스턴스에 소속

중첩 클래스는 언제 사용해야 하나?

  • 내부 클래스를 포함한 모든 중첩 클래스는 특정 클래스가 다른 하나의 클래스 안에서만 사용되거나, 둘이 아주 긴밀하게 연결되어 있는 특별한 경우에만 사용해야 한다.

  • 외부의 여러 클래스가 특정 중첩 클래스를 사용한다면 중첩 클래스로 만들면 안된다. (캡슐화 깨짐..)

    • DTO 를 내부 클래스의 형태로 사용하는 방향을 캡슐화를 지킨다고 볼 수 없다.. (사용하지 말자..)

중첩 클래스를 사용하는 이유

  • 논리적 그룹화 : 특정 클래스가 다른 하나의 클래스 안에서 사용되는 경우 해당 클래스 안에 포함하는 것이 논리적으로 더 그룹화 된다. 패키지를 열었을 때 다른 곳에서 사용될 필요가 없는 중첩 클래스가 외부에 노출되지 않는 장점도 있다.

  • 캡슐화 : 중첩 클래스는 바깥 클래스의 private 멤버에 접근할 수 있다. 이렇게 해서 둘을 긴밀하게 연결하고 불필요한 public 메서드를 제거할 수 있다.

정적 중첩 클래스

  • 정적 중첩 클래스는 앞에 static 이 붙는다.

  • 정적 중첩 클래스는

    • 자신의 멤버에는 당연하게 접근할 수 있다.

    • 바깥 클래스의 인스턴스 멤버에는 접근할 수 없다..

    • 바깥 클래스의 클래스 멤버에는 접근할 수 있다.

  • 정적 중첩 클래스는 new 바깥클래스.중첩클래스() 로 생성할 수 있다.

    • 정적 변수/메서드와는 달리 정적 중첩 클래스는 초기화 할 때마다 힙 영역에 인스턴스를 만든 후 사용된다.

정적 변수/메서드 철학

  • 공통된 속성, 행동 공유

  • 메서드 영역에 정적 변수/메서드가 저장된다.

정적 중첩 클래스 철학

  • 바깥 클래스 인스턴스와 연관이 없다.

  • 메서드 영역에 정적 중첩 클래스의 설계도가 저장

  • 힙 영역에 실제 인스턴스 저장

  • 중첩 클래스는 바깥 클래스.중첩클래스 로 접근할 수 있다.

  • 실제로 바깥 클래스와 중첩 클래스는 메모리적으로 관련이 없는 클래스이다. 단지 구조적으로 중첩해두었을 뿐이다.

    • 때문에, 정적 중첩 클래스의 인스턴스만 따로 생성해도 된다.

    • 메모리적으로 관련은 없지만, 객체지향 관점으로 클래스 사이 관련이 있는 경우 사용한다.

그림을 통해서 코드를 분석해보자

정리

정적 중첩 클래스는 사실 다른 클래스를 중첩해 둔 것일 뿐이다!

쉽게 이야기해서 그냥 클래스 2개를 따로 만든것과 같다. 구조적으로 중첩되어 있을 뿐!

내부 클래스

  • 내부 클래스는 앞에 static 이 붙지 않는다. 쉽게 이야기해서 인스턴스 멤버가 된다.

  • 내부 클래스는

    • 자신의 멤버에는 당연히 접근할 수 있다.

    • 바깥 클래스의 인스턴스 멤버에 접근할 수 있다.

    • 바깥 클래스의 클래스 멤버에 접근할 수 있다.

  • 내부 클래스는 바깥 클래스의 인스턴스에 소속된다. 따라서 바깥 클래스의 인스턴스 정보를 알아야 생성할 수 있다.

  • 내부 클래스는 바깥클래스의 인스턴스 참조.new 내부클래스() 로 생성할 수 있다.

    • 내부 클래스는 바깥 클래스의 인스턴스에 소속되어야 한다.

    • 따라서 내부 클래스를 생성할 때, 바깥 클래스의 인스턴스 참조가 필요하다.

개념 - 내부 클래스의 생성

  • 개념상 바깥 클래스의 인스턴스 내부에서 내부 클래스의 인스턴스가 생성된다.

  • 따라서 내부 인스턴스는 바깥 클래스를 알기 때문에, 바깥 인스턴스의 멤버에 접근할 수 있다.

실제 - 내부 클래스의 생성

  • 실제로 내부 인스턴스가 바깥 인스턴스를 안에 생성되는 것은 아니다. 하지만 개념상 인스턴스 안에 생성된다고 이해하면 충분하다.

  • 실제로는 내부 인스턴스는 바깥 인스턴스의 참조를 보관한다. 이 참조를 통해서 바깥 인스턴스의 멤버에 접근할 수 있다.

궁금증? - 정적 중첩 클래스가 정말 필요할까?

궁금증

캡슐화를 위해 내부 클래스를 쓴다면, 실제 인스턴스와 연관 있는 일반 내부 클래스(Inner Class)를 쓰면 되지, 왜 굳이 외부 인스턴스와 무관한 정적 중첩 클래스(static nested class)를 써야 할까?"

왜?! 정적 중첩 클래스를 사용할까?

1. 불필요한 외부 인스턴스 참조 제거 (메모리 누수 방지)

  • 내부 클래스는 컴파일 시 자동으로 외부 클래스의 참조(this$0)를 갖는다

  • 이건 때때로 GC가 외부 클래스 인스턴스를 회수하지 못하게 만들 수 있음

    • 특히 콜백 구조나 멀티스레딩 상황에서는 의도치 않은 메모리 누수(leak)로 이어질 수 있음

    • 정적 중첩 클래스는 아예 외부 인스턴스와 끊어진 독립 클래스니까 이런 문제가 없음

2. 단순한 논리적 그룹화가 목적일 경우

  • Map.Entry처럼 단순히 관련된 타입을 내부적으로 묶어놓고 싶은 경우

  • 외부 Map 인스턴스를 꼭 참조할 필요 없음 → 이럴 때 정적 중첩 클래스가 더 가볍고 안전한 선택

3. 클래스 설계 명확성 / 의도 표현

  • 누군가 코드를 봤을 때 static class Inner를 보면 → "아, 이 클래스는 외부 인스턴스에 의존하지 않는구나" 라고 한눈에 알 수 있음

  • 설계 의도를 명확히 보여줌 → 유지보수성 향상

결론

내부 클래스를 사용하면 좋은 케이스

  • ClickListener는 항상 Button 인스턴스의 상태(enabled 등)에 의존

  • 외부 객체가 사라질 일이 없고, 연결된 관계 유지가 필수적 → 내부 클래스 적합

정적 중첩 클래스를 사용하면 좋은 케이스

  • Builder는 외부 클래스와 독립적으로 존재할 수 있음

  • 필요할 때만 외부 클래스 객체를 만들므로 GC 부담 없음

정리

  • 내부 클래스는 ‘논리적 포함’과 ‘실제 인스턴스 참조’가 동시에 필요한 경우에만 써야한다.

  • 그 외에는 정적 중첩 클래스를 사용하는 게 메모리, 유지보수, 성능 면에서 훨씬 유리하다.

    • 바깥 클래스에 대한 레퍼런스 정보를 가지지 않기 때문에, GC, 클래스 로딩, CPU 캐시 등 여러 면에서 성능적으로 더 효율적이다.

Last updated