[Java] Generics - 2

eeminsu·2021년 11월 24일
0
post-thumbnail

자바의 정석을 통해 공부한 내용을 요약하였습니다

1. 제한된 지네릭 클래스

  • 지네릭 타입에 'extends'를 사용하면, 특정 타입의 자손들만 대입할 수 있게 제한할 수 있다.
class FruitBox<T extends Fruit>{
	ArrayList<T> list = new ArrayList<T>(); // Fruit의 타입만 자손으로 지정 가능
	....
}
  • 다형성에서 조상타입의 참조변수로 자손타입의 객체를 가리킬 수 있는 것처럼, 매개변수화된 타입의 자손 타입도 가능하다.
    클래스가 아닌 인터페이스 또한 'extends'를 사용한다.(implements가 아니다.)
FruitBox<Fruit> fruitBox = new FruitBox<Fruit>;
fruitBox.add(new Apple());
fruitBox.add(new Grape());

interface Eatable {}
class FruitBox<T extends Eatable> {...}
class FruitBox<T extends Fruit & Eatable> {...} // 클래스의 자손이면서 인터페이스도 구현해야한다면 &를 사용하여 연결한다.



2. 와일드 카드

class Juicer {
	static Juice makeJuice(FruitBox<Fruit> box) { // Fruit으로 지정
		String tmp = "";
		
		for(Fruit f : box.getList()) 
			tmp += f + " ";
		return new Juice(tmp);
	}
}
  • 위 클래스는 지네릭 클래스가 아닌데다 static 메서드이므로 타입 매개변수 T를 매개변수로 사용할 수 없다. 아예 Ginerics를 적용하지 않던가 위 처럼 타입을 지정해주어야 한다.
static Juice makeJuice(FruitBox<Fruit> box) {
		String tmp = "";
		
		for(Fruit f : box.getList()) 
			tmp += f + " ";
		return new Juice(tmp);
}

static Juice makeJuice(FruitBox<Apple> box) {
		String tmp = "";
		
		for(Fruit f : box.getList()) 
			tmp += f + " ";
		return new Juice(tmp);
} 
  • 그래서 위와 같이 오버로딩을 해주어야 할 것 같지만, 지네릭 타입이 다른 것만으로는 오버로딩이 성립하지 않는다.
    이럴 때 사용하기 위해 고안된 것이 와일드 카드이다.

< ? extends T> - 와일드 카드의 상한 제한, T와 그 자손들만 가능
< ? super T> - 와일드 카드의 하한 제한, T와 그 조상들만 가능
< ?> - 제한 없음, 모든 타입 가능, < ? extends Object>와 동일

  • 와일드 카드는 기호'?'로 표현하는데 이것만으로는 Object타입과 다를 게 없으므로 위처럼 extends와 super를 사용하여 제한할 수 있다.
static Juice makeJuice(FruitBox<? extends Fruit> box) {
		String tmp = "";
		
		for(Fruit f : box.getList()) 
			tmp += f + " ";
		return new Juice(tmp);
}
  • 와일드 카드를 사용해서 makeJuice의 매개변수 타입을 위 처럼 바꾸게 되면 매개변수로 Fruit뿐만 아니라 그의 자손들 까지 사용할 수 있다.
  • 와일드 카드는 하나의 참조변수로 서로 다른 타입이 대입된 여러 제네릭 객체를 다룰 수 있게 한 것이다.


3. 지네릭 메서드

static <T> void sort(List<T> list, Comparator<? super T> c)
  • 메서드의 선언부에 지네릭 타입이 선언된 메서드를 지네릭 메서드라 한다.
    지네릭 타입의 선언 위치는 반환 타입 바로 앞이다.
  • 지네릭 클래스에 정의된 타입 매개변수와 지네릭 메서드에 정의된 타입 매개변수는 전혀 별개의 것이다.(인스턴스 변수와 로컬 변수의 차이로 보면 된다.)
static <T extends Fruit> Juice makeJuice(FruitBox<T> box) {
		String tmp = "";
		
		for(Fruit f : box.getList()) 
			tmp += f + " ";
		return new Juice(tmp);
}
  • 앞서 나왔던 makeJuice()를 지네릭 메서드로 바꾸면 위와 같다.
public static void printAll(ArrayList<? extends Product> list, ArrayList<? extends Product> list2) {
	for(Unit u : list) {
		System.out.println(u);
	}
}

public static <T extends Product> void printAll(ArrayList<T> list, ArrayList<T> list2) {
	for(Unit u : list) {
		System.out.println(u);
	}
}
  • 매개변수의 타입이 복잡할 때도 유용하게 사용된다.
  • 지네릭 메서드는 제네릭 클래스처럼 호출할 때마다 다른 타입을 대입할 수 있다.


4. 지네릭 타입의 형변환

Box box = null;
Box<Object> objBox = null;

box = (Box)objBox; // OK, 지네릭 타입 -> 원시타입 , 경고 발생
objBox = (Box<Object>)box; // OK, 원시타입 -> 지네릭 타입, 경고 발생 
  • 지네릭 타입과 넌 지네릭 타입간의 형변환은 가능하나 경고가 발생한다.
    이와 같은 형변환은 바람직 하지 않다.
  • 대입된 타입이 다른 지네릭 타입 간은 형변환이 불가능하다.
Box<? extends Fruit> wBox = new Box<Apple>();
  • 와일드 카드가 사용된 지네릭 타입으로는 형변환이 가능하다.
  • 반대로의 형변환도 가능하나 경고가 발생한다.
    < ? extends Fruit>에 대입될 수 있는 타입은 여러 개인데, Apple을 제외한 다른 타입은 Apple로 형변환 될 수 없기 때문이다.


5. 지네릭 타입의 제거

  • 지네릭스는 JDSK1.5부터 도입이 되었다.
  • 컴파일러는 하위 호환성때문에 컴파일 단계에서 지네릭 타입을 제거하고, 필요한 곳에 형변환을 넣는다.

5-1. 지네릭 타입의 경계(bound)를 제거

class Box<T extends Fruit>{
	void add(T t) {
		....
	}
}

👇

class Box{
	void add(Fruit t) {
		....
	}
}

5-2. 지네릭 타입 제거 후에 타입이 불일치하면, 형변환을 추가

T get(int i) {
	return list.get(i);
}

👇

Fruit get(int i) {
	return (Fruit)list.get(i);
}

5-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);
}
profile
안되면 될 때까지

0개의 댓글