다양한 타입의 객체들을 다루는 클래스나 메소드에 컴파일 시 타입 체크를 해주는 기능
일반적으로 유지 보수가 발생하는 경우를 살펴보면 자료형(type)의 수정이 주요 원인이다.
제네릭(Generic)은 클래스를 정의하면서 사용할 변수의 자료형을 설계할(코드 작성 시) 때 검사하는게 아니라, 컴파일할 때 자료형을 검사해서 자료형에 유연하면서도 안정성까지 고려할 수 있도록 제공하는 기능이다.
JDK 1.5 이후부터 지원한다.
자료형에 유연한 구조를 만들 수 있다.
컴파일할 때 미리 자료형을 강하게 검사해서 오류를 방지한다.
형변환 시 발생할 수 있는 속도 저하를 방지한다.
| 표기 | 의미 |
|---|---|
| E | Element |
| K | Key |
| N | Number |
| T | Type (미결정 상태) |
| V | Value |
| S, U, V | 2, 3, 4번째 타입 |
T는 미결정 상태이기 때문에 어떤 클래스형도 가능하고 자료형에 대한 정보를 컴파일할 때 최종적으로 결정한다.
class Example {
// Object 클래스형은 어떠한 클래스형이라도 모두 할당할 수 있다.
private Object data = null;
public void setData(Object data) {
this.data = data;
}
public Object getData() {
return this.data = data;
}
}
public class Main {
public static void main(String[] args) {
String str = "제네릭을 사용하지 않은 경우";
Example ex = new Example();
ex.setData(str);
// setData() 메서드의 매개 변수로 String이 지정되지만,
// 내부에서 Object 클래스형으로 업캐스팅되기 때문에 본래의 클래스형을 잃어버린다.
// 따라서 getData() 메서드가 반환하는 클래스형은 Object가 된다.
String str2 = (String)ex.getData(); // String으로 캐스팅해서 사용해야 한다.
}
}
class Example<T> {
private T data = null;
public void setData(T data) {
this.data = data;
}
public T getData() {
return this.data = data;
}
}
public class Main {
public static void main(String[] args) {
String str = "제네릭을 사용한 경우";
Example<String> ex = new Example<String>();
ex.setData(str);
String str2 = ex.getData();
}
}
// 배열
Object[] objectArray = new Integer[1]; // 성공!
// 제네릭
List<Object> objectArray = new ArrayList<Integer>(); // 컴파일 에러
FoodCategory 클래스에는 음식 종류 타입의 객체만 생성하고 싶으면 T extends Food 코드를 추가해서 타입을 제한하면 된다.
class FoodCategory<T extends Food> {
private T t;
public void set(T t) {
this.t = t;
}
public void get(T t) {
return t;
}
}
FoodCategory<Food> foodCategory = new FoodCategory<>(); // 성공!
FoodCategory<Pizza> pizzaCategory = new FoodCategory<>(); // 성공!
FoodCategory<Car> carCategory = new FoodCategory<>(); // 컴파일 에러
제네릭은 자료형을 미지정하는 것이지만, 제네릭을 사용하면 배열처럼 자료형에 대한 안정성을 확보하는 장점을 가질 수 있다.
물음표(?)는 와일드카드로 불리며, 알 수 없는 타입을 뜻한다.
변수의 제한을 완화하기 위해 상위 제한 와일드카드를 사용할 수 있다.
자신과 자신의 하위 타입 사용 가능
// List<Integer>, List<Double>, List<Number>를 인수로 받을 수 있는 메서드
public static void process(List<? extends Number> list) {
...
}
List<?>
물음표만으로 정의되어서 모든 타입을 인수로 받을 수 있다.
Object 클래스에서 제공되는 기능만을 사용할 때 사용
제너릭 클래스의 메서드 중에 List.size(), List.clear()처럼 매개 변수의 자료형에 의존하지 않는 메서드만을 사용할 때 사용
하위 경계 와일드카드는 <? super A>처럼 물음표와 super 키워드로 정의한다.
상위 경계 와일드카드와는 반대로 지정된 타입과 그 상위 타입만을 허용
예를 들어 List<? super Integer>로 정의하면 Integer의 상위인 Number와 Object가 사용 가능하다.