이펙티브 자바 5장-1) 제네릭

동동주·2025년 11월 12일

이펙티브 자바

목록 보기
5/13

💡 [아이템 26] 로 타입은 사용하지 말라 (Raw Type)

핵심 개념

구분예시설명
제네릭 클래스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

왜 존재하나?

  • 제네릭 이전 코드와의 호환성을 위해 존재 (erasure + raw type 지원)

대안: 비한정 와일드카드 (<?>)

  • 모든 타입을 다루되 타입 안전성을 유지하고 싶을 때
void printList(Collection<?> list) {
    for (Object e : list)
        System.out.println(e);
}

📍 차이점:

  • Collection<?> : 안전 (요소 삽입 불가, null 제외)
  • Collection (raw) : 안전하지 않음 (타입 불변식 훼손 가능)

예외적 허용

  1. 클래스 리터럴: List.class (O), List<String>.class (X)

  2. instanceof:

    if (obj instanceof List<?>) { ... }  // O
    if (obj instanceof List<String>) { ... }  // X

핵심 정리

✅ “로 타입은 절대 사용하지 말자.”
대신 비한정 와일드카드 타입(<?>)을 써서 타입 안전성을 지켜라.


💡 [아이템 27] 비검사 경고를 제거하라

비검사 경고(Unchecked warning)

  • 제네릭 사용 시 컴파일러가 “타입 안전성 보장 불가” 경고를 냄.

✅ 해결 방법

  1. 경고 원인을 없애라. (다이아몬드 연산자 등 활용)

    List<String> list = new ArrayList<>(); // 타입 추론으로 경고 제거
  2. 안전한 경우에만 @SuppressWarnings("unchecked") 사용

    @SuppressWarnings("unchecked")
    T[] result = (T[]) new Object[size]; // 안전하다는 주석 필수!
  3. 적용 범위를 최소화

    • 가능한 좁은 블록이나 지역 변수 수준에 붙이기.

핵심 정리

⚡ 비검사 경고는 무시하지 말고 제거하라.
정말 안전하다는 확신이 있을 때만 @SuppressWarnings("unchecked")로 감추고, 반드시 이유를 주석으로 남겨라.


💡 [아이템 28] 배열보다는 리스트를 사용하라

배열리스트
공변 (covariant): Sub[]Super[]의 하위불공변 (invariant): List<Sub>List<Super>
런타임 타입 정보 유지컴파일타임 타입 검사 가능
타입 불안전 (ArrayStoreException)타입 안정성 보장
Object[] array = new Long[1];
array[0] = "문자열";  // 런타임 에러

List<Object> list = new ArrayList<Long>(); // 컴파일 에러

👉 컴파일 타임에 잡히는 쪽(List) 이 훨씬 안전하다.


💡 [아이템 29] 이왕이면 제네릭 타입으로 만들라

✅ 예시: 제네릭 스택 구현

Before (Object 기반)

public class Stack {
    private Object[] elements;
    ...
    public void push(Object e) {...}
    public Object pop() {...}
}

→ 형변환 필요, 런타임 오류 위험

After (제네릭 기반)

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 배열로 처리하던 구조는 제네릭으로 리팩터링하라.


💡 [아이템 30] 이왕이면 제네릭 메서드로 만들어라

잘못된 예시 (Raw Type)

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; // 타입 안전함
}

재귀적 타입 한정 (Recursive Type Bound)

타입 자신과 비교 가능한 타입 한정

public static <E extends Comparable<E>> E max(Collection<E> c) {...}
  • “E는 자기 자신과 비교 가능해야 한다”는 의미

핵심 정리

⚡ 제네릭 메서드는 형변환 없이 안전하게 재사용할 수 있다.
입력과 반환 타입이 관련된 유틸리티 메서드는 반드시 제네릭으로 설계하라.


참고 글: https://github.com/back-end-study/effective-java/tree/main/5%EC%9E%A5_%EC%A0%9C%EB%84%A4%EB%A6%AD

0개의 댓글