요약
- 비검사 경고는 런타임에 ClassCastException을 일으킬 수 있는 잠재적 가능성을 뜻하므로, 가능한 모든 비검사 경고를 제거해야한다.
- 비검사 경고를 없앨 방법을 찾지 못한다면, 그 코드가 타입 안전함을 증명하고 가능한 범위를 좁혀서 @java.lang.SuppressWarnings("unchecked") 애노테이션으로 경고를 숨겨라. 그리고 경고를 숨기기로 한 근거를 주석으로 남겨라.
비검사 경고의 제거
비검사(unchecked) 경고는 컴파일러가 타입 안정성을 확인하는데 필요한 정보가 충분하지 않을 때 발생시키는 경고이다. 모든 비검사 경고는 런타임에 ClassCastException을 일으킬 수 있는 잠재적 가능성을 뜻하므로, 가능한 모든 비검사 경고를 제거해야한다.
대부분의 비검사 경고는 쉽게 제거할 수 있다.
public class SetExample {
/*
타입 안전성을 보장할 수 없다! (raw 타입을 사용했으므로)
HashSet에 Integer, String을 섞어서 넣고
모두 String 타입으로 파싱하면 ClassCastException 발생한다
Warning : Raw use of parameterized class 'HashSet'
*/
Set s1 = new HashSet();
/*
여전히 타입 안전성을 보장할 수 없다! (raw 타입을 사용했으므로)
Warning : Raw use of parameterized class 'HashSet'
*/
Set<String> s2 = new HashSet();
/*
타입 안전한 코드
컴파일러가 컬렉션에서 원소를 꺼내는 모든 곳에 보이지 않는 형변환을 추가하여 절대 실패하지 않음을 보장할 수 있음
*/
Set<String> s3 = new HashSet<String>();
/*
다이아몬드 연산자로 타입 추론 가능
*/
Set<String> s4 = new HashSet<>();
}
반면 제거하기 훨씬 어려운 경고도 있다. 이번 5장에서 그러한 경고를 내는 예제와 해결 방법을 전반적으로 소개한다.
@SuppressWarnings(”unchecked”)
언제 사용하나?
만약 경고를 제거할 수는 없지만, 타입이 안전하다고 확신할 수 있다면 @SuppressWarnings(”unchecked”)을 달아서 경고를 숨길 수 있다.
아래 코드는 ArrayList.java의 toArray 메소드이다. ArrayList는 toArray의 매개변수로 들어오는 T[] a의 원소가 모두 Object 타입이도록 생성하는 메소드가 어딘가에 있다. 그래서 toArray의 T[] a는 타입이 안전하다고 확신할 수 있다. 하지만 toArray 자체만 봤을 때 컴파일러는 타입이 안전하다고 보장할 수 없으므로(T → Object) 비검사 경고를 던진다. 이때 경고를 무시하기 위해 @SuppressWarnings(”unchecked”)를 사용한다. 그리고 경고를 무시해도 안전한 이유를 함께 달아야 한다.
public class SuppressWarnings {
private int size;
Object[] elements;
public <T> T[] toArray(T[] a) {
if (a.length < size) {
/**
* a의 원소는 전부 Object임
*/
@java.lang.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;
}
}
ArrayList는 toArray의 매개변수로 들어오는 T[] a의 원소가 모두 Object 타입이도록 생성하는 메소드가 어딘가에 있다. 그래서 toArray의 T[] a는 타입이 안전하다고 확신할 수 있다. 하지만 toArray 자체만 봤을 때 컴파일러는 타입이 안전하다고 보장할 수 없으므로(T → Object) 비검사 경고를 던진다. 이때 경고를 무시하기 위해 @SuppressWarnings(”unchecked”)를 사용한다. 그리고 경고를 무시해도 안전한 이유를 함께 달아야 한다.
사용시 주의할 점
SuppressWarnings("unchecked")를 사용할 때는 가능한 범위를 좁혀서 사용해야한다. 클래스 레벨에 달면 해당 클래스에서 발생할 수 있는 심각한 경고를 놓칠 수도 있다.
애너테이션은 return 문에 달 수 없기 때문에 (애노테이션 선언 시 Target 설정) @SuppressWarnings를 달 수 없다. 이러한 경우에는 return하는 참조를 지역변수로 분리해서 @SuppressWarnings를 다는 것이 좋다. 지역변수를 새로 선언하는 수고를 해야하지만 그만한 값어치가 있다.
// bad
@java.lang.SuppressWarnings("unchecked")
public class SuppressWarnings {
// .. 메소드 ..
return (T[]) Arrays.copyOf(elements, size, a.getClass());
}
// good
public class SuppressWarnings {
// .. 메소드 ..
@java.lang.SuppressWarnings("unchecked")
T[] result = (T[]) Arrays.copyOf(elements, size, a.getClass());
return result;
}
'JAVA > Effective Java' 카테고리의 다른 글
Item 32. 제네릭과 가변인수를 함께 쓸 때는 신중하라 (0) | 2023.07.11 |
---|---|
Item24. 멤버 클래스는 되도록 static으로 만들라 (0) | 2023.06.29 |
Item23. 태그 달린 클래스보다는 클래스 계층 구조를 활용하라 (0) | 2023.06.29 |
Item18. 상속보다는 컴포지션을 사용하라 (1) | 2023.05.29 |
Item10. equals는 일반 규약을 지켜 재정의하라 (1) | 2023.05.29 |