본문 바로가기
JAVA/Effective Java

Item27. 비검사 경고를 제거하라

by 민휘 2023. 7. 11.

요약

  • 비검사 경고는 런타임에 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;
}