반응형
이 포스팅에서 작성하는 내용은 EFFECTIVE JAVA(이펙티브자바) 에서 발췌하였습니다.
아이템 26. 로 타입은 사용하지 말라
관련 용어 모음집
한글 용어 | 영문 용어 | 예 | 아이템 |
매개변수화 타입 | parameterized type | List<String> | 아이템26 |
실제 타입 매개변수 | actual type parameter | String | 아이템26 |
제네릭 타입 | generic type | List<E> | 아이템26, 29 |
정규 타입 매개변수 | formal type parameter | E | 아이템26 |
비한정적 와일드카드 타입 | unbounded wildcard type | List<?> | 아이템26 |
로 타입 | raw type | List | 아이템26 |
한정적 타입 매개변수 | bounded type parameter | <E extends Number> | 아이템29 |
재귀적 타입 한정 | recursive type bound | <T extends Comparable<T>> | 아이템30 |
한정적 와일드카드 타입 | bounded wildcard type | List<? extends Number> | 아이템31 |
제네릭 메서드 | generic method | static <E> List<E> asList(E[] a) |
아이템30 |
타입 토큰 | type token | String.class | 아이템33 |
제네릭 타입
- 클래스와 인터페이스 선언에 타입 매개변수가 쓰이는 것
- 예시로, List 인터페이스는 원소의 타입을 나타내는 타입 매개변수 E를 받는다.
- 그래서 이 인터페이스의 완전한 이름은 List<E>지만, 짧게 그냥 List라고도 자주 씀
- 제네릭 클래스와 제네릭 인터페이스를 통틀어 제네릭 타입이라 함.
- 각각의 제네릭 타입은 일련의 매개변수화 타입을 정의한다.
- ex) List<String>은 원소의 타입인 String인 리스트를 뜻하는 매개변수화 타입이다.
- 여기서 String이 정규 타입 매개변수 E에 해당하는 실제 타입 매개변수다.
로 타입(raw type)
- 제네릭 타입을 하나 정의하면 그에 딸린 로 타입(raw type)도 함께 정의된다.
- raw type? 제네릭 타입에서 타입 매개변수를 전혀 사용하지 않을 때를 말함
- ex) List<E>의 raw type은 List 다.
- 로 타입을 쓰는 걸 언어 차원에서 막아두진 않았지만, 절대로 사용하면 안된다.
- 로 타입을 쓰면 제네릭이 안겨주는 안전성과 표현력을 모두 잃게 됨
- 이 기능은 왜 있는걸까? 제네릭이 사용되기 이전 코드와 호환되기 위한 기능.
- List 같은 로 타입은 사용해서는 안되지만, List<Object> 처럼 임의 객체를 허용하는 매개변수화는 괜찮다. (ex 2 소스)
- List는 제네릭 타입에서 완전히 발을 뺀 것이고, List<Object>는 모든 타입을 허용한다는 의사를 컴파일러에 명시한 것
- List에는 List<String>을 넘길 수 있지만, List<Object>를 받는 메서드에는 넘길 수 없음
- 제네릭의 하위 타입 규칙 때문.
즉, List<String>은 로 타입인 List의 하위 타입이지만, List<Object>의 하위타입이 아님
- List<Object> 같은 배개변수화 타입을 사용할 때와 달리 List 같은 로 타입을 사용하면 타입 안정성을 잃게된 다.
ex 1-1) raw type 예제
// stamps는 Stamp Instance만 넣을 수 있다고 가정하자.
private final Collection stamps = ...;
// 이렇게 Coin Instance를 넣으면, 오류없이 컴파일되고 실행됨.
stamps.add(new Coin(...)); // unchecked call "경고"를 뱉음
for(Iterator i = stamps.iterator(); i.hasNext() ; ) {
// ClassCastException 발생(위에 넣은 Coin Instance를 Stamp로 변환하려고 해서)
Stamp stamp = (Stamp) i.next();
stamp.cancel();
}
- Coin Instance를 넣을 때는 오류가 안나지만, 실제로 꺼내서 사용하면 오류가 발생한다.
- 오류는 가능한 한 발생 즉시, 이상적으로는 컴파일할 때 발견하는 것이 좋다.
ex 1-2) 매개변수화된 컬렉션 타입 (타입 안정성이 확보)
// stamps는 Stamp Instance만 넣을 수 있다고 컴파일러에게 알려줌
private final Collection<Stamp> stamps = ...;
// stamps에 Coin Instance를 넣으면 경고가 아닌 컴파일 오류가 발생함
stamps.add(new Coin(...));
ex 2-1) raw type 예제(List 로 받는 경우)
public static void main(String[] args) {
List<String> strings = new ArrayList<>();
unsafeAdd(strings, Integer.valueOf(42));
// 컴파일은 정상작동하지만, ClassCastException이 발생(Integer를 String으로 변환하려함)
String s = strings.get(0); // 컴파일러가 자동으로 형변환 코드를 넣어줌
}
// raw type인 List 이용
private static void unsafeAdd(List list, Object o) {
list.add(o);
}
ex 2-2) List<Object> 로 받는 경우
public static void main(String[] args) {
List<String> strings = new ArrayList<>();
unsafeAdd(strings, Integer.valueOf(42));
// 컴파일조차 안 됨
String s = strings.get(0); // 컴파일러가 자동으로 형변환 코드를 넣어줌
}
// raw type인 List 이용
private static void unsafeAdd(List<Object> list, Object o) {
list.add(o);
}
원소의 타입을 몰라도 되는 로 타입을 사용하는 방법
2개의 집합(Set)을 받아 공통 원소를 반환하는 메서드
ex 3-1) 로 타입 이용(잘못된 예)
static int numElementsInCommon(Set s1, Set s2) {
int result = 0;
for (Object o : s1)
if (s2.contains(o1))
result++;
return result;
}
- 이 메서드는 동작하지만, 로 타입을 사용해 안전하지 않다.
- 이 경우에는 비한정적 와일드카드 타입을 사용하는 것이 좋다.
- 제네릭 타입을 쓰고 싶지만 실제 타입 매개변수가 무엇인지 신경쓰고 싶지 않다면 ?를 사용하면 좋다.
ex 3-2) 비한정적 와일드카드 타입 사용
static int numElementsInCommon(Set<?> s1, Set<?> s2) {
로 타입을 사용하는 예외 상황
- class 리터럴에는 로 타입을 써야 한다.
- 자바 명세는 class 리터럴에 매개변수화 타입을 사용하지 못하게 함(배열과 기본타입은 허용)
List.class, String[].class, int.class는 허용하지만, List<String>.class와 List<?>.class는 허용 안함
- 자바 명세는 class 리터럴에 매개변수화 타입을 사용하지 못하게 함(배열과 기본타입은 허용)
- 런타임시에는 제네릭 정보가 지워지므로 instanceof 연산자는 비한정적 와일드카드 타입 이외의 매개변수화 타입에는 적용할 수 없다
- 로 타입이든 비한정적 와일드카드 타입이든 instanceof 는 똑같이 동작한다.
- 비한정적 와일드카드 타입의 꺾쇠괄호와 물음표는 아무런 역할 없이 코드만 지저분하게 만드므로, 로 타입만 사용하는 것이 깔끔하다.
ex 4) raw type을 사용한 좋은 예 (instanceof 연산자)
if (o instanceof Set) { // 로 타입
Set<?> s = (Set<?>) o; // 와일드카드 타입
}
o의 타입이 Set임을 확인한 다음, 와일드카드 타입인 Set<?>로 형변환해야한다.
정리
- raw type을 사용하면 런타임에 예외가 발생할 수 있으니 사용하지 말자
- raw type은 제네릭이 도입되기 전 코드와의 호환성을 위해 제공된다.
- Set<Object>는 어떤 타입의 객체도 저장할 수 있는 매개변수화 타입
- Set<?>는 모종의 타입 객체만 저장할 수 있는 와일드카드 타입
- 이들의 raw type인 Set은 제네릭 타입 시스템에 속하지 않는다.
- Set<Object>, Set<?>는 안전하지만, raw type인 Set은 안전하지 않다.
반응형
'BE > Java' 카테고리의 다른 글
[Effective Java] 아이템 28. 배열보다는 리스트를 사용하라 (0) | 2022.10.11 |
---|---|
[Effective Java] 아이템 27. 비검사 경고를 제거하라 (0) | 2022.10.07 |
[Effective Java] 아이템 25. 톱레벨 클래스는 한 파일에 하나만 담으라 (0) | 2022.10.05 |
[Effective Java] 아이템 24. 멤버 클래스는 되도록 static으로 만들라 (0) | 2022.10.04 |
[Effective Java] 아이템 23. 태그 달린 클래스보다는 클래스 계층구조를 활용하라 (1) | 2022.09.30 |