1.5 와일드 카드 ~ 1.8 지네릭 타입의 제거

Jaeho Kim·2022년 3월 26일
0

자바의정석

목록 보기
3/7

✏️ 와일드 카드

  • 제네릭 타입은 컴파일러가 컴파일할 때만 사용하고 제거해버림.
    그래서 오버로딩 개념이 아니라, 메서드 중복 정의가 되기 때문에
    '<?>' 개념을 사용한다.

  • <> 사이에 T,E,K,V 이것은 다 같은 동작을 하고 네이밍은 관습
    ex) E는 List, ArrayList 와 같은 컬렉션을 사용할때 사용한다.

    T 유형을 의미합니다.
    E 엘리먼트 ( List: 엘리먼트 목록)
    K 열쇠입니다 (에서 Map<K,V>)
    V 값 (반환 값 또는 매핑 된 값)입니다.

  • 하지만 '?'만으로는 Obj와 다를게 없으므로 extents와 super 사용

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

  • 지넥릭 클래스와 달리 와일드 카드에서는 '&' 사용 불가능.

  • 예제 12-3

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";}}

// Juice 클래스
class Juice {
	String name;

  	Juice(String name)			{ this.name = name = "Juice";	}
  	public String toString() 	{ return name; 					}
}

// syso 출력할 주스를 만들어줌 (담겨있는 객체 뽑아냄, 믹서기 안의 과일들)
class Juicer {
	static Juice makeJuice(FruitBox<? extends Fruit> box) {
		String tmp = "";

		for(Fruit f : box.getList())	
		// 요기가 핵심 부분. box가 Fruit이 아닐 수 있는데, 아니라면 오류가 발생할것임.
		// 하지만 하단에 FruitBox의 와일드카드 지네릭클래스 FruitBox를 Fruit으로 
 		// 제한했기 때문에 컴파일러는 위 문장으로부터 모든 FruitBox의 요소들이 Fruit 
		// 자손이라는 것을 알고 있으므로 문제 삼지 않음.
			tmp += f + " ";
		return new Juice(tmp);
	}
}

// 메인코드 믹서기에 주스를 담고 주스만들기
class FruitBoxEx3 {
	public static void main(String[] args){
		FruitBox<Fruit> fruitBox = new FruitBox<Fruit>();
		FruitBox<Apple> appleBox = new FruitBox<Apple>();
		
		fruitBox.add(new Apple());
		fruitBox.add(new Grape());
		appleBox.add(new Apple());
		appleBox.add(new Apple());
		
		System.out.println(Juicer.makeJuice(fruitBox));
		System.out.println(Juicer.makeJuice(appleBox));
	}	// main
}

// FruitBox가 Fruit과 Fruit을 상속받고있는 그 조상들만 받아들일 수 있도록 약속
class FruitBox<T extends Fruit> extends Box<T> {}

// Box클래스, 믹서기 안에서의 기능
class Box<T> {
	ArrayList<T> list = new ArrayList<T>();
	void add(T item) 			{ list.add(item);			}
	T get(int i)				{ return list.get(i);		}
	ArrayList<T> getList() 		{ return list;				}
	int size()					{ return list.size();		}
	public String toString()	{ return list.toString();	}
}
  • 예제 12-4
import java.util.;

// Fruit Vo
class Fruit	{
	String name;
	int weight;

	Fruit(String name, int weight){
		this.name	= name;
		this.weight	= weight;
	}

	public String toString()	{ return name+"("+weight+")";}
}

// Apple 클래스
class Apple extends Fruit {
	Apple(String name, int weight){
		// 생성자를 통해서 Fruit 부모객체에 name, weight 주입
		// 복습
		// 2주전? 에 배운것처럼 이런 상황에서 super(); 를 호출 했을때 에러가 날것이다.
		// 왜냐하면 Fruit 객체는 기본 생성자 ()가 없기 때문.
		super(name, weight);
	}
}

class Grape extends Fruit {
	Grape(String name, int weight){
		super(name, weight);
	}
}

