Notice
Recent Posts
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
Tags
- 스택
- 내돈후기
- Simple Brute-Force Algorithm
- Divide and Conquer
- parametric search
- 완전탐색
- 개념
- 간단한 완전탐색
- hint
- 알고리즘잡스
- 힙
- Sort
- 선형자료구조
- 우선순위 큐
- Adv. recursive function
- basic data-structure
- 고급정렬
- Queue
- heap
- 큐
- 기본자료구조
- 동적계획법
- 깊이우선탐색
- dfs
- 정렬
- binary search
- Stack
- 이진탐색
- 매개 변수 탐색
- Advanced Sort
- Today
- Total
루시와 프로그래밍 이야기
ITEM29. 이왕이면 제네릭 타입으로 만들라 본문
JDK가 제공하는 제네릭 타입과 메서드를 사용하는 일은 일반적으로 쉬운 편이지만,
제네릭 타입을 새로 만드는 일은 조금 더 어렵다.
Object 기반 스택
"Generic이 절실한 강력 후보! 스택!"
public class Stack {
private Object[] elements;
private mnt 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);
}
}
STEP1. 클래스 선언에 타입 매개변수 추가
- 스택이 담을 원소의 타입 하나만 추가하면 된다.
- 이때 타입 이름으로는 보통 E를 사용한다. (ITEM68)
// 컴파일 되지 않는 코드 클래스에 <E> 추가
public class Stack<E> {
// Object[] -> E[]
private E[] elements;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;
public Stack() {
// Object -> E
elements = new E[DEFAULT_INITIAL_CAPACITY];
}
// Object -> E
public void push(E e) {
ensureCapacity();
elements[size++] = e;
}
// Object -> E
public E pop() {
if (size = 0)
throw new EmptyStackException();
// Object -> E
E result = elements[-size];
elements[size] = null; //다쓴참조해제
return result;
}
// isEmpty와ensureCapacity메서드는그대로다.
}
- 컴파일되지 않는다. E[DEFAULT_INITIAL_CAPACITY]
E와 같은 실체화 불가 타입으로는 배열을 만들 수 없다.
STEP2. 제네릭 배열 생성을 금지를 우회방법이 있지!
※ 잠깐, Object VS 제네릭 혼란스럽다면!
- Object 클래스: 자바의 모든 클래스의 최상위 조상 클래스
ex) Object a; String str = (String) a;
이런식으로 Object 변수를 String, Integer 등으로 타입변환해 줄 수 있는 최상위 조상 클래스 - 제네릭 : 다양한 타입의 객체에 재사용을 높일 수 있는 기법으로,
클래스에서 사용할 타입을 외부에서 설정하는 것을 말함
ex) 재활용이 가능하고, 강제적으로 타입변환이 필요 x
2-1. 대놓고 우회
Object 배열을 생성한 다음 제네릭 배열로 형변환해보자
(E()) new Object [DEFAULT_INITIAL_CAPACITY]
// 배열을 사용한 코드를 제네릭으로 만드는 방법 1
public class Stack<E> {
// ...
// 배열 elements 는 push(E)로 넘어온 E 인스턴스만 담는다.
// 따라서 타입 안전성을 보장하지만, 이 배열의 런타임 타입은 E[]가 아닌 Object[]이다.
@SuppressWarnings("unchecked")
public Stack() {
elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY];
}
// ...
}
비검사 형변환이 타입 안정성을 해치지 않음을 프로그램이 아닌 본인 스스로 확인한다
-> stack코드에서 다른 메서드에 전달 반환되는 일이 없으므로, 비검사 형변환은 안전하다
->안전하다 판단했다면, @SuppressWarnings 어노테이션으로 경로를 숨긴다 (ITEM27)
2-2. elements 필드의 타입을 E[]에서 Object[]로 바꾸는 방법
E result = (E) elements[--size];
E는 실체화가 불가 타입이므로, 현변환이 안전하면 @SuppressWarnings("unchecked") 추가함
// 배열을 사용한 코드를 제네릭으로 만드는 방법 2
public class Stack<E> {
// 비검사 경고를 적절히 숨긴다.
public E pop() {
if (size == 0)
throw new EmptyStackException();
// push에서 E 타입만 허용하므로 이 형변환은 안전하다.
@SuppressWarnings("unchecked")
E result = (E) elements[--size];
elements[size] = null;
return result;
}
}
- 첫 번째 방법은 가독성이 더 좋다.
- 배열의 타입을 E[]로 선언하여 오직 E 타입 인스턴스만 받음을 확실히 어필한다.
- 첫 번째 방법은 형변환을 한 번만 해주면 되지만,
두 번째 방식에서는 배열의 원소를 읽을 때마다 형변환을 해줘야 한다.
- 하지만 배열의 런타임 타입이 컴파일타임 타입과 달라 힙 오염(ITEM 32)를 일으킬 수 있다.
제네릭 Stack을 만들어두면 T에 String형을 넣어 사용하는 방법
public class Stack<T> {
public static void main(String[] args) {
Stack<String> stack = new Stack<>();
for (String arg : args) {
stack.push(arg);
}
while (!stack.isEmpty()) {
System.out.println(stack.pop().toUpperCase());
}
}
}
Stack 에서 꺼낸 원소에서 String의 toUpperCase 메서드를 호출할 때 명시적 형변환을 수행하지 않는다.
(컴파일러에 의해 자동 생성)
즉, 형변환이 항상 성공함을 보장한다.
- HashMap 같은 제네릭 타입은 성능을 높일 목적으로 배열을 사용하기도 한다.
- Stack 예처럼 대다수의 제네릭 타입은 타입 매개변수에 아무런 제약을 두지 않는다.
Stack<Object>, Stack<int[]>, Stack<List<String>>, Stack 등 어떤 참조 타입으로도 Stack을 만들 수 있다. - 기본 타입은 사용할 수 없다.
Stack<int>나 Stack<double>을 만들려고 하면 컴파일 오류가 난다. - 이는 자바 제네릭 타입 시스템의 근본적인 문제이나, 박싱된 기본타입을 사용해 우회할 수 있다.(ITEM61)
정리
- 클라이언트에서 직접 형변환해야 하는 타입보다 제네릭 타입이 더 안전하고 쓰기 편하다.
- 새로운 타입을 설계할 때는 형변환 없이도 사용할 수 있도록 하라.
- 그런 경우 제네릭 타입으로 만들어야 하는 경우가 많다.
- 기존 타입 중 제네릭이었어야 하는 부분이 있다면 제네릭 타입으로 변경하자.
- 기존 클라이언트에는 아무런 영향을 주지 않으면서, 새로운 사용자를 훨씬 편하게 해주는 길이다. ITEM26
'스터디 > 이펙티브 자바' 카테고리의 다른 글
ITEM31. 한정적 와일드카드를 사용해 API 유연성을 높이라 (0) | 2022.10.13 |
---|---|
ITEM30. 이왕이면 제네릭 메서드로 만들라 (0) | 2022.10.13 |
ITEM14. Comparable을 구현할지 고려하라 (0) | 2022.09.01 |
ITEM13. clone 재정의는 주의해서 진행하라 (0) | 2022.08.31 |
ITEM12. toString을 항상 재정의하라 (0) | 2022.08.31 |
Comments