2-3. 와일드카드

shin·2024년 7월 21일
  • 제네릭 타입을 조금 더 편리하게 사용할 수 있는 와일드 카드(wildcard)
  • 와일드카드라는 뜻은 컴퓨터 프로그래밍에서 *, ?와 같이 하나 이상의 문자들을 상징하는 특수 문자를 뜻함
  • 쉽게 이야기해서 여러 타입이 들어올 수 있다는 뜻

1. 와일드카드1

package generic.ex5;

public class Box<T> {

 	private T value;
    
	public void set(T value) {
 		this.value = value;
    }
    
 	public T get() {
 		return value;
    }
    
}
  • 단순히 데이터를 넣고 반환할 수 있는 제네릭 타입을 하나 만듦

package generic.ex5;

import generic.animal.Animal;

public class WildcardEx {

	static <T> void printGenericV1(Box<T> box) {
 		System.out.println("T = " + box.get());
    }
    
 	static void printWildcardV1(Box<?> box) {
 		System.out.println("? = " + box.get());
    }
    
 	static <T extends Animal> void printGenericV2(Box<T> box) {
 		T t = box.get();
 		System.out.println("이름 = " + t.getName());
    }
    
 	static void printWildcardV2(Box<? extends Animal> box) {
 		Animal animal = box.get();
 		System.out.println("이름 = " + animal.getName());
    }
    
 	static <T extends Animal> T printAndReturnGeneric(Box<T> box) {
 		T t = box.get();
 		System.out.println("이름 = " + t.getName());
 		return t;
    }
    
 	static Animal printAndReturnWildcard(Box<? extends Animal> box) {
 		Animal animal = box.get();
 		System.out.println("이름 = " + animal.getName());
 		return animal;
    }
    
}
  • 제네릭 메서드와 와일드 카드를 비교할 수 있게 같은 기능을 각각 하나씩 배치해둠
  • 와일드카드는 ?를 사용해서 정의함
package generic.ex5;

import generic.animal.Animal;
import generic.animal.Cat;
import generic.animal.Dog;

public class WildcardMain1 {

 	public static void main(String[] args) {
    
 		Box<Object> objBox = new Box<>();
 		Box<Dog> dogBox = new Box<>();
 		Box<Cat> catBox = new Box<>();
        dogBox.set(new Dog("멍멍이", 100));
        
 		WildcardEx.printGenericV1(dogBox);
 		WildcardEx.printWildcardV1(dogBox);
        
 		WildcardEx.printGenericV2(dogBox);
		WildcardEx.printWildcardV2(dogBox);
        
 		Dog dog = WildcardEx.printAndReturnGeneric(dogBox);
 		Animal animal = WildcardEx.printAndReturnWildcard(dogBox);
        
    }
    
}

실행 결과

T = Animal{name='멍멍이', size=100}
? = Animal{name='멍멍이', size=100}
이름 = 멍멍이
이름 = 멍멍이
이름 = 멍멍이
이름 = 멍멍이

참고사항 :

  • 와일드카드는 제네릭 타입이나, 제네릭 메서드를 선언하는 것이 아님
  • 와일드 카드는 이미 만들어진 제네릭 타입을 활용할 때 사용함

비제한 와일드카드


제네릭 메서드

static <T> void printGenericV1(Box<T> box) {
 	System.out.println("T = " + box.get());
}
  • Box<Dog> dogBox를 전달함
    • 타입 추론에 의해 타입 TDog가 됨

일반적인 메서드(제네릭 메서드가 아님)

static void printWildcardV1(Box<?> box) {
 	System.out.println("? = " + box.get());
}
  • Box<Dog> dogBox를 전달함
    • 와일드카드 ?는 모든 타입을 받을 수 있다.

  • 두 메서드는 비슷한 기능을 하는 코드임

    • 하나는 제네릭 메서드를 사용하고 하나는 일반적인 메서드에 와일드카드를 사용함
  • 와일드카드는 제네릭 타입이나 제네릭 메서드를 정의할 때 사용하는 것이 아님

    • Box<Dog>, Box<Cat>처럼 타입 인자가 정해진 제네릭 타입을 전달 받아서 활용할 때 사용함
  • 와일드 카드인 ?는 모든 타입을 다 받을 수 있다는 뜻

    • 다음과 같이 해석할 수 있음 ? == <? extends Object>
  • 이렇게 ?만 사용해서 제한없이 모든 타입을 받을 수 있는 와일드카드를 비제한 와일드카드라고 함

    • 여기에는 Box<Dog> dogBox, Box<Cat> catBox, Box<Object> object가 모두 입력될 수 있음

제네릭 메서드 실행 예시

//1. 전달
printGenericV1(dogBox)

//2. 제네릭 타입 결정 dogBox는 Box<Dog> 타입, 타입 추론 -> T의 타입은 Dog
static <T> void printGenericV1(Box<T> box) {
	System.out.println("T = " + box.get());
}

//3. 타입 인자 결정
static <Dog> void printGenericV1(Box<Dog> box) {
 	System.out.println("T = " + box.get());
}

//4. 최종 실행 메서드
static void printGenericV1(Box<Dog> box) {
 	System.out.println("T = " + box.get());
}

와일드 카드 실행 예시

//1. 전달
printWildcardV1(dogBox)

