이번장의 핵심은...
로 타입을 사용하면 런타임 예외가 일어날 수 있으니 사용하면 안 된다
로 타입은 제네릭이 도입되기 이전 코드와의 호환성을 위해 제공될 뿐
Set<Object>는 어떤 타입의 객체도 저장할 수 있는 매개변수화 타입이고,
Set<?>는 모종의 타입 객체만 저장할 수 있는 와일드카드 타입이다
그리고 이들의 로 타입인 Set은 제내릭 타입 시스템에 속하지 않는다
Set<Object>와 Set<?>은 안전하지만, 로 타입인 Set은 안전하지 않다
제네릭 타입 (제네릭 클래스, 혹은 제네릭 인터페이스)
클래스와 인터페이스 선언에 타입 매개변수를 쓰는 것
List<String> → String인 리스트를 뜻하는 매개변수화 타입
제네릭 타입 하나 정의하면 그에 딸린 raw type도 함께 정의된다
Raw Type
제네릭 타입에서 타입 매개변수를 전혀 사용하지 않을 때를 말함
List<E>의 raw type은 List이다
// 컬렉션의 raw type - 따라 하지 말 것!
// Stamp 인스턴스만 취급한다
private final Collection stamps = ...;
// 이 코드를 사용하면 stamp 대신 coin을 넣어도 아무 오류 없이 컴파일되고 실행
stamps.add(new Coin(..)):
// 반복자의 raw type - 따라 하지 말 것
// 동전을 다시 꺼낼 때 에러날 것이다
for (Iterator i = stamps.iterator(); i.hasNext(); ) {
Stamp stamp = (Stamp) i.next(); // ClassCastException
stamp.cancel();
}
오류는 가능한 한 발생 즉시, 이상적으로는 컴파일할 때 발견하는 것이 좋다
이 예에서는 런타임에야 알아챌 수 있음
→ 제네릭을 활용하면 타입 선언 자체에 녹아든다
// 매개변수화된 컬레션 타입 - 타입 안정성 확보
private final Collection<Stamp> stamps = ...;
컴파일러는 stamps에는 Stamp의 인스턴스만 넣어야 함을 인지하게 된다
Raw type (로 타입 : 타입 매개변수가 없는 제네릭 타입)을 쓰는 걸 언어 차원에서 막아 놓지는 않았지만 절대로 써서는 안 된다. raw type을 쓰면 제네릭이 안겨주는 안정성과 표현력을 모두 잃게 된다.
List vs List<Object>
List와 같은 로 타입은 사용해서는 안 되나, List <Object>처럼 임의 객체를 허용하는 매개변수화 타입은 괜찮다
List는 제네릭 타입에서 완전히 발을 뺀 것이다
List <Object>는 모든 타입을 허용한다는 의사를 컴파일러에 명확히 전달한 것
매개 변수로 List를 받는 메서드에는 List <String>을 넘길 수 있지만, List <Object>를 받는 메서드엔 넘길 수 없다. (List<String>은 로 타입인 List의 하위 타입이지만, List<Object>의 하위 타입이 아님)
List<Object> 같은 매개변수화 타입을 사용할 때와 달리 List 같은 로 타입을 사용하면 타입 안정성을 잃게 된다
예를 들어,
// 런타임에 실패한다 - unsafeAdd 메서드가 로 타입(List)을 사용
public static void main(String[] args) {
List<String> strings = new ArrayList<>();
unsafeAdd(strings, Integer.valueOf(42));
String s = strings.get(0); // 컴파일러가 자동으로 형변환 코드를 넣어준다
}
private static void unsafeAdd(List list, Object o) {
list.add(o);
}
strings.get(0) 결과를 형 변환하려 할 때 ClassCastException을 던짐 (Integer→String 변환 시도)
→ 로 타입인 List를 매개변수화 타입인 List <Object>로 바꾼 다음 다시 컴파일조차 안된다
제네릭을 처음 접하는 사람이 작성할법한 코드
//잘못된 예 - 모르는 타입의 원소도 받는 로 타입을 사용했다
static int numElementsInCommon(Set s1, Set s2) {
int result = 0;
for (Object o1 : s1)
if (s2.contains(o1)) result++;
return result;
}
동작은 하지만 로 타입을 사용해 안전하지 않는다
→ 따라서 비한정 와일드카드 타입(unbounded wildcard type)을 대신 사용하는 게 좋다
제네릭 타입을 쓰고 싶지만, 실제 타입 매개변수가 무엇인지 신경 쓰고 싶지 않다면 물음표를 사용하자
Ex) Set<E> → Set<?>
→ 어떤 타입이라도 담을 수 있는 가장 범용적인 매개변수화 Set 타입
```java
// 비한정적 와일드카드 타입을 사용하라 - 타입 안전하며 유연하다
static int numElementsInCommon(Set<?> s1, Set<?> s2) {...}
```
Set<?> vs Set
와일드카드 타입은 안전하고, 로 타입은 안전하지 않다
로 타입 컬렉션에는 아무 원소나 넣을 수 있으니 타입 불변식을 훼손하기 쉬움
반면, Collection<?>에는 (null 외에는) 어떤 원소도 넣을 수 없다?
이런 제약을 받아들일 수 없다면 제네릭 메서드나 한정적 와일드카드 타입을 사용하면 된다
- 제네릭 타입<?>: Unbounded Wildcards(제한 없음)
타입 파라미터를 대치하는 구체적인 타입으로 모든 클래스나 인터페이스 타입이 올 수 있습니다.
- 제네릭 타입<? extends 상위 타입>: Upper Bounded Wildcards(상위 클래스 제한)
타입 파라미터를 대치하는 구체적인 타입으로 상위 타입이나 하위 타입이 올 수 있습니다.
- 제네릭 타입<? Super 하위 타입>: Lower Bounded Wildcards(하위 클래스 제한)
타입 파라미터를 대치하는 구체적인 타입으로 하위 타입이나 상위 타입이 올 수 있습니다.
로 타입의 소소한 예외
-
class 리터럴에는 로 타입을 써야 한다
→ 자바 명세는 class 리터럴에 매개변수화 타입을 사용하지 못하게 했다(배열과 기본 타입은 허용한다)
예를 들어, List.class, String[].class, int.class는 허용하고 List<String>.class, List<?>.class는 허용하지 않는다
-
instanceof 연산자와 관련이 있음
런타임에는 제네릭 타입 정보가 지워지므로 instanceof 연산자는 비한정적 와일드 카드 타입 <?> 이외의 매개변수화 타입에는 적용할 수 없다
그리고 로 타입이든 비한정적 와일드카드 타입이든 instanceof는 완전히 똑같이 동작
비한정적 와일드카드 타입의 '<?>'는 아무런 역할 없이 코드만 지저분하게 만드므로 차라리로 타입을 쓰는 편이 깔끔
if (o instanceof Set) { // 로타입으로 instanceof
Set<?> s = (Set<?>) o; // 와일드 카드로 ~
}
이 글은 “이펙티브 자바 3판” 책 내용을 정리한 글입니다.
만약 저작권 관련 문제가 있다면 “shk3029@kakao.com”로 메일을 보내주시면, 바로 삭제하도록 하겠습니다.
'Java > Effective Java 3E' 카테고리의 다른 글
[이펙티브자바 3판] ITEM28. 배열보다는 리스트를 사용하라 (0) | 2020.10.14 |
---|---|
[이펙티브자바 3판] ITEM27. 비검사 경고를 제거하라 (0) | 2020.10.13 |
[이펙티브자바 3판] ITEM25. 톱레벨 클래스는 한 파일에 하나만 담으라 (0) | 2020.10.05 |
[이펙티브자바 3판] ITEM24. 멤버 클래스는 되도록 static으로 만들라 (0) | 2020.10.04 |
[이펙티브자바 3판] ITEM23. 태그 달린 클래스보다는 클래스 계층 구조를 활용하라 (0) | 2020.09.30 |
댓글