컬렉션 클래스들 타입 다음에 <String>, <Integer>, <Character> 등으로 붙여진 형식들을 많이 보셨을건데, 이게 바로 제네릭스입니다.
제네릭스란 클래스가 가질 타입을 미리 명시해줌으로써 제네릭스를 사용한 클래스의 객체가 형변환을 하지 않고 사용할 수 있도록 해줍니다.
하지만 만약 List<String> list = new ArrayList<>(); 로 제네릭스를 명시했는데 이 list 객체에 Integer 타입을 넣게 된다면 컴파일 에러가 발생하므로
제네릭스를 정하면 그 제네릭스에 맞는 타입을 사용해야 합니다.
제네릭스의 장점
1. 타입의 안정성을 제공한다.
2. 타입체크와 형변환을 생략할 수 있으므로 코드가 간결해진다.
대표적인 제네릭스의 타입
<T> 타입 제네릭스는 Object로 모든 종류의 타입을 지정 가능 합니다.
타입 | 설명 |
<T> | Object |
<E> | Element |
<K> | Key |
<V> | Value |
<N> | Number |
위 표를 예시로 HashMap 컬렉션 클래스를 보면 다음과 같이 제네릭스가 정의되어 있습니다.
public class HashMap <K, V> { ... }
제네릭스 클래스의 객체 생성과 사용
제네릭스 클래스 Box<K, V>가 다음과 같이 정의되어 있다고 가정해봅시다.
이 Box<K, V>의 객체에는 두 가지 종류 K, V타입의 객체만 저장할 수 있습니다.
아래 코드대로라면, K는 String 타입이 되고, V는 Interger 타입이 됩니다.
주의할점은, 타입 파라미터로 명시할 수 있는 것은 참조형타입만 올 수 있습니다.
즉, 기본형 타입인 int, char, double 등은 타입으로 올 수 없습니다.
class Box<K, V> {
HashMap<String, Integer> hashmap = new HashMap<>();
}
예제
import java.util.ArrayList;
class Fruit { public String toString() { return "Fruit";}}
class Apple extends Fruit { public String toString() { return "Apple";}}
class Grape extends Fruit { public String toString() { return "Grape";}}
class Toy { public String toString() { return "Toy" ;}}
class FruitBoxEx1 {
public static void main(String[] args) {
// Box -> fruitBox로 다운 캐스팅했지만 제네릭스 <Fruit>을 지정해 형변환이 생략되었다.
Box<Fruit> fruitBox = new Box<>();
// Box -> appleBox로 다운 캐스팅했지만 제네릭스 <Apple>을 지정해 형변환이 생략되었다.
Box<Apple> appleBox = new Box<>();
// Box -> toyBox로 다운 캐스팅했지만 제네릭스 <Toy>을 지정해 형변환이 생략되었다.
Box<Toy> toyBox = new Box<>();
fruitBox.add(new Fruit());
fruitBox.add(new Apple()); // OK. Box<Fruit> => Apple은 Fruit의 자손이다.
appleBox.add(new Apple());
appleBox.add(new Apple());
// appleBox.add(new Toy()); // 에러. Box<Apple>에는 Apple만 담을 수 있다.
toyBox.add(new Toy());
// toyBox.add(new Apple()); // 에러. Box<Toy>에는 Toy만 담을 수 있다.
System.out.println(fruitBox);
System.out.println(appleBox);
System.out.println(toyBox);
}
}
class Box<T> {
ArrayList<T> list = new ArrayList<T>();
void add(T item) { list.add(item); }
T get(int i) { return list.get(i); }
int size() { return list.size(); }
public String toString() { return list.toString();}
}
위 예제를 보면 제네릭스를 지정함으로써 다운 캐스팅에서도 형변환이 생략된 것을 볼 수 있습니다.
제한된 제네릭스와 와일드 카드
<T> 타입 문자로 사용할 타입을 명시하면 한 종류의 타입만 저장할 수 있도록 제한되어 있지만,
모든 종류의 타입을 지정할 수 있습니다.
하지만 제네릭스 타입에 "extends"를 사용한다면, 특정 타입의 자손들만 대입할 수 있게 제한 할 수 있습니다.
아래 코드 처럼 extends를 사용하면 Fruit와 그의 자손만 타입으로 지정가능하도록 제한을 걸 수가 있습니다.
class Box<T extends Fruit> {
ArrayList<T> list = new ArrayList<T>();
...
}
와일드카드
다음과 같은 와일드 카드로 특정 범위 내로 좁혀 제한을 걸 수있습니다.
?는 와일드 카드로 "알 수 없는 타입"이라는 의미를 가지며, 어떠한 타입도 될 수 있습니다.
<? extends T> : T와 T의 자손 타입만 가능하다. (상한 제한)
<? super T> : T와 T의 부모(조상) 타입만 가능하다. (하한 제한)
<?> : 모든 타입 가능(제한 없음) , <? extends Object>랑 동일하다.
예제
import java.util.ArrayList;
class Fruit implements Eatable {
public String toString() { return "Fruit";}
}
class Apple extends Fruit { public String toString() { return "Apple";}}
class Grape extends Fruit { public String toString() { return "Grape";}}
class Toy { public String toString() { return "Toy" ;}}
interface Eatable {}
class FruitBoxEx2 {
public static void main(String[] args) {
FruitBox<Fruit> fruitBox = new FruitBox<>();
FruitBox<Apple> appleBox = new FruitBox<>();
FruitBox<Grape> grapeBox = new FruitBox<>();
// FruitBox<Toy> toyBox = new FruitBox<Toy>(); // 에러. (제한) Toy는 Fruit의 자손이 아니다.
fruitBox.add(new Fruit());
fruitBox.add(new Apple());
fruitBox.add(new Grape());
appleBox.add(new Apple());
// appleBox.add(new Grape()); // 에러. Grape는 Apple의 자손이 아니다.
grapeBox.add(new Grape());
System.out.println("fruitBox-"+fruitBox);
System.out.println("appleBox-"+appleBox);
System.out.println("grapeBox-"+grapeBox);
} // main
}
class FruitBox<T extends Fruit & Eatable> extends Box<T> {}
class Box<T> {
ArrayList<T> list = new ArrayList<T>();
void add(T item) { list.add(item); }
T get(int i) { return list.get(i); }
int size() { return list.size(); }
public String toString() { return list.toString();}
}
참고자료 : 자바의 정석3
'◼ JAVA' 카테고리의 다른 글
[Java/자바] Stream(스트림)이란? 사용법 총정리 (0) | 2022.11.05 |
---|---|
[우아한테크코스 백엔드 5기] 프리코스 1주차 회고 (온보딩) (2) | 2022.11.03 |
[Java/자바] HashSet과 HashMap의 차이점 (0) | 2022.11.01 |
[Java/자바] HashMap 클래스 사용법 (0) | 2022.11.01 |
[Java/자바] 배열(Array)을 리스트(List)로 변환 (0) | 2022.11.01 |