item 27 : 비검사 경고를 제거하라
제네릭에 익숙해질수록 마주치는 경고 수는 줄겠지만 새로 작성한 코드가 한번에 깨끗하게 컴파일되리라 기대하지는 말자
제네릭(Generic)을 사용하면서 발생하는 비검사 경고(Unchecked Warning)
에 대한 설명과 이를 해결하는 방법에 대해 알아보자. 제네릭을 사용하면 컴파일러가 타입 안전성을 검사해주지만, 경우에 따라 비검사 경고가 발생할 수 있다. 이런 경고는 런타임에 ClassCastException
이 발생할 가능성을 알려주는 중요한 신호이다.
1. 비검사 경고란?
비검사 경고(Unchecked Warning
)는 제네릭을 사용할 때 컴파일러가 발생시키는 경고 메시지이다.제네릭 타입 매개변수와 관련된 타입 변환이나 메서드 호출에서 타입 안전성을 보장할 수 없을 때 발생한다.
자주 발생하는 비검사 경고 종류
비검사 형변환 경고: 타입 변환이 안전한지 확신할 수 없을 때 발생.
비검사 메서드 호출 경고: 제네릭 메서드 호출 시 타입 안전성이 보장되지 않을 때 발생
비검사 매개변수화 가변인수 타입 경고: 제네릭 타입을 가변인수로 사용할 때 발생
비검사 변환 경고: 제네릭 타입에서 로 타입으로 변환할 때 발생
2. 비검사 경고를 제거하는 방법
1) 다이아몬드 연산자 사용
제네릭 타입을 사용할 때, 다이아몬드 연산자 (<>
)를 사용하면 컴파일러가 타입을 추론하여 경고를 제거할 수 있다.
2) @SuppressWarnings("unchecked") 애너테이션 사용
애너테이션은 선언에만 달 수 있기 때문에 return 문에는 @SuppressWarnings를 다는 게 불가능하다.
경고를 제거할 수 없는 상황에서는 @SuppressWarnings("unchecked")
애너테이션을 사용하여 경고를 숨길 수 있다. 하지만 이를 사용할 때는 타입 안전성이 보장된다고 확신할 수 있어야 하며, 경고를 숨긴 이유를 주석으로 남기는 것이 좋다.
주석을 남기는 이유: 다른 개발자가 코드를 이해하는 데 도움이 되고, 잘못된 수정으로 타입 안전성을 해치지 않도록 도와준다.
적용 범위를 좁혀야 함:
@SuppressWarnings
는 가능한 한 좁은 범위에 적용해야 한다. 보통은 변수 선언, 짧은 메서드, 생성자 등에 사용하며, 절대로 클래스 전체에 적용해서는 안 된다.
3. 비검사 경고를 무시하면 안 되는 이유
비검사 경고는 타입 안전성이 보장되지 않는다는 신호이다. 이를 무시하면 런타임에
ClassCastException
이 발생할 가능성이 있다.모든 비검사 경고를 제거하면 해당 코드는 타입 안전성이 보장되며, 런타임 오류 없이 정상 동작할 가능성이 높아진다.
4. 비검사 경고가 발생하는 이유와 해결 방법
비검사 경고는 제네릭 타입에서 타입 정보를 런타임에 유지하지 않기 때문에 발생한다. Java에서는 제네릭 타입의 정보가 타입 소거(Type Erasure) 과정에서 제거되어, 런타임에는 제네릭 타입 정보가 없다.
이를 해결하기 위해 컴파일 시점에 최대한 타입 안전성을 검사하고, 필요할 경우
@SuppressWarnings("unchecked")
로 경고를 숨긴다.
5. @SuppressWarnings
사용 시 유의사항
@SuppressWarnings
사용 시 유의사항항상 타입 안전성을 보장할 수 있는지 확인:
@SuppressWarnings("unchecked")
를 사용할 때는 경고를 무시해도 안전한지 확실해야 한다.안전한 근거를 주석으로 남겨 다른 개발자가 코드를 이해하고 유지보수할 때 도움이 되도록 한다.
적용 범위를 가능한 한 좁게 유지:
@SuppressWarnings
를 사용할 때, 메서드 전체나 클래스 전체에 적용하는 대신, 특정 로컬 변수나 짧은 코드 블록에만 적용한다.이렇게 하면 새로운 경고가 나타났을 때 이를 쉽게 식별할 수 있다.
그렇다면... 타입을 안전하다는 안전성 검증은 어떻게 하는데?
타입 안전성 검증은 코드가 제네릭 타입과 관련된 작업을 수행할 때, 런타임에 ClassCastException
과 같은 타입 관련 오류가 발생하지 않도록 보장하는 것을 말한다.
@SuppressWarnings("unchecked")
애너테이션을 사용하여 비검사 경고를 억제하기 전에, 해당 코드가 타입 안전하다는 것을 검증해야 한다.
1. 제네릭 타입의 일관성 확인
제네릭 타입의 일관성을 유지하는지 확인해야 한다. 예를 들어, 배열의 원소나 컬렉션의 요소가 같은 제네릭 타입을 가지는지 확인한한다.
위 예제에서는 배열을 복사한 결과가 매개변수로 받은 배열의 타입과 동일하다는 것을 알고 있기 때문에,
(T[])
로 형변환해도 안전하다.
이 경우 @SuppressWarnings("unchecked")
를 사용하여 비검사 경고를 억제하고, 주석으로 생성된 배열과 원래 배열의 타입이 같으므로 안전한 형변환이라고 설명한다.
2. 제네릭 타입이 명확히 보장될 때
제네릭 타입이 구체적으로 보장될 때, 즉 해당 코드가 항상 같은 타입을 다루는 상황이라면 안전하다고 할 수 있다.
위 예제에서는 List<T>
에 T
타입의 요소를 추가하고 있어 타입 안전성이 보장된다. T
타입의 요소를 다루는 작업이기 때문에 런타임에 타입 오류가 발생하지 않는는다.
3. 타입 검증을 주석으로 설명하기
타입 안전성 검증을 마쳤다면, @SuppressWarnings("unchecked")
애너테이션을 사용하기 전에 해당 코드가 왜 타입 안전한지를 주석으로 설명해야 한다. 이는 다른 개발자가 해당 코드를 이해하는 데 도움을 주고, 잘못된 수정으로 인해 타입 안전성을 해치는 일을 방지한다.
4. 제네릭 타입의 범위와 관계를 고려
상한 경계(
extends
)와 하한 경계(super
)를 사용하여 제네릭 타입의 범위와 관계를 명확하게 정의할 수 있다. 이를 통해 타입 안전성을 강화할 수 있다.
위 예제에서는 T
가 Number
의 하위 타입이라는 것을 명확히 하여 타입 안전성을 보장한다. List<T>
를 처리할 때 Number
타입으로 안전하게 사용할 수 있다.
5. 클래스 또는 메서드의 내부 구현을 신뢰할 수 있을 때
제네릭 타입을 사용하는 라이브러리의 특정 메서드나 클래스의 구현이 타입 안전성을 보장하는지 신뢰할 수 있는 경우에는 해당 경고를 억제할 수 있다. 예를 들어, 표준 라이브러리 메서드가 반환하는 제네릭 타입의 객체는 안전하다고 간주할 수 있다.
6. 기존 코드와의 호환성 유지
기존 코드와의 호환성을 위해 로 타입을 제네릭으로 변환해야 할 때
@SuppressWarnings("unchecked")
를 사용할 수 있다. 그러나 이 경우에도 타입 안전성을 확인하고, 주석을 통해 설명하는 것이 중요하하다.
위의 예에서, legacyRawList
가 List<String>
타입으로 안전하게 변환될 수 있는지 확신할 수 있는 경우에만 형변환을 수행하고 경고를 억제한다.
✨ 최종 정리
비검사 경고를 무시하지 말고 해결하라: 모든 비검사 경고를 제거하면 런타임에
ClassCastException
이 발생할 가능성을 줄이고, 타입 안전성을 보장할 수 있다.경고를 숨기기 전에 반드시 코드의 타입 안전성을 검증하라: 타입 안전성을 검증한 후에만
@SuppressWarnings("unchecked")
애너테이션을 사용하여 경고를 숨기고, 이유를 주석으로 남긴다.경고를 숨기는 범위를 최소화하라:
@SuppressWarnings
애너테이션은 가능한 한 좁은 범위에 적용하고, 클래스 전체에는 적용하지 않도록 한다.
제네릭을 사용하는 Java 코드에서는 비검사 경고를 잘 다루는 것이 중요하며, 이를 통해 타입 안전한 코드를 작성할 수 있습니다.
Last updated