Java/[인프런 김영한 실전 자바 - 중급편]

[인프런 김영한 실전 자바 - 중급편] 중첩 클래스, 내부 클래스 2

h2boom 2024. 7. 23. 16:34

지역 클래스

  • 지역 클래스 : 내부 클래스 중 한 종류로 지역 변수와 같이 코드 블럭 안에 정의된다.
    • 지역 변수에 접근할 수 있다.
    • 자신이 속한 코드 블록의 매개 변수에도 접근할 수 있다. (매개 변수도 지역 변수)
    • 지역 클래스는 지역 변수와 같이 접근 제어자를 사용할 수 없다.
  • 지역 클래스도 클래스의 일종이기 때문에 인터페이스를 구현하거나 부모 클래스를 상속받을 수 있다.

지역 클래스 - 지역 변수 캡처

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 localPrinter = new LocalPrinter();
        return localPrinter;
    }

    public static void main(String[] args) {
        LocalOuterV3 localOuterV1 = new LocalOuterV3();
        Printer printer = localOuterV1.process(2);
        printer.print();
    }
}
  • 지역 클래스는 지역 변수에 접근할 수 있다.
    print()에서 지역 변수 localVar와 매개 변수 paramVar에 접근해 사용했다.
    • 지역 변수는 스택 영역에 존재한다.
      지역 변수의 생명 주기는 해당 코드 블록(스택 프레임)이 종료 시 함께 제거된다.
    • 인스턴스의 생명 주기는 해당 인스턴스를 아무도 참조하지 않을 때(GC 발생 전)까지 존재한다.
  • 예제의 경우 main()에서 process()를 호출한다.
    process()가 종료될 때 process() 내부의 지역 변수들도 함께 제거된다.
    그 이후 process() 내부에 존재하는 지역 클래스의 print()를 통해 process() 내부의 지역 변수를 참조한다.

    스택 프레임이 종료되어 지역 변수도 제거됐지만 정상적인 참조가 가능하다.

 

  • 지역 변수가 제거된 시점에 지역 클래스에서 참조할 수 있는 이유는?
    • 지역 변수 캡쳐로 인해서 참조가 가능해졌기 때문이다.
  • 지역 변수 캡처 : 지역 클래스의 인스턴스를 생성하는 시점에 필요한 지역 변수를 복사해서 인스턴스에 저장한다.
    • 모든 지역 변수가 아닌 접근이 필요한 지역 변수만 캡처한다.

  • 위 예제에서는 필요한 지역 변수인 localVar와 매개 변수 paramVar를 인스턴스에 캡처해둔다.
    지역 클래스에서 지역 변수를 참조할 때 스택 영역이 아닌 캡처한 변수에 접근한다.
  • 지역 변수 캡처를 통해 지역 변수와 지역 클래스의 인스턴스 생명 주기가 서로 다른 문제를 해결한다.

 

  • ★지역 클래스가 접근하는 지역 변수는 절대로 중간에 값이 변하면 안된다
    • final로 선언해야 하거나, 사실상 final이어야 한다.
    • 지역 변수의 값이 변경되게 되면 스택 영역의 지역 변수의 값과 인스턴스에 캡처한 변수의 값이 서로 달라지는 동기화 문제가 발생할 수 있다.
      • 예상치 못한 곳에서 값이 변경되어 디버깅을 어렵게 할 수 있고 멀티 쓰레드 상황에서 이런 동기화는 더욱이 어렵다.
사실상 final (effectively final) : 사실상 final 지역 변수는 지역 변수에 final 키워드를 사용하지는 않았지만 값을 변경하지 않는 지역 변수를 의미한다.

익명 클래스 (Anonymous)

  • 익명 클래스 : 지역 클래스의 종류 중 하나로 이름이 없는 클래스를 의미한다.
    • 클래스의 이름을 생략하고 클래스의 선언과 생성을 한 번에 처리할 수 있다.

 

