Java | 중첩 클래스

Lumpen·2025년 4월 25일
0

Java

목록 보기
30/38

중첩 클래스

클래스 안에서 클래스를 중첩해 정의하는 것을 중첩 클래스라고 한다
중첩 클래스는 클래스를 정의하는 위치에 따라 분류한다

중첩 클래스의 분류

non-static 내부 클래스

내부 클래스 (inner class)
	↓ 코드 블록에 선언
지역 클래스 (local class)
	↓ 이름 없는 클래스
익명 클래스 (anonymous class)

바깥 인스턴스 소속

static 클래스

정적 중첩 클래스 (static nested class)

전혀 다른 인스턴스

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

변수 선언 위치

  • 정적 변수 (클래스 변수)
  • 인스턴스 변수
  • 지역 변수

중첩 클래스의 선언 위치

  • 정적 중첩 클래스 -> 정적 변수와 같은 위치
  • 내부 클래스 -> 인스턴스 변수와 같은 위치
  • 지역 클래스 -> 지역 변수와 같은 위치
class Outer {

	static class StaticNested {
    	// 정적 중첩 클래스
    }
    
    class Inner {
    	// 내부 클래스
    }
}
class Outer {

	public void process() {
    
    	class Local {
        	// 지역 클래스
        }
    }
}

중첩 클래스

정적 중첩 클래스

  • static
  • 외부 클래스의 인스턴스에 소속되지 않는다

내부 클래스

  • 외부 클래스의 인스턴스에 소속된다

내부 클래스의 종류

  • 내부 클래스: 외부 클래스의 인스턴스 멤버에 접근
  • 지역 클래스: 내부 클래스의 특징 + 지역 변수에 접근
  • 익명 클래스: 지역 클래스의 특징 + 이름이 없는 특별한 클래스

정리

  • 중첩 클래스: 정적 중첩 클래스 + 내부 클래스
  • 정적 중첩 클래스: static
  • 내부 클래스: 내부, 지역, 익명 클래스

중첩 클래스의 사용

모든 중첩 클래스는 특정 클래스가 특정 클래스 안에서만 사용되거나
아주 긴밀하게 연결되어 있는 특별한 경우에만 사용한다
여러 클래스가 사용한다면 중첩 클래스로 만들지 않는다

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

  • 논리적 그룹화: 특정 클래스가 다른 하나의 클래스 안에서만 사용하는 경우
  • 캡슐화: 중첩 클래스는 외부 클래스의 private 멤버에 접근할 수 있기 때문에 불필요한 public 을 제거할 수 있고 긴밀하게 연결할 수 있다

정적 중첩 클래스

static 으로 선언되기 때문에
자신이 선언된 외부 클래스의 멤버에에 직접 접근할 수 없다
static 은 클래스 레벨로 인스턴스 영역이 아닌 메서드 영역의 메모리에 올라간다

class Outer {
	    private static outClassValue = 0;
        private outInstanceValue = 0;
        
        // 정적 중첩 클래스
        static class Nested {
        	private int nestedInstanceValue = 0;
			public void print () {
        		System.out.println(nestedInstanceValue); 
                // 자신의 멤버에 접근
                System.out.println(outInstanceValue); 
                // 외부 클래스 인스턴스 멤버에는 접근할 수 없다
                System.out.println(Outer.outClassValue); 
                // 외부 클래스의 클래스 멤버에는 private 로 작성되었더라도 접근 가능
            }
        }
}
public static void main(String[] args) {
	Outer outer = new Outer(); // 외부 클래스 생성
    Outer.Nested nested = new Outer.Nested(); // 중첩 클래스 생성
}
  • 정적 중첩 클래스의 경우 외부 클래스와 직접적인 관련이 없기 때문에 따로 중첩 클래스의 인스턴스만 생성해서 사용하여도 된다
  • 외부 클래스의 private 클래스 멤버에 접근 가능하다는 것
  • 중첩 클래스는 다른 클래스에서 접근 시 외부 클래스 . 참조로 접근할 수 있다
  • 외부 클래스 내에서는 . 참조 없이 바로 접근 가능

중첩 클래스의 목적은 자신이 작성된 외부 클래스 내에서만 사용하는 것이기 떄문에
다른 클래스에서 접근을 해야한다면 중첩 클래스로 작성하지 않는 편이 좋다

내부 클래스

내부 클래스는 외부 클래스의 인스턴스에 소속된다

  • 외부 클래스의 인스턴스 멤버, 클래스 멤버에 바로 접근 가능 (private 도 가능)
  • 외부 클래스를 먼저 생성해야 내부 클래스의 인스턴스 생성 가능
  • 실제 내부 인스턴스가 외부 인스턴스 안에 생성되는 것은 아니지만 선언 시의 스코프 체인에 의해 참조 가능
  • 내부 클래스는 외부 클래스의 인스턴스 내부에서 구성 요소로 사용된다
	Outer outer = new Outer();
    Outer.Inner = outer.new Inner(); // 외부 클래스 참조를 통해 생성자 호출

