와일드 카드

2022. 5. 12. 11:56·자바/지네릭스

1. 와일드 카드란?

- Juicer라는 클래스가 존재하고 static 메서드로 makeJuice()가 있다고 가정해보자.

class Juicer{
    static Juice makeJuice(FruitBox<Fruit> box) {  // <Fruit>으로 지정
         String tmp = "";
         for(Fruit f : box.getList()) tmp += f + " ";
         return new Juice(tmp);
    }
}

 

( Juice 클래스는 지네릭 클래스가 아닌데다, 지네릭 클래스라고 해도 static 메서드에는 타입 매개변수 T를 매개변수에

사용할 수 없으므로 아예 지네릭스를 적용하지 않던가, 위와 같이 같이 <Fruit>로 지정해줘야 한다. )

 

 

 

( 그래서 아래와 같이 fruitBox는 컴파일이 잘 되지만, appleBox에 대해서는 컴파일 에러가 발생한다. )

FruitBox<Fruit> fruitBox = new FruitBox<Fruit>();
FruitBox<Apple> appleBox = new FruitBox<Apple>();

Juicer.makeJuice(fruitBox);
// 컴파일 정상

Juicer.makeJuice(appleBox);
// 컴파일 에러

 

 

 

( 그렇게 되면, 우리는 또 아래의 코드처럼 static 메서드를 생성해야 한다. )

class Juicer{
    static Juice makeJuice(FruitBox<Fruit> box) {  // <Fruit>으로 지정
         String tmp = "";
         for(Fruit f : box.getList()) tmp += f + " ";
         return new Juice(tmp);
    }
    
    static Juice makeJuice(FruitBox<Apple> box) {  // <Fruit>으로 지정
         String tmp = "";
         for(Fruit f : box.getList()) tmp += f + " ";
         return new Juice(tmp);
    }
}

 

( 그러나, 위와같이 오버로딩하면, 컴파일 에러가 발생한다. )

( 지네릭 타입이 다른 것만으로 오버로딩이 성립하지 않기 때문이다. )

( 그 이유는 지네릭 타입은 컴파일러가 컴파일할 때만 사용하고 제거하므로, 메서드 중복 정의로 간주되기 때문이다. )

 

( 이럴 때 사용하기 위해 고안된 것이 바로 '와일드 카드' 이다. )

( 와일드 카드는 기호 "?"로 표현하는데, 어떠한 타입도 될 수 있다. )

( "?" 만으로는 Object 타입과 다를 게 없으므로, 'extends'와 'super'로 상한과 하한을 제한할 수 있다. )

 

"?" 표현방식 설명
<? extends T>  와일드 카드의 상한 제한. T와 그 자손들만 가능.
<? super T>  와일드 카드의 하한 제한. T와 그 조상들만 가능.
<?>  제한 없음. 모든 타입이 가능. <? extends Object> 와 동일함.

 

 

 

( 그러므로, 와일드 카드를 사용하면 아래의 코드가 나오게 된다. )

class Juicer{
    static Juice makeJuice(FruitBox<? extends Fruit> box) {  // <Fruit>으로 지정
         String tmp = "";
         for(Fruit f : box.getList()) tmp += f + " ";
         return new Juice(tmp);
    }
}

 

 

 

 

( 또한 아래의 코드처럼 <? extends Object>를 수행할 수 있다. )

class Juicer{
    static Juice makeJuice(FruitBox<? extends Object> box) {  // <Fruit>으로 지정
         String tmp = "";
         for(Fruit f : box.getList()) tmp += f + " ";
         return new Juice(tmp);
    }
}

 

( 그러나 이렇게 되면 for문 안에 있는 Fruit가 에러일 수 있다. )

( 그 이유는 FruitBox의 모든 객체가 사용할 수 있기 때문이다. )

 

( 그러나, 실제로 컴파일을 해보면 에러가 발생하지 않는다. )

( 왜냐하면 FruitBox에서 이미 클래스에 <T extends Fruit>을 썻기 때문이다. )

