# 제네릭

**클래스 내부에서 사용할 데이터 타입을 외부에서 지정하는 기법**
## 제네릭을 사용 전 코드
어떠한 타입의 객체도 저장할 수 있는 `Box` 클래스를 작성한다고 가정하자
```java
public class Box {
private Object item;
public void setItem(Object item) {
this.item = item;
}
public Object getItem() {
return item;
}
}
```
```java
public class BoxExample {
public static void main(String[] args) {
Box box = new Box();
box.setItem("홍길동");
String name = (String) box.getItem(); // 형변환 필요
System.out.println(name);
int value = (int) box.getItem(); // 컴파일 에러가 발생하지 않음. 런타임 에러 발생
System.out.println(value);
}
}
```
- 위 코드는 컴파일 시점에는 문제가 없지만, 런타임 시점에 `ClassCastException`이 발생한다.
- 이는 `Object` 타입으로 모든 객체를 다룰 수 있지만, 타입 안정성이 보장되지 않는다는 문제가 있다.
- 이러한 문제를 해결하기 위해 제네릭을 사용한다
## 사용법

- 제네릭 클래스를 선언할 때는 클래스 이름 뒤에 `<T>`를 붙여주고, 제네릭 타입을 사용할 때는 실제 타입을 명시해준다
- 제네릭 타입은 클래스, 인터페이스, 메서드에 사용할 수 있다
- 제네릭 타입을 사용할 때는 실제 타입을 명시해주어야 한다
## 제네릭을 사용한 코드
```java
// 제네릭 클래스 예시
public class GenericBox<T> {
private T item;
public void setItem(T item) {
this.item = item;
}
public T getItem() {
return item;
}
}
```
```java
public class GenericBoxExample {
public static void main(String[] args) {
GenericBox<String> box = new GenericBox<>();
box.setItem("홍길동");
String name = box.getItem();
System.out.println(name);
// int value = box.getItem(); // 컴파일 에러 발생
}
}
```
## 여러 개의 제네릭 사용하기
여러 개의 제네릭을 사용하여 다양한 타입의 데이터를 동시에 처리할 수 있는 클래스나 메서드를 구현할 수 있다. 이는 다중 타입 파라미터를 가질 수 있게 해주며, 각 타입 파라미터는 콤마(,)로 구분된다.
### 여러 제네릭을 사용한 클래스 예시
아래는 두 가지 타입의 데이터를 저장할 수 있는 `Pair` 클래스의 예시이다. 이 클래스는 두 개의 제네릭 타입 `K`와 `V`를 사용하여, 다양한 타입의 키와 값 쌍을 저장할 수 있다.
```java
public class Pair<K, V> {
private K key;
private V value;
public Pair(K key, V value) {
this.key = key;
this.value = value;
}
public K getKey() {
return key;
}
public void setKey(K key) {
this.key = key;
}
public V getValue() {
return value;
}
public void setValue(V value) {
this.value = value;
}
}
```
```java
public class PairExample {
public static void main(String[] args) {
Pair<String, Integer> pair = new Pair<>("홍길동", 30);
String name = pair.getKey();
Integer age = pair.getValue();
System.out.println("이름: " + name + ", 나이: " + age);
}
}
```
- 위 예제에서 `Pair` 클래스는 두 개의 제네릭 타입 `K`(키)와 `V`(값)를 사용한다.
- 이를 통해 다양한 타입의 키와 값 쌍을 안전하게 처리할 수 있다.
## 제네릭의 장점
- 타입 안정성: 컴파일 시점에 타입 검사를 수행하여 타입 오류를 줄이며 런타임 시점에 `ClassCastException`과 같은 오류를 방지할 수 있다.
- 코드 간결성: 명시적인 타입 캐스팅이 필요 없어져 코드가 더 간결해지고 가독성이 향상된다
- 오류 최소화: 컴파일 시점에서 오류를 잡을 수 있어, 런타임 오류를 예방한다
## 타입 한정 키워드 `extends`
- 제네릭에서 특정 클래스 또는 인터페이스를 상속하거나 구현한 타입만을 허용하는 제한을 두는 기능
- 제네릭 클래스나 메서드의 사용 범위를 제한하고, 해당 타입이 가지는 메소드나 속성에 대한 접근을 보장할 수 있게 해준다
- 제네릭에 `extends` 키워드를 붙여줌으로써 제네릭의 타입을 한정지어줄 수 있다.
- 클래스와 인터페이스 모두를 한정할 수 있다
- 인터페이스를 한정하려면 & 기호를 사용하여 구분한다
- 기본적인 용법은 `<T extends [제한 타입]>` 이다
``` java
// 한정 타입을 사용한 제네릭 클래스 예시
public class BoundedGenericBox<T extends Number> {
private T item;
public void setItem(T item) {
this.item = item;
}
public T getItem() {
return item;
}
}
// 한정 타입을 사용한 제네릭 메소드 예시
public static <T extends Comparable<T>> T findMax(T[] items) {
T maxItem = items[0];
for (T item : items) {
if (item.compareTo(maxItem) > 0) {
maxItem = item;
}
}
return maxItem;
}
```
---
## 제네릭 형변환
- 일반 변수 타입과 달리 **제네릭 서브 타입간에는 형변환이 불가능**하다.
- 대입된 타입이 `Object`여도 불가능하다
``` java
// 배열은 OK
Object[] arr = new Integer[1];
// 제네릭은 ERROR
List<Object> list = new ArrayList<Integer>();
```
``` java
List<Object> listObj = null;
List<String> listStr = null;
// 에러. List<String> -> List<Object>
listObj = (List<Object>) listStr;
// 에러. List<Object> -> List<String>
listStr = (List<String>) listObj;
```
- 제네릭 와일드카드를 이용한 형변환은 허용되지 않는다. 이는 자바의 제네릭 타입 시스템이 타입 안전성을 보장하기 위한 제한이다.
- 이런 제네릭 간의 형변환을 성립하기 위해서는 제네릭에서 제공하는 와일드카드 `?`문법을 이용해야 한다.
``` java
List<?> list = null;
List<String> listStr = null;
// 올바른 형변환. List<String> -> List<?>
list = listStr;
```
### 제네릭 와일드카드
- 제네릭 타입의 범위를 지정할 때 사용한다
- 와일드카드를 사용하면 다양한 타입의 객체를 한 번에 처리할 수 있으며, 타입 안전성을 유지하면서 유연한 코드를 작성할 수 있다
#### Unbounded Wildcards
- 제한 없음
- 타입 파라미터를 대치하는 구체적인 타입으로 모든 클래스나 인터페이스 타입이 올 수 있다
- 표현 : `<?>`
``` java
public static void printItems(List<?> items) {
for (Object item : items) {
System.out.println(item);
}
}
```
#### Upper Bounded Wildcards
- 상위 클래스 제한
- 타입 파라미터를 대치하는 구체적인 타입으로 상위 타입이나 상위 타입의 하위 타입만 올 수 있다
- 표현 : `<? extends 상위타입>`
``` java
public static double sumOfList(List<? extends Number> numbers) {
double sum = 0;
for (Number number : numbers) {
sum += number.doubleValue();
}
return sum;
}
```
#### Lower Bounded Wildcards
- 하위 클래스 제한
- 타입 파라미터를 대치하는 구체적인 타입으로 하위 타입이나 하위 타입의 상위 타입만 올 수 있다
- 표현 : `<? super 하위타입>`
``` java
public static void addIntegersToList(List<? super Integer> list) {
for (int i = 1; i <= 10; i++) {
list.add(i);
}
}
```