제네릭 : 컴파일 타임에 타입을 체크함으로써 코드의 안정성을 높여주는 기능
<T>
/ T : 타입 매개변수<String>
/ String : 매개변수화된 타입사용 이유
컴파일 타임에 강력한 타입 검사 : 형식에 맞지 않는 타입을 넣었을 때 런타임 시에 이를 검출할 수 있는 반면, 제네릭 사용 시 컴파일 시에 검사할 수 있음
List stringList = new ArrayList<>();
stringList.add("woo");
stringList.add(1);
String result = (String) stringList.get(0) +
(String) stringList.get(1); // Runtime Error
List<String> stringList = new ArrayList<>();
stringList.add("woo");
stringList.add(1); // Compile Error
```
캐스팅(타입 변환) 제거 : 리스트에 저장되는 요소를 알 수 없어 꺼낼때 매번 타입변환 -> 제네릭 사용 시 캐스팅 제거 가능
List stringList = new ArrayList<>();
stringList.add("woo");
String result = (String) stringList.get(0);
List<String> stringList = new ArrayList<>();
stringList.add("woo");
String result = stringList.get(0);
배열 vs 제네릭 타입
// 아래 코드는 문법상 허용이 되지 않음
List<Object> objectList = new ArrayList<Integer>();
// 배열 vs 제네릭 타입
// 배열 : Integer가 Object 하위 -> 배열도 하위 타입 성립(공변)
Object[] objectArray = new Integer[1];
// Integer가 Object 하위 -> 제네릭 타입에는 적용 불가(무공변)
List<Object> objectList = new ArrayList<Integer>(); // Compile Error !
타입 계층 관계에서 서로 다른 타입간에 어떠한 관계가 있는지를 나타냄
무공변(Invariance) - <T>
- 제네릭 이에 해당
<B>
가 Category<A>
의 하위 타입이 아닌 경우. 즉 아무런 관계가 없음<Object>
와 Category<Integer>
는 아무런 관련이 없음<Object>
!= List<Integer>
공변(Covariance) - <? extends T>
<B>
가 Category<A>
의 하위 타입인 경우반공변(Contravariance) - <? super T>
<B>
가 Category<A>
의 상위 타입인 경우---- 비제네릭 ----
class Category {
private Object object;
public void set(Object object) {
this.object = object;
}
public Object get() {
return object;
}
}
---- 제네릭 변경 ----
class Category<T> {
private T t;
public void set(T t) {
this.t = t;
}
public T get() {
return t;
}
}
class NoodleCategory<T> {
private T t;
public void set(T t) {
this.t = t;
}
public T get() {
return t;
}
public <T> void printClassName(T t) {
System.out.println("클래스 필드에 정의된 타입 = " + this.t.getClass().getName());
System.out.println("제네릭 메서드에 정의된 타입 = " + t.getClass().getName());
}
}
NoodleCategory<Noodle> noodleCategory = new NoodleCategory<>();
noodleCategory.set(new Noodle()); // 패키지명.Noodle 출력
noodleCategory.printClassName(new Pasta()); // 패키지명.Pasta 출력
NoodleCategory<Noodle> noodleCategory = new NoodleCategory<>();
---- Coke도 가능 -> Noodle만 하고싶은데 Coke도 되는 현상 ---
NoodleCategory<Coke> cokeNoodleCategory = new NoodleCategory<>();
제한된 제네릭 타입
extends Noodle
을 붙여줘서 타입 제한class NoodleCategory<T extends Noodle> {
private T t;
public void set(T t) {
this.t = t;
}
public T get() {
return t;
}
}
NoodleCategory<Noodle> noodleCategory = new NoodleCategory<>();
NoodleCategory<Ramen> ramenNoodleCategory = new NoodleCategory<>();
NoodleCategory<Coke> cokeNoodleCategory = new NoodleCategory<>(); // 컴파일 에러
상한 경계 : 제네릭 클래스 사용 시 상속을 사용하여 제한하고, 위쪽에 한계를 둔 경우
코드에서 ?
문자를 와일드 카드라 하고 제네릭에서 형태는 3가지가 있음
<?>
Unbounded Wildcards : 모든 타입 가능<? extends Noodle>
Upper Wildcards : 상한 경계 <? super Noodle>
Lower Bounded Wildcards : 하한 경계 class Category<T> {
private T t;
public void set(T t) {
this.t = t;
}
public T get() {
return t;
}
}
class CategoryHelper {
public void popNoodle(Category<? extends Noodle> category) {
Noodle noodle = category.get(); // 꺼내기 ok
category.set(new Noodle()); // 저장은 NO!
}
public void pushNoodle(Category<? super Noodle> category) {
category.set(new Noodle()); // 저장 OK
Noodle noodle = category.get(); // 꺼내기 No
}
}
popNoodle : 카테고리 타입 매개변수를 Noodle로 상한 제한
pushNoodle : 카테고리 매개변수를 Noodle 타입으로 하한 제한
class NoodleCategory<E> {
private List<E> list = new ArrayList<>();
public void pushAll(Collection<? extends E> box) {
for(E e : box) {
list.add(e);
}
}
}
class NoodleCategory<E> {
private List<E> list = new ArrayList<>();
public void popAll(Collection<? super E> box) {
box.addAll(list);
list.clear();
}
}
class Category<T> {
private T t;
public void set(T t) {
this.t = t;
}
public T get() {
return t;
}
}
-> 타입 소거 1 : 런타임 시 경계 타입이 없으면 Object로 변경
class Category {
private Object t;
public void set(Object t) {
this.t = t;
}
public Object get() {
return t;
}
}
class Category<T extends Noodle> {
protected T t;
public void set(T t) {
this.t = t;
}
public T get() {
return t;
}
}
-> 타입 소거 2 : 런타임 시 경계 타입이 있으면 해당 경계타입으로 변경
class Category {
protected Noodle t;
public void set(Noodle t) {
this.t = t;
}
public Noodle get() {
return t;
}
}