빡세게 과제 중이던 오후
슬랙에 멘토님의 함성이 들렸다.
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 타입을 컴파일 타임에서만 검사하고, 런타임에서는 타입 정보를 알 수 없다는 의미다.
다시 말해서, 컴파일 타임에는 타입 제약 조건을 정의하고, 런타임에서는 타입을 제거한다는 거.
동작방법은 아래 과정으로 진행된다.
1. 경계가 표시된 (extends, super) 타입은 컴파일 타임에 경계로 치환
2. ? 타입은 Object 타입으로 치환
3. 캐스팅 연산으로 타입 안정성 보장
위에 발생했던 문제는 런타임 시 Type Erasure에 의해서 제너릭의 타입이 Unknown Type.
즉, 불일치하여 발생하는 문제인거다.
런타임에 Object의 타입을 완전하게 표현할 수 있는 타입이다.
즉, 컴파일 타임에 타입 소거로 타입이 제거되지 않는다.
와일드 카드는 컴파일 타임에 Object로 치환되기 때문에 reifiable.
Reifiable하지 않은 타입은 3가지다.
Parameterzied Type은 컴파일 시 Raw Type으로 변환된다고 함.
다시 말해 사용된 제너릭은 컴파일 타임에 타입 안전성 검증 용도로 사용되고,
컴파일이 완료되면 Raw Type인 List로 치환된다. -> 타입 소거
위 내용을 바탕으로 문제를 해결해보자.
순서대로 읊어보자.
컴파일만 발생하지 않는 방법이다.
문제에서 원하는 것은 List 컬렉션이 Integer 타입으로만 이뤄져 있는지에 대한 검사다.
즉, 의미가 없다.
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<?>;
}
}
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
어노테이션을 써주면 된다.
경고가 사라짐.
이렇게 개발자의 명시로 캐스팅의 안정성을 확보할 수 있기 때문에 캐스팅 방법은 컴파일 에러가 아닌 경로로 끝나는 것이다.