public interface Printer {
	void print();
}


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);
            }
};
  • 익명 클래스는 클래스의 본문을 정의하면서 동시에 생성한다.
    • new 다음에 바로 상속 받으면서 구현 할 부모 타입을 입력한다.
    • new 인터페이스명() {구현 body};
    • 익명 클래스는 변수에 참조 값을 담아서 인스턴스를 생성할 수도 있지만 인수로 바로 넘겨줄 수도 있다.
      ex) run(new Car{구현});

 

  • 익명 클래스의 특징
    • 이름 없는 지역 클래스를 선언하면서 동시에 생성한다.
    • 부모 클래스를 상속 받거나, 인터페이스를 구현해야 한다. (상위 클래스나 인터페이스가 필요하다.)
    • 이름이 없기 때문에 생성자를 가질 수 없다. (기본 생성자만 사용된다.)
    • 익명 클래스는 자바 내부에서 외부 클래스 이름 + $ + 숫자로 표현된다.
      ex) 외부 클래스 = Outer 일 때, Outer$1, Outer$2 이런 식으로 숫자가 증가하면서 구분된다.
    • 클래스를 별도로 구현하지 않고 즉석에서 구현해서 사용할 수 있어서 코드가 간결해진다.
    • 복잡하거나 재사용이 필요한 경우에 익명 클래스를 사용하는 것보다 지역 클래스를 사용하는 것이 더 좋다.
      • 익명 클래스의 인스턴스는 단 한 번만 생성할 수 있기 때문에 

 

  • 프로그래밍에서 중복 제거, 좋은 코드를 유지하는 핵심
    • 변하는 부분과 변하지 않는 부분을 분리하고 변하는 부분을 외부에서 전달 받음으로 메소드의 재사용을 높일 수 있다.
    • 문자열과 같은 데이터를 메소드에 전달할 때는 각 데이터에 맞는 타입을 전달하면된다.
    • 코드 조각을 메소드에 전달할 때는 인스턴스를 전달하고 해당 인스턴스에 있는 메소드를 호출한다.
      • 변하는 부분을 같은 타입의 인터페이스로부터 메소드를 구현해놓고 다형성을 활용해 외부에서 전달되는 인스턴스에 따라서 다른 코드 조각이 실행될 수 있게 한다. 
public class Ex1RefMainV1 {

    static class Dice implements Process {
        @Override
        public void run() {
            int randomValue = new Random().nextInt(6) + 1;
            System.out.println("주사위 = " + randomValue);
        }
    }

    static class Sum implements Process {
        @Override
        public void run() {
            for (int i = 0; i < 3; i++) {
                System.out.println("i = " + i);
            }
        }
    }

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

        //코드 조각 시작
        process.run();
        //코드 조각 종료 

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

    public static void main(String[] args) {
        hello(new Dice());
        hello(new Sum());
    }
}
  • 중복을 처리하기 위해서 단순 데이터 메소드에 전달하는 것이 아닌 코드 조각을 메소드에 전달하기 위한 방법.

 

  • 람다 (Lambda)
    • Java 8 이전까지는 기본형과 참조형만 메소드 인수로 전달할 수 있었다.
    • 람다 : 메소드를 인수로 전달하는 것.
hello(() -> {
	int randomValue = new Random().nextInt(6) + 1;
	System.out.println("주사위 = " + randomValue);
});

hello(() -> {
	for (int i = 0; i < 3; i++) {
		System.out.println("i = " + i);
	}
});
  • 클래스나 인스턴스를 정의하지 않고 메소드의 코드 블록을 직접 인수로 전달한다.

 

  • 람다와 익명 클래스의 차이는?
    • 익명 클래스: 내부에서 멤버 변수를 선언해서 사용할 수 있다, 인스턴스를 통해서 인스턴스 내부의 메소드를 수행하는 방식
    • 람다: 내부에서 멤버 변수를 선언해서 사용할 수 없다, 인스턴스를 따로 정의하지 않고 코드 블록 자체를 인수로 전달해서 수행하는 방식

출처: [인프런 김영한 실전 자바 - 중급편]

https://www.inflearn.com/course/%EA%B9%80%EC%98%81%ED%95%9C%EC%9D%98-%EC%8B%A4%EC%A0%84-%EC%9E%90%EB%B0%94-%EC%A4%91%EA%B8%89-1/dashboard

 

김영한의 실전 자바 - 중급 1편 강의 | 김영한 - 인프런

김영한 | 실무에 필요한 자바의 다양한 중급 기능을 예제 코드로 깊이있게 학습합니다., 국내 개발 분야 누적 수강생 1위, 제대로 만든 김영한의 실전 자바[사진][임베딩 영상]단순히 자바 문법을

www.inflearn.com