# 제네릭 ![](https://i.imgur.com/wQvvOD3.png) **클래스 내부에서 사용할 데이터 타입을 외부에서 지정하는 기법** ## 제네릭을 사용 전 코드 어떠한 타입의 객체도 저장할 수 있는 `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` 타입으로 모든 객체를 다룰 수 있지만, 타입 안정성이 보장되지 않는다는 문제가 있다. - 이러한 문제를 해결하기 위해 제네릭을 사용한다 ## 사용법 ![](https://i.imgur.com/jcUquyK.png) - 제네릭 클래스를 선언할 때는 클래스 이름 뒤에 `<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); } } ```