중첩 클래스, 내부 클래스 (Local, Anonymous)

2024. 11. 19. 00:13·Java

 

지역 클래스는 내부 클래스의 종류 중 하나이다. 따라서 내부 클래스의 특징을 그대로 가진다.

지역 클래스는 지역변수와 같이 코드 블럭 안에서 정의된다.

 

package nested.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 localOuterV1 = new LocalOuterV1();
        localOuterV1.process(2);
    }
}

지역 클래스의 접근 범위

- 지역 클래스는 지역 변수에 접근할 수 있다. (localVar 접근 가능)

- 자신의 인스턴스 변수 (value 접근 가능)

- 자신이 속한 코드블럭의 매개변수 (paramVar 접근 가능)

- 바깥 클래스의 인스턴스 멤버 (outInstanceVar 접근 가능)

 

지역 클래스도 인터페이스를 구현하거나, 부모 클래스를 상속할 수 있다.

package nested.local;

public class LocalOuterV2 {

    private int outInstanceVar = 3;

    public void 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);
            }
        }
        LocalPrinter printer = new LocalPrinter();
        printer.print();
    }
    
    public static void main(String[] args) {
        LocalOuterV2 localOuterV1 = new LocalOuterV2();
        localOuterV1.process(2);
    }
}

 

지역 변수 캡처

변수의 생명주기는 다양하다.

클래스 변수: (static 변수)는 메서드 영역에 존재하고, 프로그램이 종료될 때 까지 존재한다.

인스턴스 변수: 힙 영역에 존재하고, 인스턴스가 GC되기 전까지 존재한다.

지역 변수: 스택 영역에 존재하고, 메서드 호출 종료 전 까지 존재한다.

package nested.local;

import java.lang.reflect.Field;

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);
            }
        }
        LocalPrinter printer = new LocalPrinter();
//        printer.print(); 여기서 실행하지 않고 Printer 인스턴스만 반환한다
        return printer;
    }

    public static void main(String[] args) {
        LocalOuterV3 localOuterV1 = new LocalOuterV3();
        Printer printer = localOuterV1.process(2);
        // printer.print() 를 나중에 실행. process()의 스택 프레임이 사라진 이후에 실행
        printer.print();

        // 추가
        System.out.println("필드 확인");
        Field[] fields = printer.getClass().getDeclaredFields();
        for (Field field : fields) {
            System.out.println("field = " + field);
        }
    }
}

이 코드를 자세하게 읽어보면, 지역 클래스의 print() 메서드는 지역 변수 (localVar)와 매개 변수(paramVar)를 참조한다.

그런데 지역 변수는 해당 메서드의 호출이 종료되면 스택 영역에서 삭제되는데, process()가 종료된 이후에 

print()에서 localVar, paramVar의 값을 가져올 수 있는 상황이 발생했다. 삭제된 지역변수를 어떻게 참조했을까?

 

지역 변수 캡쳐

자바는 위의 상황을 해결하기 위해 지역 클래스의 인스턴스를 생성하는 시점에서 필요한 지역변수를 복사해 인스턴스에

함께 넣어두게 했다. 이런 과정을 Capture라고 한다. 물론 모든 지역변수를 캡쳐하는것이 아닌 필요한 지역 변수만 캡쳐한다.

 

따라서 정확히 표현하자면 print() 메서드에서 참조하는 localVar, paramVar는 지역변수에 접근하는 것이 아닌 인스턴스 내에

캡처된 localVar, paramVar에 접근하는 것이다.

 

그렇다면, 지역 변수의 값을 변경하면 어떻게 될까?

 

결론부터 말하면 지역 변수의 값을 변경할 수 없다. 자바에서 막아뒀다. 그 이유는 동기화 문제 때문이다.

지역변수를 변경하면 캡쳐된 지역변수도 같이 변경되어야 하는 문제가 발생하는데, 이 문제가 상당히 복잡하기 때문에

자바에서 막아둔 것이다.

 

따라서 지역 변수는 처음부터 final로 선언하거나, 또는 사실상 final이어야 한다.

사실상 final이란 final로 선언하지 않았지만, 값이 변경되지 않기 때문에 사실상 final이라고 한 것이다.

 

익명 클래스

익명 클래스는 지역 클래스의 종류 중 하나이다. 클래스의 이름이 없다는 것이 특징이다.

package nested.anonymous;

import nested.local.LocalOuterV2;
import nested.local.Printer;

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

 

이전의 V2 코드에선 지역 클래스를 생성하고, 지역 클래스 타입의 인스턴스를 생성하고 오버라이딩 된 print 메서드를 호출했다.

그러나 익명 클래스에선 new 다음 구현/상속할 부모 타입을 입력해서 인스턴스를 생성하면 된다.

인터페이스는 인스턴스를 생성할 수 없다. 그러나 위 코드에선 가능하게 보이는데,

인터페이스의 인스턴스를 생성한 것이 아니라 Printer 인터페이스를 구현한 익명 클래스를 생성한 것이다.

 

어떨때 익명 클래스를 사용하는가?

 사실 그냥 지역 클래스를 사용해도 된다. 편리성 때문에 사용하는 기능이다.

- 특정 부모 클래스(인터페이스)를 상속 받고 바로 생성하는 경우

- 지역 클래스가 일회성으로 사용되거나 간단한 구현일 경우

'Java' 카테고리의 다른 글

예외 처리 (2)  (0) 2024.12.10
예외 처리 (1)  (0) 2024.12.10
중첩 클래스, 내부 클래스 (Static, Inner)  (0) 2024.11.15
날짜와 시간 라이브러리 (2)  (0) 2024.11.12
날짜와 시간 라이브러리(1)  (0) 2024.11.12
'Java' 카테고리의 다른 글
  • 예외 처리 (2)
  • 예외 처리 (1)
  • 중첩 클래스, 내부 클래스 (Static, Inner)
  • 날짜와 시간 라이브러리 (2)
공부처음하는사람
공부처음하는사람
  • 공부처음하는사람
    lazzzykim
    공부처음하는사람
  • 전체
    오늘
    어제
    • 분류 전체보기 (127)
      • Kotlin (31)
      • Java (55)
      • Spring (18)
      • Algorithm (3)
      • TroubleShooting (1)
      • 내일배움캠프 프로젝트 (14)
      • Setting (2)
      • ... (1)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
    • 글쓰기
  • 링크

  • 인기 글

  • 태그

    래퍼클래스
    중첩클래스
    김영한
    Di
    내일배움캠프
    빈 생명주기
    생성자 주입
    캡슐화
    김영한의 실전자바
    kotlin
    배열
    김영한의 실전 자바
    다형성
    OCP
    언체크예외
    싱글톤
    spring
    java
    @Component
    제네릭
  • hELLO· Designed By정상우.v4.10.3
공부처음하는사람
중첩 클래스, 내부 클래스 (Local, Anonymous)
상단으로

티스토리툴바