[Java] Generic Type

김선형·2025년 9월 6일

Java

목록 보기
8/27

개요

제네릭 타입은 클래스, 인터페이스, 메서드에서 사용할 데이터 타입을 일반화하여 작성할 수 있는 문법이다. 컴파일 시 타입을 체크하고, 타입 캐스팅 없이 다양한 객체를 처리할 수 있어 안정성과 재사용성을 제공한다.

// Stock.java
class Stock {
    private String name;
    private double price;

    public Stock(String name, double price) {
        this.name = name;
        this.price = price;
    }

    public String getName() {
        return name;
    }

    public double getPrice() {
        return price;
    }
}
// Box.java
class Box<T> {
    private T item;

    public void setItem(T item) {
        this.item = item;
    }

    public T getItem() {
        return item;
    }
}
// BoxExample.java
public class BoxExample {
    public static void main(String[] args) {
        Box<Integer> intBox = new Box<>();
        intBox.setItem(100);
        System.out.println("Integer: " + intBox.getItem());

        Box<String> strBox = new Box<>();
        strBox.setItem("Apple");
        System.out.println("String: " + strBox.getItem());

        Stock stock = new Stock("Apple", 10000.0);
        Box<Stock> stockBox = new Box<>();
        stockBox.setItem(stock);
        System.out.println("Stock Name: " + stockBox.getItem().getName());
    }
}

관용적인 제네릭 타입 매개 변수

제네릭 타입 매개 변수의 이름은 꼭 T일 필요는 없으나 관례적으로 T를 많이 사용한다. 가독성과 의미 전달을 위해 관용적인 알파벳 약어를 사용하는 것을 권고한다.

  • T: Type (일반적인 타입)
  • E: Element (컬렉션 요소용)
  • K: Key (Map의 키)
  • V: Value (Map의 값)
  • N: Number (숫자형)
  • S, U, R: 기타 보조 타입 이름

제네릭 타입의 제한

상속 관계가 있는 클래스들 사이에서 타입 안정성을 유지하면서 코드의 범용성을 높이는 데 필수적인 도구다.

상한 제한 (Upper Bound - extends)

T extends NumberTNumber 또는 그 하위 클래스 (Integer, Double, Float) 등만 허용한다.

class NumberBox<T extends Number> {
    private T value;

    public void printDouble() {
        System.out.println(value.doubleValue());
    }

    public void setValue(T value) {
        this.value = value;
    }
}

하한 제한 (Lower Bound - super)

제네릭 클래스의 정의에는 사용하지 않고, 메서드 매개변수나 와일드카드에서만 사용한다. List<Number>List<Object>에 안전하게 integer를 추가할 수 있다. 반대로 list.get()Object로만 얻을 수 있다. 하한이므로 타입 정보가 모호하다.

import java.util.ArrayList;
import java.util.List;

public class LowerBound {
    public static void addIntegers(List<? super Integer> list) {
        list.add(1);
        list.add(2);
    }

    public static void main(String[] args) {
        List<Number> numbers = new ArrayList<>();
        addIntegers(numbers);

        System.out.println("List: " + numbers);
    }
}

제네릭 와일드카드: ?

제네릭 타입을 알 수 없거나 상관 없이 처리할 때 사용하는 자리 표시자다.

문법의미비고
<?>모든 타입 허용 (Unbounded wildcard)값을 읽을 때
<? extends T>T 또는 T의 하위 타입 (Upper bound wildcard)값을 읽을 때 – 생산자 역할 (Producer Extends)
<? super T>T 또는 T의 상위 타입 (Lower bound wildcard)값을 쓸 때 – 소비자 역할 (Consumer Super)
import java.util.ArrayList;
import java.util.List;

public class AnimalExample {
    public static void main(String[] args) {

        // List<Animal> 사용
        List<Animal> animals = new ArrayList<>();
        animals.add(new Dog());
        animals.add(new Cat());

        for (Animal a : animals) {
            a.speak(); // OK
        }

        // 상한 와일드 카드 <? extends Animal> - 읽기 전용 (Producer Extends)
        List<? extends Animal> animalList = new ArrayList<Dog>();
        // animalList.add(new Dog()); // 컴파일 오류: <? extends Animal>에는 구체적인 요소 추가 불가
        // animalList.add(new Animal());

        // 읽기만 가능하지만, 현재 리스트는 비어 있음(new ArrayList<Dog>())
        // Animal a = animalList.get(0); // 런타임 오류: IndexOutOfBoundsException (비어있는
        // 리스트에서 get(0))
        // a.speak();

        // 하한 와일드 카드 <? super Dog> - 쓰기 위주 (Consumer Super)
        List<? super Dog> dogList = new ArrayList<Animal>();
        dogList.add(new Dog());
        // dogList.add(new Animal()); // 컴파일 오류: <? super Dog>에는 Dog 이상(상위)은 보장되지 않음 ->
        // Animal 추가 불가

        Object obj = dogList.get(0); // 읽을 수 있지만 타입은 Object로 나옴
    }
}
profile
선형의 비선형적 기록 🐜

0개의 댓글