( 그러므로, 이미 Fruit의 자손만 가능하므로 컴파일 에러가 발생하지 않는 것이다. )

 

 

 

 

1-1. 와일드 카드를 이해하기 위한 예제(1)

: 위에서 했던 코드들을 합친 예제이다.

 

import java.util.ArrayList;

public class Exercise003 {
	public static void main(String[] args){
		Fruit3Box3<Fruit3> Fruit3Box3 = new Fruit3Box3<Fruit3>();
		Fruit3Box3<Apple3> Apple3Box3 = new Fruit3Box3<Apple3>();

		Fruit3Box3.add(new Apple3());
		Fruit3Box3.add(new Grape3());
		Apple3Box3.add(new Apple3());
		Apple3Box3.add(new Apple3());

		System.out.println(Juicer.makeJuice(Fruit3Box3));
		System.out.println(Juicer.makeJuice(Apple3Box3));
	}
}

class Fruit3		          { public String toString() { return "Fruit3";}}
class Apple3 extends Fruit3 { public String toString() { return "Apple3";}}
class Grape3 extends Fruit3 { public String toString() { return "Grape3";}}

class Juice {
	String name;

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

class Juicer {
	static Juice makeJuice(Fruit3Box3<? extends Fruit3> Box3) {
		String tmp = "";
		
		for(Fruit3 f : Box3.getList()) 
			tmp += f + " ";
		return new Juice(tmp);
	}
}

class Fruit3Box3<T extends Fruit3> extends Box3<T> {}

class Box3<T> {
//class Fruit3Box3<T extends Fruit3> {
	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();}
}

 

 

 

 

1-2. 와일드 카드를 이해하기 위한 예제(2)

: 'super'로 와일드 카드의 하한을 사용하여 제한하는 예제이다.

 

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;

public class Exercise004 {
	public static void main(String[] args){
		Fruit4Box4<Apple4> Apple4Box4 = new Fruit4Box4<Apple4>();
		Fruit4Box4<Grape4> Grape4Box4 = new Fruit4Box4<Grape4>();

		Apple4Box4.add(new Apple4("GreenApple4", 300));
		Apple4Box4.add(new Apple4("GreenApple4", 100));
		Apple4Box4.add(new Apple4("GreenApple4", 200));

		Grape4Box4.add(new Grape4("GreenGrape4", 400));
		Grape4Box4.add(new Grape4("GreenGrape4", 300));
		Grape4Box4.add(new Grape4("GreenGrape4", 200));

		Collections.sort(Apple4Box4.getList(), new Apple4Comp());
		Collections.sort(Grape4Box4.getList(), new Grape4Comp());
		System.out.println(Apple4Box4);
		System.out.println(Grape4Box4);
		System.out.println();
		Collections.sort(Apple4Box4.getList(), new Fruit4Comp());
		Collections.sort(Grape4Box4.getList(), new Fruit4Comp());
		System.out.println(Apple4Box4);
		System.out.println(Grape4Box4);
	}
}
class Fruit4	{
	String name;
	int weight;
	
	Fruit4(String name, int weight) {
		this.name   = name;
		this.weight = weight;
	}

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

class Apple4 extends Fruit4 {
	Apple4(String name, int weight) {
		super(name, weight);
	}
}

class Grape4 extends Fruit4 {
	Grape4(String name, int weight) {
		super(name, weight);
	}
}

class Apple4Comp implements Comparator<Apple4> {
	public int compare(Apple4 t1, Apple4 t2) {
		return t2.weight - t1.weight;
	}
}

class Grape4Comp implements Comparator<Grape4> {
	public int compare(Grape4 t1, Grape4 t2) {
		return t2.weight - t1.weight;
	}
}

class Fruit4Comp implements Comparator<Fruit4> {
	public int compare(Fruit4 t1, Fruit4 t2) {
		return t1.weight - t2.weight;
	}
}

class Fruit4Box4<T extends Fruit4> extends Box4<T> {}

class Box4<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();
	}
}

 

( Collections.sort()를 사용하여 정렬하였는데, 이 메서드의 선언부는 아래와 같다. )

