중첩 클래스와 익명 클래스
한 타입의 구현 세부 사항으로만 쓰이는 작은 타입은 바깥 클래스 안에 둘 수 있습니다.
static 중첩 클래스는 바깥 객체 없이 존재하고 내부 클래스는 특정 바깥 인스턴스와 연결됩니다.
지역 클래스와 익명 클래스는 메서드 가까이에서 일회성 구현을 정의합니다.
람다가 함수형 인터페이스를 더 짧게 표현하지만 상태나 여러 메서드가 필요한 익명 객체는 여전히 의미가 있습니다.
이 절에서는 타입의 사용 범위와 바깥 객체 의존성을 기준으로 형태를 고릅니다.
내부 클래스 인스턴스에는 소스에 보이지 않는 바깥 객체 참조가 들어갑니다. 이 참조 덕분에 prefix에 바로 접근하지만, 콜백이 오래 보관되면 바깥 객체도 가비지 컬렉션 대상이 되지 않습니다. 바깥 상태가 필요하지 않다면 static 중첩 클래스를 기본 선택으로 삼는 이유입니다.
static 중첩 클래스는 이름만 바깥 타입에 묶인다
바깥 인스턴스 참조가 없어 독립적으로 생성할 수 있습니다.
Builder나 결과 타입처럼 바깥 타입과 개념적으로 가까운 도우미에 적합합니다.
내부 클래스는 바깥 객체 상태에 접근한다
생성할 때 특정 바깥 인스턴스가 필요하며 그 참조를 유지합니다.
오래 살아 있는 내부 객체가 바깥 객체 수명까지 늘릴 수 있습니다.
생성 문법을 눈여겨보세요. Parser는 new Parser()로 만들지만 Formatter는 어느 바깥 인스턴스에 속할지 알려 주는 outer.new Formatter()가 필요합니다.
public class NestedTypes {
private final String prefix;
NestedTypes(String prefix) { this.prefix = prefix; }
static final class Parser {
int parse(String text) { return Integer.parseInt(text); }
}
final class Formatter {
String format(int value) { return prefix + value; }
}
public static void main(String[] args) {
Parser parser = new Parser();
NestedTypes outer = new NestedTypes("value=");
Formatter formatter = outer.new Formatter();
Runnable task = new Runnable() {
@Override public void run() { System.out.println(formatter.format(parser.parse("25"))); }
};
task.run();
}
}Parser가 만든 숫자를 특정 outer의 Formatter가 가공하고, 익명 Runnable이 그 결과를 출력합니다.
value=25static Parser는 바깥 객체 없이 만들고 Formatter는 prefix를 가진 특정 바깥 객체와 연결됩니다.
지역·익명 타입은 사용 지점 가까이에 둔다
메서드 지역의 effectively final 변수를 캡처할 수 있습니다.
구현이 커지거나 재사용되면 이름 붙은 별도 클래스로 옮깁니다.
익명 객체와 람다는 모두 지역 변수 limit의 현재 값을 캡처합니다. 이후 limit++를 허용하면 캡처된 값과 지역 변수 중 무엇을 읽는지 모호해지므로 Java는 effectively final 규칙으로 이를 컴파일 때 막습니다.
int limit = 10; // effectively final
java.util.function.IntPredicate predicate = new java.util.function.IntPredicate() {
@Override public boolean test(int value) { return value < limit; }
};
java.util.function.IntPredicate lambda = value -> value < limit;
// limit++; // 컴파일 오류: 캡처한 지역 변수는 effectively final이어야 함중첩 형태는 코드를 감추기 위한 기교가 아니라 타입이 필요한 범위와 바깥 객체에 대한 의존성을 정확히 표현해야 합니다.
직접 변형하려면 Formatter가 prefix 대신 매개변수를 받도록 바꾼 뒤 static 중첩 클래스로 옮겨 보세요. 출력은 같지만 숨은 바깥 참조가 사라집니다. 반대로 바깥 객체마다 다른 설정을 계속 읽어야 한다면 내부 클래스의 결합이 의도에 맞습니다.