Object 배열 기반 스택이다.
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 = elemtns[--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를 사용한다.
public class Stack {
private E[] elements;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;
public Stack() {
elements = new Object[DEFAULT_INITIAL_CAPACITY];
}
public void push(E e) {
ensureCapacity();
elements[size++] = e;
}
public E pop() {
if (size == 0)
throw new EmptyStackException();
E result = elemtns[--size];
elements[size] = null; // 다 쓴 참조 해제
return result;
}
...
}
위 코드는 컴파일에 실패한다.
E와 같은 실체화 불가타입은 배열을 만들 수 없다.
배열을 사용하는 코드를 제네릭으로 바꿀 때에는 항상 이 문제가 발생한다.
아전 포스팅에서 설명한 것 처럼,
비검사 형변환을 안전하게 구현하고, 범위를 최소로 좁혀 @SuppressWarnings 어노테이션으로 경고를 숨기자.
또한, 타입 매개변수를 선언할 때 제약을 둘 수 있다.
E extends String 으로 String의 하위 타입만 받는다는 뜻이다.
이것을 한정적 타입 매개변수라고 한다.
클래스와 마찬가지로, 메서드도 제네릭으로 만들 수 있다.
또한, Comparable인터페이스를 사용하여 재귀적 타입 한정을 사용할 수 있다.
public static <E extends Comparable<E>> E max(Collection<E> c);
모든 타입 E는 자신과 비교할 수 있다 라고 읽을 수 있다.
제네릭 타입과 마찬가지로, 클라이언트에서 입력 매개변수와 반환값을 명시적으로 형변환해야 하는 메서드보다 제네릭 메서드가 더 안전하고 사용하기도 쉽다.
메서드도 형변환없이 사용할 수 있는 편이 좋으며, 많은 경우 그렇게 하려면 제네릭 메서드가 되어야 한다.
형변환을 해줘야 하는 기존 메서드가 있다면 제네릭하게 만들어주자.
또한, 여러가지 형을 사용해야 하는 추상클래스를 정의할 때도 형마다 추상클래스를 만들어줄 필요 없이,
제네릭을 이용하면 하나의 추상클래스를 사용하여 여러가지 형의 상속 클래스들을 만들어 줄 수 있다.
질문 1: 제네릭 메서드를 사용하는 이유는?
제네릭 메서드는 컴파일 타임에 타입 체크를 수행하여 캐스팅 에러를 방지해, 타입 안정성을 보장한다.
때문에 런타임 시 에러를 방지한다.
또한 여러 타입에 동일한 로직을 수행할 수 있게 하고, 클래스나 메서드가 타입에 한정되게 하지 않아 재사용성을 높일 수 있다.
호출 시 타입에 대한 추론이 가능하기 때문에 타입 인자를 명시할 필요가 없어 코드 가독성이 향상된다.
질문 2: 제네릭 메서드를 만들 때 주의해야 할 점은?
필요한 타입 제약조건을 확실히 구분해놓는 것이 중요하다.
특정 타입이나 인터페이스를 구현하는 타입만 받을 수 있도록 제한해야 한다.
또한 와일드 카드를 사용하여 유연성을 높일 수 있다.
'JAVA > 이펙티브 자바' 카테고리의 다른 글
32. 제네릭과 가변인수를 함께 쓸 때는 신중하라 (0) | 2023.07.16 |
---|---|
31. 한정적 와일드카드를 사용해 API 유연성을 높이라 (0) | 2023.07.16 |
28. 배열보다는 리스트를 사용하라 (0) | 2023.07.16 |
27. 비검사 경고를 제거하라 (0) | 2023.07.16 |
26. 로 타입을 사용하지 말라 ( 제네릭, 타입 안정성 ) (0) | 2023.07.10 |