9osari.log
← 목록으로

[JAVA] 중첩 클래스와 내부 클래스

2025년 5월 27일 · #java#김영한-java-중급

중첩 클래스

중첩 클래스는 바깥 클래스의 안에 있지만, 바깥 클래스와 상관 없는 전혀 다른 클래스

class Outer {
  ...
  //중첩 클래스
  class Nested {
  ...
  }
}

중첩 클래스의 종류

중첩(Nested): 내 안에 있지만 내것이 아닌 것

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

중첩 클래스의 선언 위치

정적 중첩 클래스는 바깥 클래스 안에 있지만 바깥 클래스와 관계없는 다른 클래스 내부 클래스는 바깥 클래스의 내부에 있으면서 바깥 클래스를 구성하는 요소

중첩 클래스를 왜 사용하는지?

중첩 클래스 예제

public class NestedOuter {
  private static int outClassValue = 3;
  private int outInstancValue = 2;

  //정적 중첩 클래스는 앞에 static <- 붙음.
  static class Nested {
    private int nestedInstanceValue = 1;

    public void print() {
      //ㅈㅏ신의 멤버에 접근
      System.out.println(nestedInstanceValue);

      // 바깥 클래스의 인스턴스 멤버에는 접근할 수 없다.
      // System.out.println(outInstancValue);

      //바깥 클래스의 클래스 멤버에는 접근할 수 있다 private도 접근 가능
      System.out.println(NestedOuter.outClassValue);
    }
  }
}
public class NestedOuterMain {
    public static void main(String[] args) {
        NestedOuter.Nested nested = new NestedOuter.Nested(); //정적 중첩 클래스 생성
        nested.print();

        System.out.println("nestedClass = " + nested.getClass());
    }
}

//실행결과
1
3
nestedClass = class nasted.nasted.NestedOuter$Nested

private 접근 제어자

정적 중첩 클래스는 다른 클래스를 중첩해 둔 것일 뿐. 둘은 아무런 관계가 없다. 그냥 클래스 두개를 만든 것과 같다.

내부 클래스

내부 클래스는 바깥 클래스의 인스턴스를 이루는 요소로 바깥 클래스의 인스턴스에 소속된다.

public class InnerOuter {
  private static int outClassValue = 3;
  private int outInstanceValue = 2;

  //static이 붙지 않음 즉 인스턴스 멤버가 된다.
  class Inner {
    private int innerInstanceValue = 1;

    public void print() {
      // 자기 자신에 접근
      System.out.println(innerInstanceValue);

      //외부 클래스의 인스턴스 멤버에 접근 가능 private도 접근 가능
      System.out.println(outInstanceValue);

      // 외부 클래스의 클래스 멤버에는 접근 가능. private 접근 가능
      System.out.println(InnerOuter.outClassValue);
    }
  }
}

InnerOuter outer = new InnerOuter(); //x001
InnerOuter.Inner inner = outer.new Inner(); //x001 의 내부에다 inner를 만든다
inner.print(); //그래서 바깥 인스턴스 접근 가능
System.out.println(inner.getClass());

 //실행결과
1
2
3
class nasted.inner.InnerOuter$Inner

private 접근 제어자

정적 중첩 클래스는 다른 클래스를 그냥 중첩해 둔 것일 뿐. 아무런 관계가 없다 반면 내부 클래스는 바깥 클래스의 인스턴스 내부에서 구성요소로 사용된다.

지역 클래스

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

package nasted.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);
    }
}

지역 변수 캡쳐

자바는 지역 클래스의 인스턴스를 생성하는 시점에 필요한 지역 변수를 복사해 생성된 인스턴스에 함께 넣어둔다. 이 과정을 변수 캡쳐라 한다.

public class LocalOuterV3 {
    private int outInstanceVar = 3;

    public Printer process(int paramVar) {
        int localVar = 1;   //지역 변수는 스택 프레임이 종료되는 순간 함께 제거된다.

				//localVar = 2; // <- 이렇게 바꾸면 컴파일 에러!
        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 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
ㅡㅡㅡㅡ필드확인 ㅡㅡㅡㅡㅡ
//인스턴스 변수
field = int nasted.local.LocalOuterV3$1LocalPrinter.value
//캡처 변수
field = final int nasted.local.LocalOuterV3$1LocalPrinter.val$localVar
field = final int nasted.local.LocalOuterV3$1LocalPrinter.val$paramVar
//바깥 클래스 참조
field = final nasted.local.LocalOuterV3 nasted.local.LocalOuterV3$1LocalPrinter.this$0

final 키워드를 넣지 않았을 뿐 사실상 final 키워드를 넣은 값을 변경하지 않는 지역변수

익명 클래스

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

지역 클래스의 선언과 생성을 한번에 가능

public class AnonymusOuter {
    private int outInstanceVar = 3;

    public void process(int paramVar) {
        int localVar = 1;   //지역 변수

        //익명 클래스는 클래스의 본문의 정의하며 생성
        //printer를 구현 하며 바로 생성
        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) {
        AnonymusOuter main = new AnonymusOuter();
        main.process(2);
    }
}

익명 클래스는 클래스를 정의하지 않고 인터페이스나 추상 클래스를 즉석 구현할 수 있어 코드가 간결해지지만 복잡하거나 재사용이 필요할땐 별도의 클래스를 만들자.

익명클래스의 활용

public class Ex1RefMain5 {
    public static void hello(Process process) {
        System.out.println("프로그램 시작");

        //여기서 받아야함
        process.run();

        System.out.println("프로그램 종료");
    }

    public static void main(String[] args) {
        System.out.println("hello 실행");
        //익명 클래스 람다 사용
        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);
            }
        });
    }
}

출처: [김영한의 실전 자바 - 중급 1편](https://www.inflearn.com/course/김영한의-실전-자바-중급-1)
···
← PREV [JAVA] ENUM - 열거형 NEXT → [JAVA] 예외 처리