[Java] Reification

텐저린티·2023년 6월 22일
0

알쓸신잡

목록 보기
2/10

빡세게 과제 중이던 오후

슬랙에 멘토님의 함성이 들렸다.

public static void main(String[] args) {
    List<Integer> ints = Arrays.asList(1, 2, 3);
    System.out.println(isIntegerList(ints));
}
public static <T> boolean isIntegerList(T b){
    return b instanceof List<Integer>; //Compile 에러 발생
}

이거 왜 컴파일 에러가 날까요??ㅠ

나는 당최 모르쇠였는데 팀원 중에 한 분이 좋은 정보를 들고 오셔서 이걸 정리해본다.

먼 훗날 같은 이유로 컴파일 오류가 날 때

내 소중한 하루를 지켜줄 치트키가 되기를 바란다.


Generic Type Erasure

Generic 타입을 컴파일 타임에서만 검사하고, 런타임에서는 타입 정보를 알 수 없다는 의미다.
다시 말해서, 컴파일 타임에는 타입 제약 조건을 정의하고, 런타임에서는 타입을 제거한다는 거.

동작방법은 아래 과정으로 진행된다.
1. 경계가 표시된 (extends, super) 타입은 컴파일 타임에 경계로 치환
2. ? 타입은 Object 타입으로 치환
3. 캐스팅 연산으로 타입 안정성 보장

위에 발생했던 문제는 런타임 시 Type Erasure에 의해서 제너릭의 타입이 Unknown Type.
즉, 불일치하여 발생하는 문제인거다.

Reifiable Type

런타임에 Object의 타입을 완전하게 표현할 수 있는 타입이다.
즉, 컴파일 타임에 타입 소거로 타입이 제거되지 않는다.

  • Primitive Type
  • 일반 클래스 (Number, Integer), 인터페이스 클래스
  • ? 포함된 Parameterized Type (List, ArrayList)
  • Raw Type (List, ArrayList, Map)

와일드 카드는 컴파일 타임에 Object로 치환되기 때문에 reifiable.

Reifiable하지 않은 타입은 3가지다.

  • Generic Type (T)
  • Parameterized Type(List, ArrayList)
  • 경계있는 Parameterized Type(List<? extends Number>, List<? super String>)

Parameterzied Type은 컴파일 시 Raw Type으로 변환된다고 함.
다시 말해 사용된 제너릭은 컴파일 타임에 타입 안전성 검증 용도로 사용되고,
컴파일이 완료되면 Raw Type인 List로 치환된다. -> 타입 소거

위 내용을 바탕으로 문제를 해결해보자.

컴파일 에러를 방지할 수 있는 방법은 크게 세 가지다.

  1. 파라미터 타입이 없는 Raw Type으로 instanceof 연산을 수행하는 방법
  2. 파라미터 타입이 ? 인 리스트와 instanceof 연산을 수행하는 방법
  3. 캐스팅 연산 방법

순서대로 읊어보자.

1번. Raw Type 비교 방법

컴파일만 발생하지 않는 방법이다.
문제에서 원하는 것은 List 컬렉션이 Integer 타입으로만 이뤄져 있는지에 대한 검사다.
즉, 의미가 없다.

2번. ? 타입 비교 방법

Integer 타입만을 요소로 갖는지 여부는 확인할 수 없다.
다만, 와일드카드처럼 reifiable 타입인 경우에만 컴파일 에러 없이 사용할 수 있으므로
Reifiable 타입인지 아닌지에 대한 판단은 가능하다.

public class Main {
    public static void main(String[] args) {
        List<Integer> ints = Arrays.asList(1,2,3);
        System.out.println(isList(ints));
    }
    public static <T> boolean isList(T b){
        return b instanceof List<?>;
    }
}

3번. 캐스팅 방법


public class Main {
    public static void main(String[] args) {
        List<Integer> list = List.of(0, 1, 2, 3);
        System.out.println(isIntegerList(list));
    }

    private static <T> boolean isIntegerList(T obj) {
        if (!(obj instanceof List)) {
            return false;
        }
        List result = ((List<?>) obj).stream()
                .filter(o -> o instanceof Integer)
                .collect(Collectors.toList());
        return result.size() == ((List<?>) obj).size();
    }
}

이런 식으로 코드를 작성하면 문제를 해결할 수 있다.

다만, Warning이 발생한다.
이유는 타입 소거 때문으로, 위 내용과 동일하다.
컴파일 완료 시점에 타입 소거로 타입 정보가 없는 상태이므로,
List 타입으로 안전한 캐스팅 보장이 불가하기 때문.

의도적으로 Reifiable 하지 않은 타입에서 캐스팅을 했다면, @SuppressWarning 어노테이션을 써주면 된다.
경고가 사라짐.

이렇게 개발자의 명시로 캐스팅의 안정성을 확보할 수 있기 때문에 캐스팅 방법은 컴파일 에러가 아닌 경로로 끝나는 것이다.

profile
개발하고 말테야

0개의 댓글