본문 바로가기

JAVA/JAVA

Generics 지네릭스 - 와일드 카드 및 메소드 (2/2)

728x90

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. 정리

지네릭 메서드: 메서드를 호출할 때마다 다른 지네릭 타입을 대입할 수 있게 한 것

와일드 카드: 하나의 참조변수로 서로 다른 타입이 대입된 여러 지네릭을 다루기 위한 것

 

컴파일러는 지네릭 타입을 제거하고, 필요한 곳에 형변환을 넣는다.

 

출처: 자바의 정석

728x90