# 제네릭

**클래스 내부에서 사용할 데이터 타입을 외부에서 지정하는 기법**
- 타입 안정성: 컴파일 시점에 타입 검사를 수행하여 타입 오류를 줄이며 런타임 시점에 ClassCastException과 같은 오류를 방지할 수 있다.
- 코드 중복 감소: 다양한 타입에 대해 동일한 로직을 수행하는 클래스나 메소드를 작성할 때, 제네릭을 사용하면 중복 코드를 최소화할 수 있다.
- 다양한 타입의 객체를 저장하는 컬렉션 클래스를 작성할 때 제네릭을 사용하면 하나의 구현으로 여러 타입을 지원할 수 있다.
- 가독성 향상: 제네릭을 사용하면 코드에서 명시적인 타입 변환을 줄일 수 있으며 코드가 간결해지고 가독성이 향상된다.
## 사용법

**Sample Code**
```java
// 제네릭 클래스 예시
public class GenericBox<T> {
private T item;
public void setItem(T item) {
this.item = item;
}
public T getItem() {
return item;
}
}
// 제네릭 메소드 예시
public static <T> void genericPrint(T[] items) {
for (T item : items) {
System.out.print(item + " ");
}
System.out.println();
}
```
---
## 타입 한정 키워드 `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);
}
}
```
---
- `<? extends 상위타입>` : Upper Bounded Wildcards (상위 클래스 제한)
- 타입 파라미터를 대치하는 구체적인 타입으로 상위 타입이나 상위 타입의 하위 타입만 올 수 있다
``` java
public static double sumOfList(List<? extends Number> numbers) {
double sum = 0;
for (Number number : numbers) {
sum += number.doubleValue();
}
return sum;
}
```
---
- `<? super 하위타입>` : Lower Bounded Wildcards (하위 클래스 제한)
- 타입 파라미터를 대치하는 구체적인 타입으로 하위 타입이나 하위 타입의 상위 타입만 올 수 있다
``` java
public static void addIntegersToList(List<? super Integer> list) {
for (int i = 1; i <= 10; i++) {
list.add(i);
}
}
```
---
## 과제
목표 : 자바로 ArrayList를 구현해본다.
**요구사항**
- 제네릭 클래스를 사용하여 다양한 데이터 타입을 저장할 수 있는 어레이 리스트(`MyArrayList`) 클래스를 구현한다.
- `MyArrayList` 클래스는 다음의 메소드를 포함해야 한다
- `public void add(T data)`: 어레이 리스트의 끝에 새로운 요소를 추가한다.
- `public void add(int index, T data)`: 지정된 인덱스에 새로운 요소를 삽입한다.
- `public T remove(int index)`: 지정된 인덱스의 요소를 삭제하고, 삭제된 요소의 데이터를 반환한다.
- `public T get(int index)`: 지정된 인덱스의 요소 데이터를 반환한다.
- `public int size()`: 어레이 리스트의 크기(요소 수)를 반환한다.
- `public boolean isEmpty()`: 어레이 리스트가 비어 있는지 확인한다.
- 작성한 `MyArrayList` 클래스를 사용하여 다양한 데이터 타입의 요소를 추가, 삭제, 조회할 수 있는 예제도 같이 작성한다.
###### tags: `과외(하희영)`