제네릭 타입은 클래스, 인터페이스, 메서드에서 사용할 데이터 타입을 일반화하여 작성할 수 있는 문법이다. 컴파일 시 타입을 체크하고, 타입 캐스팅 없이 다양한 객체를 처리할 수 있어 안정성과 재사용성을 제공한다.
// 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: 기타 보조 타입 이름상속 관계가 있는 클래스들 사이에서 타입 안정성을 유지하면서 코드의 범용성을 높이는 데 필수적인 도구다.
extends)T extends Number는 T가 Number 또는 그 하위 클래스 (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;
}
}
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로 나옴
}
}