이왕이면 제네릭 타입으로 만들라
Stack을 제네릭 타입으로 만들어 보자.
public class Stack {
private Object[] elements;
private int size = 0;
private static final int DEFALUT_INITIAL_CAPACITY = 16;
public Stack() {
elements = new Object[DEFALUT_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);
}
}
1. 선언에 타입 매개변수를 추가하자. 타입이름으로느 보통 E를 사용한다.
public class Stack<E> {
private E[] elements;
private int size = 0;
private static final int DEFALUT_INITIAL_CAPACITY = 16;
public Stack() {
elements = (E[]) new Object[DEFALUT_INITIAL_CAPACITY];
}
public void push(E e) {
ensureCapacity();
elements[size++] = e;
}
public E 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);
}
}
경고 제거 하기
(E[])를 사용하는 것은 안전하지 않다. 형변환이 안전성을 해치지 않음을 확인하고 @SuppressWarning를 사용하여 제거한다.
@SuppressWarnings("unchecked")
public Stack() {
elements = (E[]) new Object[DEFALUT_INITIAL_CAPACITY];
}
다른 방법으로는 E대신에 Object를 사용하도록 하고 메서드에서 E로 형변환을 하는 것이다.
public E pop() {
if(size == 0)
throw new EmptyStackException();
@SuppressWarnings("unchecked")
E result = (E) elements[--size];
elements[size] = null;
return result;
}
제너릭 배열 생성을 제거하는 두 방법 모두 나름의 지지를 얻고 있다.
첫 번째 | 가독성이 좋다. 형변환을 배열 생성 시 단 한번만 해주면 된다. |
힙 오염을 일으킨다. |
두 번째 | 힙 오염이 없다. | 원소를 읽을 때마다 해줘야 한다. |
이왕이면 제네릭 메서드로 만들어라
매개변수화 타입을 받는 정적 유틸리티 메서드는 보통 제네릭이다. 제네릭 타입으로 바꾸기 위해서는 메서드와 제한자 타입에 매개변수 목록을 준다.
public static <E> Set<E> union(Set<E> s1, Set<E> s2) {
Set<E> result = new HashSet<>(s1);
result.addALl(s2);
return result;
}
불변 객체를 여러 타입으로 활용 할 수 있게 만드는 방법
소거때문에 어떤 타입이든 매개변수화 할 수 있다. 그래서 요청한 타입 매개변수에 맞게 타입 캐스팅을 해주는 정적 팩터리를 만들어야 한다.
private static UnaryOperator<Object> IDENTITY_FN = (t) -> t;
@SuppressWarnings("unchecked")
public static <T> UnaryOperator<T> identityFunction() {
return (UnaryOperator<T>) IDENTITY_FN;
}
UnaryOperator<Object>와 UnaryOperator<T>는 상속 관계도 아닌 서로 타른 타입이기 때문에 형변환 경고가 발생한다. 하지만 항등함수란 입력 값을 수정 없이 그대로 반환하는 특별한 함수이므로, T가 어떤 타입이든 UnaryOperator<T>를 사용해도 타입 안전하다. @SuppressWarning을 사용해서 경고를 없애주자!
재귀적 한정 타입
자기 자신이 들어간 표현식을 사용하여 타입 매개변수의 허용 범위를 한정한다. <E extends Comparable<E>>은 자기 자신만으로 타입을 한정시키는 표현이다.
public static <E extends Comparable<E>> E max(Collection<E> c);
한정적 와일드카드를 사용해 API 유연성을 높이라
다시 한번 상기시키면 List<Object>와 List<String>은 불공변이다. List<String>은 List<Object>의 하위타입이 아니라는 뜻이다.
하위 타입으로 한정시키는 방법
위 pushAll함수는 언뜻보면 잘 되는 것처럼 보이지만 Stack<Number>선언하고 Integer값을 pushAll한다면 에러가 발생한다. 하위타입이라 되야 할거 같지만 제네릭은 불공변이기 때문에 하위타입이 아니라 서로 다른 타입이다.
public void pushAll(Iterable<E> src) {
for(E e : src) {
push(e);
}
}
<? extends E>로 변경시키면 `E의 하위타입인 Iterable`이 인수로 받을수 있도록 강제한다.
public void pushAll(Iterable<? extends E> src) {
for(E e : src) {
push(e);
}
}
상위 타입을 정의하는 방법
아래 코드는 dst로 Stack에 있는 객체들을 옮기는 메서드이다. Stack<Number>로 정의하고 Collection<Object>에 넣는다면 괜찮을까? 당연히 안된다. 똑같이 Collection<Stack>과 Collection<Object>는 서로 다른 타입이기 때문이다.
public void popAll(Collection<E> dst) {
while(!isEmpty())
dst.add(pop());
}
E의 상위 타입으로 Collection을 한정시켜 사용해야 한다.
public void popAll(Collection<? super E> dst) {
while(!isEmpty())
dst.add(pop());
}
PECS : producer-extends, consumer-super
유연성을 극대화하려면 원소의 생산자나 소비자용 입력 매개변수에 와일드카드 타입을 사용하라. 생산자는 extends를 사용하고 소비자는 super를 사용하자. 타입을 정확히 지정할 때는 와일드카드를 쓰지 않아야 한다. 이것을 겟 풋 원리(Get And Put Principle)이라고 한다.
즉, 매개변수화 타입 T가 생산자라면 <? extends T>를 사용하고, 소비자라면 <? super T>를 사용하라.
아이템 28 Choser Method 수정하기.
public Chooser(Collection<? extends T> choices)
아이템 30-2 uinon Method 수정하기.
반환타입에서는 한정적 와일드카드 타입을 사용하면 안된다.
public static <E> set<E> union(Set<? extends E> s1, Set<? extends E> s2)
명시적 타입 인수 ( 자바 7까지는 해줘야 한다. )
자바 7까지는 올바른 타입을 수정하지 못할 때 명시해줘야 한다.
Set<Number> numbers = Union.<Number>union(intergers, doubles);
코드 30-7 수정하기.
public static <E extends Comparable<? super E>> E max(List<? extends E> list)
기존의 List<E>는 E를 반환하는 생성자이므로 <? extends E>로 바꿔줄 수 있다.
Comparable은 소비자이므로 <? super E>로 바꿔주면 된다.
메서드 선언에 타입 매개변수가 한 번만 나오면 와일드 카드로 대체하라.
타입 매개변수와 와일드카드에는 공통된 부분이 있어서 메서드를 정의 할 때 둘 중 어느것을 사용해도 괜찮을 때가 많다.
public static <E> void swap(List<E> list, int i, int j);
public static void swap(List<?> list, int i, int j);
public API라면 2번째가 더 낫다. 어떤 리스트든 이 메서드에 넘기면 명시한 인덱스의 원소들을 교환해 줄 것이며 신경 써야 할 매입 매개변수도 없다.
public static void swap(List<?> list, int i, int j) {
swapHelper(list, i, j);
}
public static <E> void swapHelper(List<E> list, int i, int j) {
list.set(i, list.set(j, list.get(i)));
}
'Language > Effective java' 카테고리의 다른 글
열거 타입과 애너테이션 (0) | 2022.07.13 |
---|---|
제네릭 (0) | 2022.06.26 |
제네릭 (0) | 2022.06.18 |
클래스와 인터페이스 (0) | 2022.06.17 |
클래스와 인터페이스 (0) | 2022.06.17 |
댓글