| 구분 | 예시 | 설명 |
|---|---|---|
| 제네릭 클래스 | class Sample<T> {} | 타입 매개변수를 받는 클래스 |
| 매개변수화 타입 | Sample<String> | 실제 타입을 지정한 제네릭 타입 |
| 로 타입 (Raw Type) | Sample | 타입 매개변수를 지정하지 않은 제네릭 타입 |
ClassCastException 발생 위험List<String> strings = new ArrayList<>();
List rawList = strings; // raw type 사용
rawList.add(42); // 런타임 오류 가능!
String s = strings.get(0); // ClassCastException
<?>)void printList(Collection<?> list) {
for (Object e : list)
System.out.println(e);
}
📍 차이점:
Collection<?> : 안전 (요소 삽입 불가, null 제외)Collection (raw) : 안전하지 않음 (타입 불변식 훼손 가능)클래스 리터럴: List.class (O), List<String>.class (X)
instanceof:
if (obj instanceof List<?>) { ... } // O
if (obj instanceof List<String>) { ... } // X
✅ “로 타입은 절대 사용하지 말자.”
대신 비한정 와일드카드 타입(<?>)을 써서 타입 안전성을 지켜라.
경고 원인을 없애라. (다이아몬드 연산자 등 활용)
List<String> list = new ArrayList<>(); // 타입 추론으로 경고 제거
안전한 경우에만 @SuppressWarnings("unchecked") 사용
@SuppressWarnings("unchecked")
T[] result = (T[]) new Object[size]; // 안전하다는 주석 필수!
적용 범위를 최소화
⚡ 비검사 경고는 무시하지 말고 제거하라.
정말 안전하다는 확신이 있을 때만@SuppressWarnings("unchecked")로 감추고, 반드시 이유를 주석으로 남겨라.
| 배열 | 리스트 |
|---|---|
공변 (covariant): Sub[]는 Super[]의 하위 | 불공변 (invariant): List<Sub> ≠ List<Super> |
| 런타임 타입 정보 유지 | 컴파일타임 타입 검사 가능 |
타입 불안전 (ArrayStoreException) | 타입 안정성 보장 |
Object[] array = new Long[1];
array[0] = "문자열"; // 런타임 에러
List<Object> list = new ArrayList<Long>(); // 컴파일 에러
👉 컴파일 타임에 잡히는 쪽(List) 이 훨씬 안전하다.
public class Stack {
private Object[] elements;
...
public void push(Object e) {...}
public Object pop() {...}
}
→ 형변환 필요, 런타임 오류 위험
public class Stack<E> {
private E[] elements;
private int size = 0;
@SuppressWarnings("unchecked")
public Stack() {
elements = (E[]) new Object[16]; // 타입 안전함
}
public void push(E e) { ... }
public E pop() { ... }
}
📍 비검사 형변환을 “안전하다”고 증명할 수 있으므로 @SuppressWarnings 허용.
| 방법 | 장점 | 단점 |
|---|---|---|
(E[]) new Object[...] | 형변환 한 번만 | 힙 오염 가능 |
Object[] elements | 힙 오염 없음 | 꺼낼 때마다 형변환 필요 |
✅ 제네릭 타입으로 만들면 컴파일 시점에 타입 안정성 확보.
Object 배열로 처리하던 구조는 제네릭으로 리팩터링하라.
public static Set union(Set s1, Set s2) {
Set result = new HashSet<>(s1);
result.addAll(s2);
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;
}
→ 경고 없음, 타입 안정, 재사용성 ↑
(예: Collections.emptySet(), Function.identity())
private static final UnaryOperator<Object> IDENTITY_FN = t -> t;
@SuppressWarnings("unchecked")
public static <T> UnaryOperator<T> identityFunction() {
return (UnaryOperator<T>) IDENTITY_FN; // 타입 안전함
}
타입 자신과 비교 가능한 타입 한정
public static <E extends Comparable<E>> E max(Collection<E> c) {...}
⚡ 제네릭 메서드는 형변환 없이 안전하게 재사용할 수 있다.
입력과 반환 타입이 관련된 유틸리티 메서드는 반드시 제네릭으로 설계하라.
참고 글: https://github.com/back-end-study/effective-java/tree/main/5%EC%9E%A5_%EC%A0%9C%EB%84%A4%EB%A6%AD