// Comparator 인터페이스를 상속, compare 오버라이딩
class AppleComp implements Comparator<Apple> {
	public int compare(Apple t1, Apple t2) {
		retunr t2.weight - t1.weight;
	}
}

class AppleComp implements Comparator<Grape> {
	public int compare(Grape t1, Grape t2) {
		retunr t2.weight - t1.weight;
	}
}

class AppleComp implements Comparator<Fruit> {
	public int compare(Fruit t1, Fruit t2) {
		retunr t1.weight - t2.weight;
	}
}

class FruitBoxEx4 {
	public static void main(String[] args) {
		FruitBox<Apple> appleBox = new FruitBox<Apple>();
		FruitBox<Grape> appleBox = new FruitBox<Grape>();

		appleBox.add(new Apple("GreenApple", 300));
		appleBox.add(new Apple("GreenApple", 100));
		appleBox.add(new Apple("GreenApple", 200));

		grapeBox.add(new Grape("GreenApple", 400));
		grapeBox.add(new Grape("GreenApple", 300));
		grapeBox.add(new Grape("GreenApple", 200));		
  
		Collections.sort(appleBox.getList(), new AppleComp());
		Collections.sort(grapeBox.getList(), new GrapeComp());
		System.out.println(appleBox);
        System.out.println(grapeBox + "\n");
        Collections.sort(appleBox.getList(), new FruitComp());
		Collections.sort(grapeBox.getList(), new FruitComp());
		System.out.println(appleBox);
		System.out.println(grapeBox);
	}	// main
}

class FruitBox<T extends Fruit> extends Box<T> {}
ckass Box<T> {
	ArrayList<T> list = new ArrayList<T>();

	void add(T item) {
		list.add(item);
	}

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

	ArrayList<T> getList() { return list; }

	int size() {
		return list.size();
	}

	public String toString() {
		return list.toString();
	}
}
  • 위 예제의 핵심은... 여러 클래스를 Comparator나 sort등등 처리를 할때, 항상 다른 타입으로
    받아들여야 하는지임. FruitComp() 를 통해서 자손이 생길때마다 반복해서 같은 코드를 작성하는것이 아니라 <? super T>를 붙여 줌으로써 하한제한. 즉 자신을 포함한 조상객체의 접근을 허용하는 코드를 작성해서 자손이 늘어나도 조상에 접근 할 수 있도록 함이 핵심이다.

✏️ 지네릭 메서드

  • 메서드 선언부에 지네릭 타입이 선언된 메서드를 지네릭 메서드라 한다.
	static <T> void sort(List<T> list, Comparator<? super T> c)
	// 선언부의 T와 매개변수의 T는 전혀 다른 별개의 것이다.

앞에 나왔던 makeJuice 메서드를 지네릭 메서드로 바꾸는 예시

	static Juice makeJuice(FruitBox<? extends Fruit> box){
	}

	static <T extends Fruit> Juice makeJuice(FruitBox<T> box){
	}
  • 이 메서드를 호출할 때에는 대입된 타입을 표시해야 하는데, 클래스 내부에서 호출 할때에는 생략 가능하지만 이게 컴파일러가 타입을 추정할 수 없을때는 생략 불가능(말이 너무 어려워서 한동안 고민했는데, 이게 선언이 현재 되어있는지를 보는거 같습니다. 모르는 객체라면 불가능)

✏️ 지네릭 타입의 형변환

  • 지네릭 메소드나 객체 자체 형변환은 안되지만 타입 내부에서의 형변환은 가능하다
    그래서 매개변수의 다형성이 적용될 수 있다.
  • ? , T 로 표현 함으로서 가능성을 열어두고 개발할 수 있다.

✏️ 지네릭 타입의 제거

  • 컴파일러는 지네릭 타입을 이용해서 소스파일을 체크하고, 필요한 곳에 형변환을 넣어준다. 그리고 형변환을 넣어주고 지네릭을 제거한다. 즉, 컴파일된 파일에는 지넬기 타입에 대한 정보가 없다.
profile
Hello, World!

0개의 댓글