Notice
Recent Posts
Recent Comments
Link
«   2026/03   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
Tags more
Archives
Today
Total
관리 메뉴

프로그래밍 공부

[자바] #09. 와일드카드 (Wildcard) - 2. 상한 제한과 하한 제한 본문

자바

[자바] #09. 와일드카드 (Wildcard) - 2. 상한 제한과 하한 제한

하 냥 2025. 2. 15. 19:29

?는 제네릭 타입을 정확히 모를 때 사용하는 것.

예를 들어, List<?>는 List<String>, List<Integer>, List<Double> 등을 모두 받을 수 있다.

하지만 타입 안정성을 위해 "제한"을 걸어야 할 때가 많다!
→ 이때 상한 제한(? extends T)하한 제한(? super T)을 사용.

 

📢 와일드카드의 상한 제한 (? extends T)

  • T 또는 그 하위 타입(subtype)만 허용
  • 읽기 가능 (get) ✅, 쓰기 불가능 (add) ❌

👀 <? extends Number> 를 사용한 예제

public static void printNumbers(List<? extends Number> list) {
    for (Number num : list) {
        System.out.println(num);  // ✅ 읽기 가능
    }

    list.add(10);  // ❌ ERROR! (쓰기 불가능)
}

🔹 왜 <? extends Number> 를 쓰는 걸까?

  • 여러 하위 타입을 받을 수 있도록 하기 위해 -> List<Integer>, List<Double>, List<Float> 등을 모두 받을 수 있다.
  • 읽기 전용으로 사용할 때 -> list.add(10)을 하면 안 됨!
    • List<Double>일 수도 있는데, Integer를 추가하면 타입이 깨질 수 있기 때문이다.
    • 즉, 쓰기(add())를 막아서 타입 안정성을 유지
  • 대신 읽기(get())는 가능 → 공통 슈퍼클래스(Number) 로 안전하게 데이터를 가져올 수 있음.

 

📢 와일드카드의 하한 제한 (? super T)

  • T 또는 그 상위 타입(supertype)만 허용
  • 쓰기 가능 (add) ✅, 읽기 제한적 (get 시 Object 반환) ❌

👀 <? super Number> 를 사용한 예제

public static void addNumbers(List<? super Integer> list) {
    list.add(10);  // ✅ Integer 추가 가능
    list.add(20);
    
    Object obj = list.get(0);  // ❌ get() 하면 Object 타입 반환됨
}

🔹 왜 <? super Integer> 를 쓰는 걸까?

  • 데이터를 안전하게 추가해야 할 때 -> List<Integer>, List<Number> 에 안전하게 Integer 값을 넣을 수 있음
  • 쓰기 전용으로 설계할 때 -> 쓰기(add())는 가능 → Integer를 추가할 수 있음
  • 하지만 읽기(get())는 제한적 → 반환 타입이 항상 Object.

 

 

📌 ? extends T를 써야 하는 경우

목적: 여러 타입을 받으면서도 안전하게 읽어야 할 때

  • List<Integer>, List<Double> 등 여러 타입을 받되 읽기 전용으로 사용할 때
  • get()을 하면 안전하게 T 타입을 유지할 수 있다.

📌 ? super T를 써야 하는 경우

 

목적: 특정 타입 이상의 리스트를 받으면서 데이터를 추가해야 할 때

  • List<Integer>, List<Number>, List<Object>에서 공통으로 특정 타입(ex. Integer)을 추가할 때
  • 하지만 읽을 때 원래 타입을 유지할 수 없고, Object로 받아야 함.

🔍추가 개념 )

<? super Integer>는 Integer뿐만 아니라 Number나 Object도 포함할 수 있음.

즉, 리스트 내부의 요소들이 반드시 Integer라고 확신할 수 없음!

List<? super Integer> list = new ArrayList<Number>(); // Number 리스트!
list.add(10); // ✅ Integer 추가 가능
Number num = list.get(0); // ❓ Integer일까? Number일까?
  • list.get(0)을 하면 반환 타입이 무엇인지 알 수 없음!
  • list가 List<Integer>일 수도, List<Number>일 수도 있고, List<Object>일 수도 있음.
  • 컴파일러는 이를 안전하게 처리하기 위해 반환 타입을 Object로 고정함.
Integer x = list.get(0); // ❌ ERROR! (Object로 반환됨)
Object obj = list.get(0); // ✅ OK!
  • get()을 할 때, list가 List<Number>인지 List<Object>인지 알 수 없어서 get() 시 반환 타입을 확정할 수 없다.
  • 그래서 컴파일러는 가장 일반적인 타입(Object)을 반환하도록 강제한다!

 

다음은 내가 상한 제한과 하한 제한을 이해할 때 썼던 예제이다.

 

🔍와일드카드 상한 제한에서 쓰기, 하한 제한에서 읽기는 안 되는 이유

class Box<T> {
	private T ob;
	public void set(T o) { ob = o; }
	public T get() { return ob; }
}

class Toy {
	@Override
	public String toString() {
		return "I am a Toy";
	}
}

public class BoxHandler {
	public static void outBox(Box<? extends Toy> box) {
		Toy t = box.get(); ✅
        	// ?는 Toy를 상속하는 클래스니까 당연히 box는 부모인 Toy 객체에 담을 수 있다!
		
        	// box.set(new Toy()); ❌
        	/* 위 문장은 불가능하다.
        	이 메소드에 Toy를 상속하는 Box<Car>이나 Box<Robot>이 인자로 전달된다고 생각해보라.
        	자식 객체에 부모 객체를 담을 수 있겠는가? */
		System.out.println(t);
	}
	
	public static void inBox(Box<Toy> box, Toy n) {
		box.set(n);
	}
	
	public static void main(String[] args) {
		Box<Toy> box = new Box<>();
		BoxHandler.inBox(box, new Toy());
		BoxHandler.outBox(box);
	}
}

자식 객체(Box<Car> 또는 Box<Robot>)에 부모 객체(Box<Toy>)를 담을 수는 없다.

 

 

또 다른 예제를 살펴보자.

public class GenericWildcards {
	public static <T> void copy(List<? super T> dest, List<? extends T> src) {
	    for (T item : src) {  // ✅ src에서 요소를 하나씩 가져와서
	        dest.add(item);   // ✅ dest에 추가
	    }
	}
}

 

Person 클래스를 상속받는 Student 클래스가 있다고 생각해보자. 또 그 Student 클래스를 상속받는 Science 클래스가 있다고 생각해보자.

여기서 T는 Student라고 가정하고 진행하겠다.

먼저 List<? extends T> 부터 살펴보자.

T가 Student이고, ?가 Science일 경우에 Student inst = src.get(0); 이게 되는데,

?가 Person이면 Student inst = src.get(0) 이게 되겠는가? 자식 객체에 부모 객체를 넣는 게? 안 된다.

 

그 다음 List<? super T> 를 살펴보자.

마찬가지로 T가 Student이고, ?가 Person일 경우에는 Student inst 객체가 있을 때 dest.add(inst) 이게 된다.

부모 객체에 자식 객체를 넣는 거니까.

그런데 ?가 Science일 경우에는 Student inst 객체가 있을 때 dest.add(inst)가 되겠는가?

자식 객체에 부모 객체를 넣는 건데, 안 된다.

 

또 이때 get이 안되는 이유는, T가 Science이고 ?가 Student라고 생각해 보자.

Science sc = dest.get()이 되겠는가? 안 된다.