중첩 클래스 사용 이유

중첩 클래스는 하나의 클래스가 다른 클래스 안에서만 사용되거나
긴밀하게 연결되어 있는 경우에만 사용한다

  • 논리적 그룹화
  • 캡슐화

같은 이름의 외부 변수 접근

외부 클래스와 내부 클래스의 인스턴스 변수 이름이 같은 경우

Outer.this.value

지역 클래스

지역 클래스는 내부 클래스의 특별한 종류 중 하나
외부 클래스의 메서드 내에 내부 클래스를 작성하는 것으로
메서드 내에서 지역 클래스의 인스턴스를 생성하여 사용

  • 내부 클래스이므로 외부 클래스의 인스턴스 멤버에 접근 가능
  • 지역 변수와 같이 코드 블럭 안에서 정의된다
  • 지역 변수처럼 접근 제어자를 사용할 수 없다

지역 변수 캡처

지역 클래스가 접근하는 지역 변수의 값은 변경되면 안된다

변수의 생명 주기

  • 클래스 변수: 프로그램 시작부터 종료 까지 (메서드 영역)
  • 인스턴스 변수: 인스턴스의 생존 기간, GC 되기 전까지 (힙 영역)
  • 지역 변수: 메서드 호출이 끝나면 사라짐 (스택 영역)

지역 클래스에서 지역 변수를 사용한다면
인스턴스의 생명 주기가 지역 변수의 생명 주기보다 짧기 때문에
지역 클래스는 지역 변수를 사용할 수 없어야 한다

하지만 자바에서는 이런 문제를 해결하기 위해
변수 캡쳐를 지원한다
인스턴스를 생성하는 시점에 필요한 지역 변수를 복사해
생성한 인스턴스와 함께 넣어둔다
이 때 접근이 필요한 변수만을 캡쳐하여 두었다가 사용한다

지역 변수 캡처

지역 클래스가 접근하는 지역 변수는 절대 중간에 값이 변하면 안된다
따라서 final 로 선언하거나 effectively final 이어야 한다

중간에 값이 변한다면
인스턴스에서 변수 캡처를 한 값과
지역 변수의 값이 달라지는 문제가 생기기 떄문이다
이 것을 동기화 문제라고 한다

캡쳐 변수의 값을 변경하지 못하는 이유

  • 지역 변수의 값 변경 시 인스턴스 캡쳐 변수의 값 또한 변해야 한다
  • 인스턴스에 있는 캡쳐 변수의 값 변경 시 해당 지역 변수의 값도 다시 변경해야 한다
  • 개발자 입장에서 디버깅이 어렵다
  • 멀티쓰레드 환경에서는 이러한 동기화 작업이 매우 어렵고 성능에도 좋지 않다

익명 클래스

지역 클래스인데 이름이 없는 클래스
익명 클래스는 선언과 생성을 한 번에 처리할 수 있다

인터페이스를 구현 하면서 구현체의 인스턴스를 바로 만든다

public interface Printer {
	void print();
}


public class Outer {
	private int outInstalceVar = 3;
	public void process() {
	    int localVar = 1;
    	Printer printer = new Printer(int paramVar) {
        	int value = 0;
            @Override
            public void print() {
            	System.out.println(value);
                System.out.println(localVar);
                System.out.println(paramVar);
                System.out.println(outInstanceVar);
            }
        }
    }
}

내부 클래스와 기능은 같은데
이름 없이 간결하게 작성할 수 있다는 장점이 있다

  • 부모 클래스나 인터페이스가 있을 때만 가능
  • 생성자를 가질 수 없다 (기본 생성자만 가능)
  • 바깥 클래스 이름 + $ + 숫자로 정의된다 (1, 2, 3...)

new Interface() {...}

클래스를 별도로 정의하지 않고 인터페이스나 추상 클래스를 즉시 구현할 수 있어
코드가 간결해지지만 복잡하거나 재사용이 필요한 경우 사용하지 않는다

익명 클래스의 활용

public interface Printer {
	void print();
}


public class Outer {
	private int outInstalceVar = 3;
    
    // 상황에 따라 변하는 값은 매개변수로 받아서 활용
	public void process(String str) {
	    int localVar = 1;    
        
        // 익명 클래스에서 참조하는 변수는 변하지 않는 값만을 활용
    	Printer printer = new Printer(int paramVar) {
        	int value = 0;
            @Override
            public void print() {
            	System.out.println(value);
                System.out.println(localVar);
                System.out.println(paramVar);
                System.out.println(outInstanceVar);
            }
        }
    }
}
profile
떠돌이 생활을 하는. 실업자, 부랑 생활을 하는

0개의 댓글