[JAVA] 중첩 클래스, 내부 클래스 2

2025. 7. 9. 16:46·자바

이번 장에서는 지역 클래스에 대해 알아볼 것이다.

 

자바에서 내부 클래스는 클래스 내부에 선언된 클래스를 의미하며, 그중에서도 지역 클래스(Local Class)는 메서드나 생성자, 초기화 블럭 내부에 선언되는 특별한 종류의 내부 클래스이다. 지역 클래스는 지역 변수처럼 코드 블럭 안에 선언되며, 그 블럭을 벗어나면 사용할 수 없다.

 

지역 클래스는 다음과 같은 방식으로 선언된다.

class Outer {
    public void process() {
        int localVar = 0;

        class Local {
            void print() {
                System.out.println(localVar);
            }
        }

        Local local = new Local();
        local.print();
    }
}

 

위 예제에서 지역 클래스 Local은 메서드 process() 내부에 선언된다. 이 클래스는 localVar에 접근할 수 있다. 이는 내부 클래스이므로 바깥 클래스의 인스턴스 멤버에도 접근 가능하다는 점에서 일반 클래스와 다르다.

 

 

지역 클래스의 특징

  • 지역 변수처럼 메서드, 생성자, 초기화 블럭 내부에서 선언된다.
  • 내부 클래스이므로 바깥 클래스의 인스턴스 변수에 접근할 수 있다.
  • 선언된 블럭 안에서만 사용할 수 있으며, 외부에서는 사용할 수 없다.
  • 접근 제어자(public, private 등)를 사용할 수 없다.
  • 인터페이스를 구현하거나 클래스를 상속할 수 있다.

 

지역 클래스의 활용 예제

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

 

지역 클래스는 자신이 속한 블럭의 지역 변수와 매개변수, 그리고 바깥 클래스의 인스턴스 변수에 모두 접근할 수 있다.

 

이 다음 부터가 중요하다.

 

변수의 생명주기와 지역 변수 캡처

지역 클래스가 접근하는 지역 변수는 생명주기가 매우 짧다. 메서드 호출이 끝나면 스택 영역에서 해당 지역 변수는 제거된다. 반면 지역 클래스로 생성한 객체는 힙 영역에 존재하므로 메서드가 종료된 이후에도 살아남을 수 있다. 이때 문제가 발생할 수 있다. 이미 메서드가 종료되어 지역 변수가 사라졌음에도 지역 클래스 인스턴스는 그 변수에 접근해야 하기 때문이다.

 

자바는 이 문제를 해결하기 위해 변수 캡처(Capture)라는 방식을 사용한다. 지역 클래스가 접근하는 지역 변수는 객체를 생성하는 시점에 복사되어 인스턴스 내부에 함께 저장된다. 이후 지역 변수에 접근하는 것처럼 보이지만, 실제로는 복사된 값을 참조하는 것이다.

다음 예제는 이를 보여준다. (역시 개발자는 예시를 통해 봐야 이해가 빠르다.)

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);
            }
        }

        return new LocalPrinter(); // 인스턴스만 반환
    }

    public static void main(String[] args) {
        LocalOuterV3 localOuter = new LocalOuterV3();
        Printer printer = localOuter.process(2);
        printer.print(); // process() 종료 이후 실행
    }
}

실행 결과는 다음과 같다.

value=0
localVar=1
paramVar=2
outInstanceVar=3

 

process() 메서드는 이미 종료된 상태다. 일반적으로 지역 변수(localVar, paramVar)는 메서드 실행이 끝나면 스택 프레임과 함께 소멸되기 때문에, 외부에서 접근할 수 없어야 한다. 그런데 위 예제에서는 print() 메서드를 통해 해당 지역 변수들의 값이 정상적으로 출력된다. 여기서 자연스럽게 의문이 생긴다. 이미 스택에서 사라진 지역 변수의 값이 어떻게 출력될 수 있는가?

 

바로 이 지점에서 지역 변수 캡처(Capture) 개념이 등장한다.

 

자바는 이러한 문제를 해결하기 위해, 지역 클래스가 접근하는 지역 변수나 매개변수를 객체 생성 시점에 복사하여 지역 클래스 인스턴스 내부에 저장해 둔다. 즉, localVar와 paramVar는 인스턴스 내부에 복제되어 존재하며, 이후 메서드가 종료되어 스택에서 해당 변수들이 사라지더라도 인스턴스에 저장된 복사본을 통해 값을 유지하고 접근할 수 있게 되는 것이다.

 

이 방식은 지역 변수의 생명주기(짧음)와 지역 클래스 인스턴스의 생명주기(힙에 존재하므로 상대적으로 길다)의 불일치를 해결하기 위한 자바의 구조적인 장치라고 볼 수 있다.

 

