코드 한 줄의 기록

Java 제네릭 완벽 가이드: 타입 파라미터부터 와일드카드까지 실전 총정리 본문

JAVA

Java 제네릭 완벽 가이드: 타입 파라미터부터 와일드카드까지 실전 총정리

CodeByJin 2025. 10. 28. 07:36
반응형

최근에 자바 프로젝트를 진행하면서 제네릭에 대해 제대로 공부할 필요성을 느꼈습니다. 그동안 ArrayList<String> 정도만 사용했는데, 실무 코드를 보니 <? extends T>나 <? super T> 같은 복잡한 문법이 자주 등장하더군요. 그래서 이번 기회에 제네릭을 처음부터 끝까지 정리하면서 저처럼 제네릭이 헷갈리는 분들과 함께 공부하고자 이 글을 작성하게 되었습니다.

제네릭이 왜 필요한가

자바를 처음 배울 때는 제네릭 없이도 프로그래밍이 가능했습니다. 하지만 실제로 프로젝트를 진행하다 보면 제네릭이 왜 필요한지 절실히 느끼게 됩니다.

List list = new ArrayList();
list.add("Hello");
list.add("World");
String text = (String) list.get(0); // 타입 캐스팅 필요

이런 방식의 문제는 컴파일 시점에는 잘못된 타입을 넣어도 에러가 발생하지 않는다는 것입니다.

List list = new ArrayList();
list.add("a");
list.add("b");
list.add("c");
int a = (Integer) list.get(0) / 1; // 런타임 에러 발생!

반면 제네릭을 사용하면 컴파일 시점에 타입을 체크할 수 있습니다.

제네릭 클래스 만들기

public class Box<T> {
    private T content;
    
    public void set(T content) {
        this.content = content;
    }
    
    public T get() {
        return content;
    }
}
Box<String> stringBox = new Box<>();
stringBox.set("Hello");
String text = stringBox.get(); 

Box<Integer> intBox = new Box<>();
intBox.set(100);
Integer number = intBox.get();

제네릭 메서드 제대로 이해하기

public class Util {
    public static <T> Box<T> boxing(T t) {
        Box<T> box = new Box<>();
        box.set(t);
        return box;
    }
}
// 타입 명시적 지정
Box<Integer> box1 = Util.<Integer>boxing(100);

// 타입 추론
Box<Integer> box2 = Util.boxing(100);

타입 파라미터 제한하기

public class Calculator<T extends Number> {
    private T number;
    
    public double getSquare() {
        return number.doubleValue() * number.doubleValue();
    }
}

와일드카드의 세계

비한정적 와일드카드

public static void printList(List<?> list) {
    for (Object item : list) {
        System.out.println(item);
    }
}

상한 경계 와일드카드

public static double sumOfList(List<? extends Number> list) {
    double sum = 0.0;
    for (Number number : list) {
        sum += number.doubleValue();
    }
    return sum;
}

하한 경계 와일드카드

public static void addNumbers(List<? super Integer> list) {
    list.add(1);
    list.add(2);
    list.add(3);
}

PECS 공식으로 와일드카드 정복하기

public void printCollection(Collection<? extends MyClass> collection) {
    for (MyClass item : collection) {
        System.out.println(item);
    }
}

public void addElement(Collection<? super MyClass> collection) {
    collection.add(new MyClass());
}

제네릭의 타입 소거

// 소거 전
class Box<T extends Number> {
    private T value;
    T getValue() {
        return value;
    }
}

// 소거 후
class Box {
    private Number value;
    Number getValue() {
        return value;
    }
}

실전에서 자주 마주치는 제네릭 패턴

제네릭 싱글톤 패턴

public class GenericSingleton {
    private static final GenericSingleton INSTANCE = new GenericSingleton();
    
    private GenericSingleton() {}
    
    public static <T> Function<T, T> identityFunction() {
        return (T t) -> t;
    }
}

빌더 패턴과 제네릭

public class Response<T> {
    private int code;
    private String message;
    private T data;
    
    public static <T> Builder<T> builder() {
        return new Builder<>();
    }
    
    public static class Builder<T> {
        private int code;
        private String message;
        private T data;
        
        public Builder<T> code(int code) {
            this.code = code;
            return this;
        }
        
        public Builder<T> message(String message) {
            this.message = message;
            return this;
        }
        
        public Builder<T> data(T data) {
            this.data = data;
            return this;
        }
        
        public Response<T> build() {
            return new Response<>(code, message, data);
        }
    }
}

제네릭 DAO 패턴

public abstract class BaseDao<T> {
    protected Class<T> entityClass;
    
    public BaseDao(Class<T> entityClass) {
        this.entityClass = entityClass;
    }
    
    public T findById(Long id) { }
    public List<T> findAll() { }
    public void save(T entity) { }
}

public class UserDao extends BaseDao<User> {
    public UserDao() {
        super(User.class);
    }
}

제네릭 사용 시 주의할 점

// 나쁜 예
List list = new ArrayList();

// 좋은 예
List<Object> list = new ArrayList<>();
@SuppressWarnings("unchecked")
T[] array = (T[]) new Object[10];

 

제네릭을 공부하면서 처음에는 복잡하고 어려웠지만, 하나씩 정리하다 보니 전체적인 그림이 보이기 시작했습니다. 제네릭 클래스와 메서드의 차이, 와일드카드의 세 가지 형태, PECS 공식 등을 이해하고 나니 실무 코드를 읽는 것도 한결 수월해졌습니다. 앞으로도 코드를 직접 작성해 보면서 익히면 훨씬 자연스럽게 익숙해질 것입니다.

Java Iterator와 Fail-Fast, 컬렉션 수정 시 주의할 점들

자바로 개발하다 보면 리스트나 셋 같은 컬렉션을 순회하면서 요소를 추가하거나 삭제해야 할 때가 있다. 그런데 이 과정에서 ConcurrentModificationException이라는 예외를 만나본 적 있지 않은가? 처

byteandbit.tistory.com

반응형