본문으로 건너뛰기
개발 머꼬
개발 노트Java
hohyeon.dev14

제네릭 타입으로 instanceof를 썼다가 컴파일이 안 된 이유

  • #Engineering Note
  • #Generics
  • #Java

문제 발생

제네릭 타입 매개변수로 instanceof 검사를 하려다가 컴파일 에러가 발생했습니다.

public <T> boolean isStringList(List<T> list) {
    return list instanceof List<String>; // 컴파일 에러
}

원인 분석

Java의 제네릭은 타입 소거(type erasure) 방식으로 구현되어 있습니다. List<String>List<Integer>는 소스 코드 레벨(컴파일 타임)에서만 구분되고, 컴파일된 바이트코드에서는 둘 다 그냥 List로 동일하게 취급됩니다 — 제네릭 타입 정보는 런타임에 존재하지 않습니다.

이 설계는 Java 5에서 제네�릭을 처음 도입할 때, 이미 존재하던 방대한 이전 버전(제네릭 없는) 코드와의 호환성을 유지하기 위해 선택된 방식입니다. 그 대가로 다음과 같은 제약이 생겼습니다.

  1. list instanceof List<String>처럼 런타임에 구체 타입 인자를 검사할 수 없습니다 — list instanceof List<?>(와일드카드)까지만 가능합니다.
  2. 제네릭 타입의 배열을 직접 만들 수 없습니다(new T[10]은 컴파일 에러).
  3. 오버로딩 시 타입 소거 후 시그니처가 같아지는 메서드를 동시에 정의할 수 없습니다.
  4. 리플렉션으로 런타임에 실제 List 객체를 봐도 그 안의 요소 타입은 별도로 확인해야 알 수 있습니다(런타임에 저장된 요소를 직접 검사하거나, 필드/메서드 시그니처에 남아있는 제네릭 정보를 리플렉션으로 읽는 방법 등).

해결 방안

  1. 런타임에 리스트 요소의 실제 타입이 필요하다면, 요소를 직접 검사합니다.
public boolean isStringList(List<?> list) {
    return !list.isEmpty() && list.get(0) instanceof String;
}
  1. 클래스 정의 시점에 타입을 명확히 고정할 수 있다면(예: class StringBox 대신 제네릭 없이 구체 타입으로 설계), 애초에 이 문제를 피할 수 있습니다.
  2. 정말로 런타임 타입 정보가 중요한 라이브러리 설계라면, Class<T> 토큰을 명시적으로 함께 전달받는 패턴("super type token" 기법 등)을 씁니다 — Jackson, Guice 같은 라이브러리가 이런 방식으로 제네릭 타입 정보를 런타임까지 보존합니다.
  3. 타입 소거는 Java 제네릭의 근본적인 설계이므로 "우회하는 방법"을 찾기보다, 애초에 런타임 타입 검사가 필요 없도록 설계를 다시 보는 것이 더 나은 경우가 많습니다.

공식 문서

마지막 수정

좋아요북마크

댓글0

아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.