따라서 위의 결과는 실제 스택에 남아 있는 지역 변수에 접근한 것이 아니라, 힙에 존재하는 지역 클래스 인스턴스가 캡처해 둔 복사본에 접근한 결과인 것이다. 이 메커니즘이 바로 '캡처(Capture)'다.

 

이후 내용에서 이어지는 final 또는 '사실상 final(effective final)' 조건도 이 복사 구조를 안전하게 유지하기 위한 제약 중 하나다. 값이 변경되면 원본과 복사본이 달라지는 동기화 문제가 발생할 수 있기 때문이다.

 

 

캡처 변수는 왜 final 또는 사실상 final이어야 하는가?

지역 클래스가 접근하는 지역 변수는 값이 중간에 변경되지 않아야 한다. 이유는 간단하다. 값이 바뀌면 복사한 값과 스택에 존재하는 값이 달라지는 문제, 즉 동기화 문제가 발생하기 때문이다. 이를 방지하기 위해 자바는 지역 클래스가 접근하는 지역 변수는 반드시 final이거나, 변경하지 않은 상태, 즉 사실상 final(effecively final) 상태여야만 허용한다.

public class LocalOuterV4 {
    public Printer process(int paramVar) {
        int localVar = 1;

        class LocalPrinter implements Printer {
            @Override
            public void print() {
                System.out.println("localVar=" + localVar);
                System.out.println("paramVar=" + paramVar);
            }
        }

        // localVar = 10; // 컴파일 오류 발생
        return new LocalPrinter();
    }
}

localVar나 paramVar를 변경하면 컴파일 오류가 발생한다. 이는 자바가 캡처된 값과 실제 값이 일치하지 않는 상황을 근본적으로 차단하기 위한 제약이다.

 

정리

  • 지역 클래스는 메서드나 코드 블럭 내부에 선언되는 클래스이며, 내부 클래스의 일종이다.
  • 지역 클래스는 바깥 클래스의 인스턴스 변수와 코드 블럭 내부의 지역 변수, 매개변수에 접근할 수 있다.
  • 지역 변수의 생명주기는 짧지만, 지역 클래스 인스턴스는 오래 살아남을 수 있다.
  • 이 차이를 해결하기 위해 자바는 변수 캡처(Capture)라는 방식을 사용한다.
  • 캡처된 변수는 final이거나 사실상 final이어야 하며, 중간에 값을 변경할 수 없다.

마무리하며:

지역 클래스는 구조적인 코드 작성이나 특정 메서드 내부에서만 사용되는 클래스 구현이 필요할 때 유용하다. 특히 익명 내부 클래스나 람다와 함께 쓰이는 경우가 많으므로, 개념을 확실히 이해해두면 실무나 면접에서도 도움이 된다고 한다.

 

 

'자바' 카테고리의 다른 글

[JAVA] 예외 처리1 -이론  (0) 2025.07.10
[JAVA] 중첩 클래스. 내부 클래스3  (2) 2025.07.09
[JAVA] 중첩 클래스, 내부 클래스 1  (2) 2025.07.07
[JAVA] 날짜와 시간  (1) 2025.07.06
[JAVA] 열거형(ENUM)에 대하여  (1) 2025.06.29
'자바' 카테고리의 다른 글
  • [JAVA] 예외 처리1 -이론
  • [JAVA] 중첩 클래스. 내부 클래스3
  • [JAVA] 중첩 클래스, 내부 클래스 1
  • [JAVA] 날짜와 시간
0kingki_
0kingki_
자바 + 스프링 웹 개발
  • 0kingki_
    0kingki_
    0kingki_
  • 전체
    오늘
    어제
    • 분류 전체보기 (134)
      • 코딩 테스트 (54)
      • 자바 (21)
      • 스프링 (27)
      • 타임리프 (16)
      • 스프링 데이터 JPA (8)
      • 최적화 (2)
      • QueryDSL (4)
      • AWS (2)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    SpringDataJpa
    LocalDateTime
    최적화
    fetch join
    dfs
    JPA
    쿼리dsl
    자바
    SOLID
    객체지향
    백준
    mvc
    코딩 테스트
    컬렉션
    예외처리
    예외 처리
    코딩테스트
    QueryDSL
    스프링 데이터 JPA
    스프링 컨테이너
    불변객체
    스프링
    Java
    spring
    쿼리
    다형성
    재귀
    thymeleaf
    BFS
    타임리프
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.4
0kingki_
[JAVA] 중첩 클래스, 내부 클래스 2
상단으로

티스토리툴바