//이것은 제네릭 메서드가 아니다. 일반적인 메서드이다.
//2. 최종 실행 메서드, 와일드카드 ?는 모든 타입을 받을 수 있다.
static void printWildcardV1(Box<?> box) {
 System.out.println("? = " + box.get());
}

제네릭 메서드 vs 와일드카드


  • printGenericV1()에서 제네릭 메서드를 보면, 제네릭 메서드에는 타입 매개변수가 존재함

    • 특정 시점에 타입 매개변수에 타입 인자를 전달해서 타입을 결정해야 함
    • 이러한 과정은 매우 복잡함
  • 반면에 printWildcardV1() 메서드를 보면, 와일드카드는 일반적인 메서드에 사용할 수 있고, 단순히 매개변수로 제네릭 타입을 받을 수 있는 것 뿐임

    • 제네릭 메서드처럼 타입을 결정하거나 복잡하게 작동하지 않음
    • 단순히 일반 메서드에 제네릭 타입을 받을 수 있는 매개변수가 하나 있는 것 뿐임
  • 따라서 제네릭 타입이나 제네릭 메서드를 정의하는게 꼭 필요한 상황이 아니라면, 더 단순한 와일드 카드 사용을 권장함


2. 와일드카드2

상한 와일드카드


static <T extends Animal> void printGenericV2(Box<T> box) {
	T t = box.get();
 	System.out.println("이름 = " + t.getName());
}
  
static void printWildcardV2(Box<? extends Animal> box) {
 	Animal animal = box.get();
 	System.out.println("이름 = " + animal.getName());
}
  • 제네릭 메서드와 마찬가지로 와일드카드에도 상한 제한을 둘 수 있음

  • 여기서는 ? extends Animal을 지정함

    • Animal과 그 하위 타입만 입력 받음
    • 만약 다른 타입을 입력하면 컴파일 오류가 발생함
  • box.get()을 통해서 꺼낼 수 있는 타입의 최대 부모는 Animal이 됨

    • 따라서 Animal 타입으로 조회할 수 있음
  • 결과적으로 Animal 타입의 기능을 호출할 수 있음


타입 매개변수가 꼭 필요한 경우


  • 와일드카드는 제네릭을 정의할 때 사용하는 것이 아님
    • Box<Dog>, Box<Cat>처럼 타입 인자가 전달된 제네릭 타입을 활용할 때 사용함
  • 따라서 다음과 같은 경우에는 제네릭 타입이나 제네릭 메서드를 사용해야 문제를 해결할 수 있음
static <T extends Animal> T printAndReturnGeneric(Box<T> box) {
 	T t = box.get();
 	System.out.println("이름 = " + t.getName());
 	return t;
}

static Animal printAndReturnWildcard(Box<? extends Animal> box) {
 	Animal animal = box.get();
 	System.out.println("이름 = " + animal.getName());
 	return animal;
}
  • printAndReturnGeneric()은 다음과 같이 전달한 타입을 명확하게 반환할 수 있음
Dog dog = WildcardEx.printAndReturnGeneric(dogBox)
  • 반면에 printAndReturnWildcard()의 경우 전달한 타입을 명확하게 반환할 수 없음
    • 여기서는 Animal 타입으로 반환함
Animal animal = WildcardEx.printAndReturnWildcard(dogBox)

  • 메서드의 타입들을 특정 시점에서 변경하려면 제네릭 타입이나, 제네릭 메서드를 사용해야 함

  • 와일드카드는 이미 만들어진 제네릭 타입을 전달 받아서 활용할 때 사용함

    • 따라서 메서드의 타입들을 타입 인자를 통해 변경할 수 없음
  • 쉽게 이야기해서 일반적인 메서드에 사용한다고 생각하면 됨

정리하면, 제네릭 타입과 제네릭 메서드가 꼭 필요한 상황이면 <T>를 사용하고, 그렇지 않은 상황이면 와일드카드를 사용하는 것을 권장함



하한 와일드 카드


  • 와일드 카드는 상한 뿐만 아니라 하한도 지정할 수 있음
package generic.ex5;

import generic.animal.Animal;
import generic.animal.Cat;
import generic.animal.Dog;

public class WildcardMain2 {

 	public static void main(String[] args) {
 		Box<Object> objBox = new Box<>();
 		Box<Animal> animalBox = new Box<>();
 		Box<Dog> dogBox = new Box<>();
 		Box<Cat> catBox = new Box<>();

 		// Animal 포함 상위 타입 전달 가능
		writeBox(objBox);
 		writeBox(animalBox);
 		//writeBox(dogBox); // 하한이 Animal
 		//writeBox(catBox); // 하한이 Animal

 		Animal animal = animalBox.get();
 		System.out.println("animal = " + animal);
    }

 	static void writeBox(Box<? super Animal> box) {
        box.set(new Dog("멍멍이", 100));
    }

}

실행결과

animal = Animal{name='멍멍이', size=100}

Box<? super Animal> box
  • 이 코드는 ?Animal 타입을 포함한 Animal타입의 상위 타입만 입력받을 수 있다는 뜻
  • 정리하면 다음과 같음
    • Box<Object> objBox : 허용
    • Box<Animal> animalBox : 허용
    • Box<Dog> dogBox : 불가
    • Box<Cat> catBox : 불가
  • 하한을 Animal로 제한했기 때문에 Animal 타입의 하위 타입인 Box<Dog>는 전달할 수 없음


강의 출처 : 김영한의 실전 자바 - 중급 2편

profile
Backend development

0개의 댓글