1. 와일드 카드 정의
하나의 참조 변수로 대입된 타입이 다른 객체에 참조가 가능하다.
와일드 카드의 종류는 세 가지이다.
- <? extends T>: T와 그 자손들만 가능하다.
- <? super T>: T와 그 조상들만 가능하다.
- <?>: 제한이 없다. 모든 타입이 가능하다.
TV가 Item의 자손임을 가정했을 때
ArrayList<Item> list = new ArrayList<TV>(); //에러 발생
지네릭을 선언하고, 위의 코드를 작성하면 타입이 불일치라 에러가 발생했다.
그러나 와일드 카드는 이 한계를 넘어설 수 있다.
ArrayList<? extends Item> list = new ArrayList<Ball>();
ArrayList<? extends Item> list = new ArrayList<phone>();
item의 자손들인 Ball과 phone을 위의 코드처럼 선언할 수 있다.
와일드 카드는 메서드의 매개변수에도 사용이 가능하다.
static Snack makeSnack(SnackBox<? extends choco> indegrident){
String name = "";
for(choco c: indegrident.getList()) name += c+" ";
return new Snack(name);
}
machine.makeSnack(new SnackBox<white>());
machine.makeSnack(new SnackBox<brown>());
2. 지네릭 메서드
(1) 정의
지네릭 타입이 선언된 메서드를 의미한다.
static <T> void exercise(List<T> list, Comparator<? extends T> c)
참고로 클래스 타입의 매개변수 <T>와 메서드의 타입 매개변수 <T>는 별개이다.
class snack<T>{
static<T> void make(List<T> list, Comparator<? super T> c){
...
}
}
class snack<T>과 static<T> void make는 타입 문자가 동일하지만, 서로 다른 타입 변수이다.
(2) 타입 대입
메서드를 호출할 때마다 타입을 대입해야 한다.(대부분 생략 가능하다.)
static <T extends ingredient> Snack makeSnack(SnackBox<T> box){
String name = "";
for(choco c: indegrident.getList()) name += c+" ";
return new Snack(name);
}
SnackBox<choco> chocoBox = new SnackBox<choco>();
SnackBox<white> whiteBox = new SnackBox<white>();
machine.<choco>makeSnack(chocoBox);
machine.<white>makeSnack(whiteBox);
machine 클래스에 makeSnack 메서드가 있다면 위처럼 메서드를 선언해야 한다.
그리고 machine.<choco>makeSnack(chocoBox)에서 <choco>를 생략할 수 있다.
이미 이전에 SnackBox<choco> chocoBox = new chocoBox<choco>(); 에서 갈색 부분과 일치하기 때문이다.
따라서 machine.makeSnack(chocoBox)로 축소할 수 있다.
단, 메서드를 호출할 때 타입을 생략하지 않을 때는 클래스 이름 생략 불가이다.
<choco>makeSnack(chocoBox); // 에러. 클래스 이름 생략 불가.
this.<white>makeSnack(whiteBox); // 가능
machine.<white>makeSnack(whiteBox); // 가능
만약 에러가 나면 생략하지 않고 다 적는 걸 기억하면 된다.
3. 지네릭 타입 형변환
지네릭 타입과 원시 타입 간의 형변환은 바람직 하지 않다.(경고 발생)
Box<Object> objBox = null;
Box box = (Box)objBox; // 가능. 지네릭 -> 원시. 경고 발생
objBox = (Box<Object>)box; // 가능. 원시 -> 지네릭. 경고 발생
그래도 가급적 하지 않는 게 좋다.
4. 지네릭 타입 컴파일러
1) 지네릭 타입의 경계를 제거
class Box<T extends Fruit>{
void add(T t){
...
}
}
위의 코드를 컴파일러는 아래의 코드로 변환한다.
class Box{
void add(Fruit t){
...
}
}
자바는 하위 호환성을 안정성 때문에 엄청 중요하게 생각한다. 그래서 하위 호환성에 발목을 잡혀서 빠르게 발전하지 못한 경우가 있다.
참고로 C#은 자바와 달리 컴파일 때 지네릭 타입을 유지한다. 지네릭을 유지하는 게 성능 문제라든가 여러 장점이 있다.
2) 지네릭 타입 제거 후에 타입이 불일치하면 형변환 추가
get(int i){
return list.get(i);
}
아래와 같이 형변환을 추가한다.
Fruit get(int i){
return (Fruit)list.get(i);
}
3) 와일드 카드가 포함한 경우 적절한 타입으로 형변환
static Juice makeJuice(FruitBox<? extends Fruit> box){
String tmp = "";
for(Fruit f: box.getList())
tmp+=f+" ";
return new Juice(tmp);
}
static Juice makeJuice(FruitBox box){
String tmp = "";
Iterator it = box.getList().iterator();
while(it.hasNext()){
tmp+=(Fruit)it.next()+" ";
}
return new Juice(tmp);
}
5. 정리
지네릭 메서드: 메서드를 호출할 때마다 다른 지네릭 타입을 대입할 수 있게 한 것
와일드 카드: 하나의 참조변수로 서로 다른 타입이 대입된 여러 지네릭을 다루기 위한 것.
컴파일러는 지네릭 타입을 제거하고, 필요한 곳에 형변환을 넣는다.
출처: 자바의 정석
'JAVA > JAVA' 카테고리의 다른 글
스트림(Stream) - 중간 연산(2/3) (0) | 2023.01.04 |
---|---|
스트림(Stream) - 개념(1/3) (0) | 2023.01.03 |
Generics 지네릭스 - 개념 및 활용 (1/2) (0) | 2023.01.02 |
int, long 타입과 같은 Primitive type null 값이 안 들어가는 이유 (0) | 2021.07.04 |
JAVA - 리스트와 ArrayList 차이점, 메소드 설명. (2) | 2021.06.28 |