static <T> void sort(List<T> list, Comparator<? super T> c)

 

( 위와 같이 정의된 <T> 메서드를 '지네릭 메서드' 라고 부른다. )

( 위의 코드에서 'super'로 정의한 이유는 코드 중복에 대한 문제를 방지하기 위해서이다. )

 

( 만약 우리가 sort()에 super라는 것이 없었더라면, 위 예제에서처럼 Fruit에 대한 자손들인 Apple과 Grape를

정렬하기 위해서는 각각의 AppleComp과 GrapeComp를 만들어 정렬하였을 것이다. )

 

( 그러나, 이런식으로 Fruit의 자손이 생길때마다 Comp에 대한 코드를 계속 추가해야하는 문제가 발생하게 된다. )

 

( 그러나, Comparator<? super T>를 정의하였기 때문에, 손쉽게 FruitComp만 정의해주면 굳이 추가적으로

각각의 Comp를 만들어줄 필요가 없는 것이다. )

 

 

( 아래의 예시를 보고 이해하자. )

 

< Collections.sort()의 호출 >

sort(appleBox.getList(), new FruitComp());

 

< 실제 sort() 메서드 >

static <Apple> void sort(List<Apple> list, Comparator<? super Apple> c);
Comparator<? super T> 올 수 있는 Comparator<>
Comparator<? super Apple> Comparator<Apple>, Comparator<Fruit>,
Comparator<Object>
Comparaotr<? super Grape> Comparator<Grape>, Comparator<Fruit>,
Comparator<Object>

 

저작자표시 (새창열림)

'자바 > 지네릭스' 카테고리의 다른 글

지네릭 타입의 형변환과 제거  (0) 2022.05.16
지네릭 메서드  (0) 2022.05.13
지네릭스의 전반적 개념(2)  (0) 2022.05.11
지네릭스의 전반적 개념(1)  (0) 2022.05.11
'자바/지네릭스' 카테고리의 다른 글
  • 지네릭 타입의 형변환과 제거
  • 지네릭 메서드
  • 지네릭스의 전반적 개념(2)
  • 지네릭스의 전반적 개념(1)
백_곰
백_곰
  • 백_곰
    친절한 코딩
    백_곰
  • 전체
    오늘
    어제
    • 분류 전체보기
      • 알고리즘 (with JAVA)
        • 기본 알고리즘
        • 완전 탐색
        • 분할 정복 알고리즘
        • 동적 계획법
        • 탐욕법
        • 코딩 테스트 기출 문제
        • 코드트리 조별과제
      • 백준 (with JAVA)
        • 완전 탐색
        • 분할 정복
        • 그 외
      • 자바
        • 개발 환경 구축하기
        • 팁
        • 기본적인 개념
        • 컬렉션 프레임워크
        • 프로세스와 쓰레드
        • 지네릭스
        • 람다식
        • 스트림
        • 입출력 IO
        • 네트워킹
        • 열거형(enums)
        • java.lang 패키지
        • java.time 패키지
        • 유용한 클래스들
        • 형식화 클래스들
      • 안드로이드 with 자바
        • 응용 문제들
        • 자잘한 문제들
        • 오류 보고서
  • 블로그 메뉴

    • 링크

    • 공지사항

    • 인기 글

    • 태그

      중간연산
      안정 정렬
      제자리 정렬
      알고스팟
      ServerSocket
      소켓 프로그래밍
      유용한 클래스
      snail
      역직렬화
      file
      Collections Framework
      다형성
      코딩트리조별과제
      람다식
      outputstream
      map()
      자바 개념
      TCP 소켓 프로그래밍
      스트림
      코딩테스트
      java.lang패키지
      문자 기반 스트림
      코드트리
      불안정 정렬
      serializable
      Arrays
      java.time 패키지
      InputStream
      안드로이드 스튜디오
      선택 정렬
    • 최근 댓글

    • 최근 글

    • hELLO· Designed By정상우.v4.10.3
    백_곰
    와일드 카드
    상단으로

    티스토리툴바