1. 로 타입(raw type)은 사용하지 말라.
- 로타입(raw type) 이란? 로타입 은 제네릭(Generic) 타입에서 타입 매개변수를 전혀 사용하지 않은 때를 말한다.
- 제네릭을 지원하기 전에는 컬렉션을 다음과 같이 선언했다.
// 컬렉션의 로 타입 - 따라하지 말 것!
// Stamp 인스턴스만 취급한다.
private final Collection stamps = ...;
// 실수로 동전을 넣는다.
stamp.add( new Coin(...) );
// 반복자의 로 타입 - 따라하지 말 것!
for( Integer i = stamp.iterator(); i.hasNext(); ) {
Stamp stamp = (Stamp) i.next(); // ClassCastException을 던진다.
Stamp.cancle();
}
// 매개변수화된 컬렉션 타입 - 타입 안전성 확보!
// 이렇게 선언하면 컴파일러는 stamp에는 Stamp의 인스턴스만 넣어야 함을 컴파일러가 인지
private final Collection<Stamp> stamps = ...;
2. 배열보다는 리스트를 사용하라.
- 배열과 제네릭에는 매우 다른 타입 규칙이 적용된다. 배열은 공변이고 실체화 되는 반면, 제네릭은 불공변이고 타입 정보가 소거된다. 그 결과 배열은 런타임에는 타입 안전하지만 컴파일타임에는 그렇지 않다. (제네릭은 반대)
- 위와 같은 이유로 둘을 섞어 쓰기란 쉽지 않다. 둘을 섞어 쓰다가 컴파일 오류나 경고를 만나면, 가장 먼저 배열을 리스트로 대체하는 방법을 적용한다.
// 배열 - 런타임에 실패한다.
Object[] objectArray = new Long[1];
objectArray[0] = "타입이 달라 넣을 수 없다.";
// 리스트 - 컴파일되지 않는다.
List<Object> ol = new ArrayList<Long>(); // 호환되지 않는 타입이다.
ol.add("타입이 달라 넣을 수 없다.");
3. 이왕이면 제네릭 타입으로 만들라.
- Object 기반의 Stack 클래스
package Generic;
import java.util.Arrays;
import java.util.EmptyStackException;
public class Stack {
private Object[] elements;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;
public Stack() {
elements = new Object[DEFAULT_INITIAL_CAPACITY];
}
public void push(Object e) {
ensureCapacity();
elements[size++] = e;
}
public Object pop() {
if( size == 0 )
throw new EmptyStackException();
Object result = elements[--size];
elements[size] = null;
return result;
}
public boolean isEmpty() {
return size == 0;
}
private void ensureCapacity() {
if( elements.length == size )
elements = Arrays.copyOf( elements, 2 * size + 1 );
}
}
- 제네릭 타입으로 변경한 Stack 클래스
package Generic;
import java.util.Arrays;
import java.util.EmptyStackException;
public class Stack<E> {
private E[] elements;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;
@SuppressWarnings("unchecked")
public Stack() {
elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY];
}
public void push(E e) {
ensureCapacity();
elements[size++] = e;
}
public Object pop() {
if( size == 0 )
throw new EmptyStackException();
E result = elements[--size];
elements[size] = null;
return result;
}
public boolean isEmpty() {
return size == 0;
}
private void ensureCapacity() {
if( elements.length == size )
elements = Arrays.copyOf( elements, 2 * size + 1 );
}
}
4. 한정적 와일드카드를 사용해 API 유연성을 높여라.
- 매개변수화 타입은 불공변(invariant) 이다. 즉, 서로 다른 타입 Type1 과 Type2가 있을 때, List<Type1>은 List<Type2>의 하위 타입도 상위 타입도 아니다.
- 하지만 때론 불공변 방식보다 유연한 무언가가 필요하다.
// Stack 의 Public API를 추려보았다.
public class Stack<E> {
public Stack();
public void push(E e);
public E pop();
public boolean isEmpty();
}
// 와일드카드 타입을 사용하지 않은 pushAll 메서드 - 결함 존재.
public void pushAll(Iterable<E> src) {
for(E e : src )
push(e);
}
- 위 pushAll 메서드는 깨끗이 컴파일되지만 완벽하진 않다. Iterable src의 원소 타입이 스택의 원소 타입과 일치하면 잘 작동한다. 하지만 Stack<Number>로 선언한 후 pushAll(intVal)을 호출하면 오류메시지가 뜬다. 매개변수화 타입이 불공변 이기 때문이다.
// 생산자(producer) 매개변수에 와일드카드 타입 적용
public void pushAll(Iterable<? extends E> src {
for(E e : src )
push(e);
}
- 팩스(PECS) : producer-extends, consumer-super 공식
- 매개변수화 타입 T가 생산자라면 <? extends T>를 사용하고, 소비자라면 <? super T>를 사용.
'Java > Effective Java 3' 카테고리의 다른 글
[이팩티브 자바3] 제 4장. 클래스와 인터페이스 (0) | 2020.11.03 |
---|---|
[이팩티브 자바3] 제 3장. 모든 객체의 공통 메서드 (0) | 2020.10.05 |
[이팩티브 자바3] 제 2장. 객체 생성과 파괴 (1) | 2020.10.01 |