[Effective JAVA] 27 "비검사 경로를 제거하라"
ITEM 27 "비검사 경로를 제거하라"
제네릭을 사용하기 시작하면, 수많은 컴파일러 경고를 보게 된다.
비검사 형변환 경고, 비검사 메서드 호출 경고, 비검사 매개변수화 가변인수 타입 경고, 비검사 변환 경고 등 수많은 비검사 경고들을 볼 수 있다. 비검사 경고란, 컴파일 시에 컴파일러가 자바 코드를 컴파일하고 나서 잘못된 것들에 대해 경고하는 옵션을 의미한다. javac 명령 인수에 -Xlint:uncheck 를 추가하면 해당 에러를 볼 수 있다.
public class EffectiveJavaTest {
static class Test {
Test() {}
}
static Set<Test> testSet = new HashSet();
public static void main(String[] args) {
testSet.add(new Test());
}
}
위 코드를 -Xlint:unchecked 옵션과 함께 컴파일하면 unchecked conversion 경고가 발생한다. HashSet 에 다이아몬드 연산자를 추가해 실제 타입 매개변수를 추론해야만 경고가 사라진다. 이렇게 가능한 모든 비검사 경로를 제거해야 한다. 그래야 런타임 시에 ClassCastException 이 발생할 일이 없고, 의도한 대로 잘 동작할 수 있다.
경고를 제거할 수는 없지만, 타입 안전하다고 확신할 수 있다면 @SuppressWarnings("unchecked") 애너테이션을 달아 경고를 숨기자. 단, 애너테이션을 다는 개발자가 직접 타입 안전함을 검증하지 않은 채 경고를 숨기면 경고 없이 컴파일은 되겠지만, 런타임시에 ClassCastException 이 발생할 수 있다.
@SuppressWarnings 애너테이션은 개별 지역변수 선언부터 클래스 전체까지 어떤 선언에도 달 수 있다. 하지만 가능한 좁은 범위에 적용해야 된다. 변수 선언이나 짧은 메서드, 생성자에 적용해야 한다. 어디에서 경고가 발생할지 모르니 절대 클래스 전체에 적용하면 안 된다.
public class EffectiveJavaTest {
// 수정 전
public <T> T[] toArray(T[] a) {
if (a.length < size)
return (T[]) Arrays.copyOf(elements, size, a.getClass());
System.arraycopy(elements, 0, a,0, size);
if (a.length > size)
a[size] = null;
return a;
}
// 수정 후
public <T> T[] toArray(T[] a) {
if (a.length < size) {
@SuppressWarnings("unchecked")
T[] result = (T[]) Arrays.copyOf(elements, size, a.getClass());
return result;
}
System.arraycopy(elements, 0, a,0, size);
if (a.length > size)
a[size] = null;
return a;
}
}
수정 전 코드를 보면 Arrays.copyOf 메서드에 unchecked cast 경고가 발생한다. 애너테이션은 선언에만 달 수 있기 때문에 return 구문에 적용할 수 없다. 그래서 return 지역 변수를 하나 만들고 그 변수에 애너테이션을 만들어야 한다. 범위도 줄일 수 있고 깔끔해진다. 애너테이션을 사용할 때면 그 경고를 무시해도 안전한 이유를 항상 주석으로 남기자.