Item 26
Raw Type을 사용하지 말라.
✅ Raw Type?
Raw Type은 제네릭 타입에서 타입 파라미터를 사용하지 않은 때를 의미합니다.
오라클 공식 문서를 보면 다음과 같이 기재되어 있습니다.
4.8. Raw Types
To facilitate interfacing with non-generic legacy code, it is possible to use as a type the erasure (§4.6) of a parameterized type (§4.5) or the erasure of an array type (§10.1) whose element type is a parameterized type. Such a type is called a raw type.
제네릭을 사용하지 않았던 레거시 코드의 자바 버전 호환성을 위해 있는 유형으로 Raw Type이라고 부르고 있습니다.
Raw Type, 어떤 친구인지 감이 오시나요? 🤔
이해하기 수월하도록 Item 26에서 사용될 용어들을 조금 정리했습니다.
🟦 제네릭 (Generic)?
데이터 타입을 일반화하는 것을 의미합니다.
컴파일 시에 사용할 데이터 타입을 미리 지정하는 방식이며 다음과 같은 장점이 있습니다.
- 객체 타입의 안정성을 높일 수 있다.
- 타입 변환 및 타입 검사를 줄일 수 있다.
- 잘못된 타입을 사용하지 않게 컴파일 시점에 방지해 준다.
🟦 타입 매개변수 (Type Parameter)
타입 매개변수는 클래스나 메서드를 정의할 때 타입 이름으로 사용합니다.
class Box<T> {
private T t;
}
위 코드에서 클래스에 정의된 타입 "T"가 "타입 매개변수" 입니다.
("Box<T>"를 "제네릭 타입"이라고 부릅니다.)
🟦 매개변수화 타입 (Parameterized Type)
아래 코드와 같이 타입 매개변수 T를 구체적인 타입으로 대체해서 사용합니다.
Box<String> box = new Box<>();
즉, 타입 매개변수를 실제 타입으로 대체한 결과물 List<String>을 "매개변수화 타입"이라고 합니다.
(String은 "실제 타입 매개변수"라고 부릅니다.)
🟦 Raw Type
Raw Type은 제네릭을 사용하지 않는 것을 의미합니다.
다음과 같은 타입 매개변수가 있다고 해봅시다.
List<T> values;
이러한 제네릭 클래스를 Raw Type으로 표현하면 아래와 같아집니다.
List values;
@Test
void 컴파일러_타입_불안정_메시지() {
final List values = new ArrayList();
values.add("");
values.add('a');
values.add(1);
}
Raw Type은 안정성을 보장할 수 없고 컴파일러 경고를 발생시킵니다.
Raw Type 사용을 왜 권장하지 않는지 조금 더 자세히 알아볼까요 ?
먼저 코드를 작성했습니다.
제네릭 클래스 Example<T>을 만들고 필드에 제네릭 타입 List<T>를 선언하고
테스트로는 "로_타입()"과 "제네릭_타입()"이 있습니다.
그리고 매우 신경 쓰이는 빨간 밑 줄도 "로_타입()" 내부에서 보실 수 있을 겁니다.
로_타입 테스트에서 왜 컴파일 에러가 발생했을까요? 🤔
Raw Type은 타입 파라미터가 없는 제네릭 타입입니다.
위 코드에서 "로_타입()" 내부에 변수명 example을 바로 Raw Type 변수라고 합니다.
Example 클래스는 제네릭 타입으로 정의되어 있지만 example은 타입 파라미터 없이 정의된 것을 볼 수 있습니다.
Raw Type은 해당 클래스에 정의된 모든 타입 파라미터를 없앱니다.
이 때문에 example.getValues()의 반환 타입은 List<T>가 아니라 List입니다.
결국 아래와 같이 Object로 받아서 사용할 수 있습니다.
하지만 타입 변환 체크와 타입 변환 작업을 진행해줘야 하고 ClassCastException이 발생할 수 있는 불안정한 상황이 생깁니다 !! 😵
지금은 컴파일에서 현재 코드에 문제가 되었다는 것을 알 수 있지만
만약 애플리케이션 실행 중에 런타임 에러가 발생하게 된다면 큰 문제가 될 수도 있습니다.
이제부터는 Raw Type을 사용하지 말고 더 안전한 방법을 알아봅시다 🔥
✅ 매개변수화된 컬렉션 타입
컴파일러는 Collection에서 원소를 꺼낼 때 자동 형변환 해주기 때문에 실패하지 않음을 보장해 줍니다.
그에 비해 Raw Type을 쓰면 제네릭 주는 안정성, 표현력 잃게 됩니다.
아래 코드는 "List<String>" 으로 매개변수화 타입으로 문자열을 추가하는 코드입니다.
실제 매개변수 타입을 Object으로 둔 List<Object>는 괜찮을까 ?
-> 모든 타입을 허용한다는 의사를 컴파일러에 명확히 전달한 것이므로 괜찮습니다.
(List<Object>는 유연성이 높지만 타입 체크를 해줘야 하는 번거로움과 위험성이 있습니다.)
✅ Raw Type 말고 제네릭 타입을 사용하자.
만약 원소 타입을 어떤 것일지 모르는 상황에서 유연성을 위해 Raw Type을 사용하는 것은 지양하는 게 좋습니다.
Raw Type은 모르는 타입의 원소도 받을 수 있는데 이러한 행동은 안전성을 많이 떨어뜨리는 방법입니다.
이런 경우에는 제네릭을 Raw Type 대신 사용해서 안정성을 높이는 것이 좋습니다.
✅ 비한정적 와일드카드 타입을 사용하라.
위에서 타입 매개변수 "T"를 두고 사용하고 있는데
만약 "Set<String> s2"의 실제 매개변수 타입 "String"을 다른 타입으로 수정하고 싶다면 다음과 같이 다시 작성해줘야 합니다.
이러한 경우 "비한정 와일드카드 타입"을 사용해도 좋습니다.
실제 타입 매개변수를 신경 쓰고 싶지 않으면 물음표(?)를 사용하는 것도 좋은 방법입니다.
제네릭 타입인 "Set<E>"의 비한정적 와일드카드 타입은 "Set<?>"입니다.
비한정적 와일드카드 타입 "Set<?>"은 어떤 타입도 가질 수 있는 Set 입니다.
"Raw Type Collection"에는
아무 원소나 넣을 수 있으므로 오류가 발생하기 쉽지만 "Collection<?>"에는 어떤 원소도 넣을 수 없다.
다른 원소를 넣으려 시도하면 다음과 같이 컴파일 오류가 발생하는 것을 확인할 수 있습니다.
🔥 결론
- Raw Type, 런타임 예외 발생 가능
- Raw Type, 제네릭이 도입되기 이전 레거시 코드와 호환하기 위해 존재
- Set<Object>, 어떤 타입의 객체도 저장할 수 있는 매개변수화 타입
- Set<?>, 모든 모종의 타입 객체만 저장할 수 있는 와일드카드 타입
- Raw Type인 Set, 제네릭 타입 시스템에 속하지 않는다.
- Set<Object>와 Set<?>는 안전하지만, Raw Type인 Set은 안전하지 않다.
'💬 언어 > 이펙티브 자바' 카테고리의 다른 글
[이펙티브 자바] Item 23 - 태그 달린 클래스보다는 클래스 계층구조를 활용하라 (0) | 2023.04.14 |
---|---|
[이펙티브 자바] Item 22 - 인터페이스 타입을 정의하는 용도로만 사용하라 (1) | 2023.03.20 |
[이펙티브 자바] Item 20 - 추상 클래스보다는 인터페이스를 우선하라 (0) | 2023.03.13 |
[이펙티브 자바] Item 49 - 매개변수가 유효한지 검사하라 (0) | 2023.03.06 |
[이펙티브 자바] Item 32 - 제네릭과 가변인수를 함께 쓸 때는 신중하라 (0) | 2023.03.05 |