와일드카드 (?, extends, super)
Coddy Java 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨 — 87개 중 57번째.
제한된 타입 파라미터(Bounded type parameters)는 제네릭을 특정 타입으로 제한하지만, 이미 존재하는 제네릭 타입을 사용하면서 정확한 타입 파라미터를 모르거나 신경 쓰고 싶지 않을 때는 어떻게 해야 할까요? 이럴 때 바로 와일드카드(wildcards)가 사용됩니다.
비한정 와일드카드(unbounded wildcard) ?는 알 수 없는 타입을 나타냅니다. 어떤 매개변수화된 타입(parameterized type)이라도 수용하고 싶을 때 유용합니다:
public static void printList(List<?> list) {
for (Object item : list) {
System.out.println(item);
}
}
printList(new ArrayList<String>()); // 동작함
printList(new ArrayList<Integer>()); // 동작함상한 제한 와일드카드(upper bounded wildcard) ? extends Type은 지정된 타입 또는 그 하위 클래스 중 하나를 허용합니다. 제네릭 구조에서 데이터를 읽어야 할 때 사용하세요:
public static double sumNumbers(List<? extends Number> numbers) {
double sum = 0;
for (Number n : numbers) {
sum += n.doubleValue();
}
return sum;
}
sumNumbers(Arrays.asList(1, 2, 3)); // Integer 리스트
sumNumbers(Arrays.asList(1.5, 2.5)); // Double 리스트하한 제한 와일드카드(lower bounded wildcard) ? super Type은 지정된 타입 또는 그 상위 클래스 중 하나를 허용합니다. 제네릭 구조에 데이터를 쓰기(write) 작업이 필요할 때 사용합니다:
public static void addIntegers(List<? super Integer> list) {
list.add(1);
list.add(2);
}
List<Number> numbers = new ArrayList<>();
addIntegers(numbers); // 정상 작동 - Number는 Integer의 상위 타입입니다도움이 되는 가이드라인: 읽기만 할 때(생산자, producer)는 extends를 사용하고, 쓰기만 할 때(소비자, consumer)는 super를 사용하세요. 이것은 PECS 원칙—Producer Extends, Consumer Super로 알려져 있습니다.
챌린지
쉬움세 가지 유형의 와일드카드를 모두 보여주는 동물원 급식 시스템을 구축해 봅시다! 동물과 음식의 계층 구조를 만든 다음, 다양한 급식 시나리오를 처리하기 위해 비제한(unbounded), 상한 제한(upper bounded), 하한 제한(lower bounded) 와일드카드를 사용하는 유틸리티 메서드를 작성하게 됩니다.
코드는 네 개의 파일로 구성됩니다:
Food.java: 음식에 대한 간단한 클래스 계층 구조를 만듭니다. private 필드name(String), 이를 초기화하는 생성자, 그리고getName()메서드를 가진 기본 클래스Food로 시작하세요. 그런 다음 같은 파일에 두 개의 서브클래스를 만듭니다:Meat는 Food를 상속받고(생성자에서super(name)호출),Vegetable도 Food를 상속받습니다(동일한 패턴).Animal.java: private 필드species(String), 생성자,getSpecies()메서드를 가진 기본 클래스Animal을 만듭니다. 두 개의 서브클래스Carnivore와Herbivore를 만들고, 각각 Animal을 상속받아 부모 생성자를 호출하도록 합니다.ZooFeeder.java: 와일드카드가 빛을 발하는 곳입니다! 세 개의 정적 메서드를 가진 유틸리티 클래스를 만듭니다:printInventory(List<?> items)- 비제한 와일드카드를 사용하여 모든 리스트를 출력합니다. 루프를 돌며toString()을 사용하여 각 항목을 한 줄에 하나씩 출력하세요.calculateTotalFood(List<? extends Food> foods)- 상한 제한 와일드카드를 사용하여 모든 Food 타입의 리스트로부터 읽어옵니다. 항목의 개수를int로 반환하세요. 이는 생산자(producer)로부터 읽는 것을 보여줍니다.addMeatToStock(List<? super Meat> stock, String meatName)- 하한 제한 와일드카드를 사용하여 Meat를 담을 수 있는 리스트에 씁니다. 주어진 이름으로 새로운Meat객체를 생성하여 stock에 추가하세요. 이는 소비자(consumer)에게 쓰는 것을 보여줍니다.Main.java: 동물원 급식 시스템을 하나로 합칩니다! 고기 이름(String)과 채소 이름(String) 두 개의 입력을 받게 됩니다.먼저,
ArrayList<Food>를 생성하고 고기 이름으로Meat객체를, 채소 이름으로Vegetable객체를 추가합니다."Food inventory:"를 출력한 다음 이 리스트로printInventory를 호출하세요.다음으로, 빈 줄을 출력한 후, 음식 리스트에 대해
calculateTotalFood를 사용하여"Total food items: [count]"를 출력합니다.마지막으로,
meatStock이라는 이름의 새로운ArrayList<Food>를 생성합니다. 빈 줄을 출력한 다음,"Adding to meat stock..."을 출력합니다. 이 리스트와 문자열"Beef"를 사용하여addMeatToStock을 호출하세요. 그런 다음"Meat stock after adding:"을 출력하고 meat stock에 대해printInventory를 호출합니다.
고기 이름과 채소 이름 순서대로 두 개의 입력을 받게 됩니다.
출력이 올바르게 작동하도록 Food 클래스에서 toString()을 오버라이드하여 음식의 이름을 반환하도록 하세요. 각 와일드카드 유형이 어떻게 다른 목적으로 사용되는지 주목하세요: 단순히 Object로 읽기만 하면 될 때 최대의 유연성을 제공하는 ?, 특정 타입을 읽을 때의 ? extends, 그리고 컬렉션에 쓸 때의 ? super입니다!
치트 시트
Java는 정확한 타입 파라미터를 알 수 없거나 관련이 없을 때 제네릭 타입을 다루기 위한 세 가지 유형의 와일드카드(wildcards)를 제공합니다.
비한정 와일드카드 (?)
알 수 없는 타입을 나타내며 모든 매개변수화된 타입을 수용합니다:
public static void printList(List<?> list) {
for (Object item : list) {
System.out.println(item);
}
}
printList(new ArrayList<String>()); // Works
printList(new ArrayList<Integer>()); // Works상한 제한 와일드카드 (? extends Type)
지정된 타입 또는 그 하위 클래스 중 하나를 수용합니다. 제네릭 구조체로부터 데이터를 읽어야(read) 할 때 사용합니다:
public static double sumNumbers(List<? extends Number> numbers) {
double sum = 0;
for (Number n : numbers) {
sum += n.doubleValue();
}
return sum;
}
sumNumbers(Arrays.asList(1, 2, 3)); // List of Integer
sumNumbers(Arrays.asList(1.5, 2.5)); // List of Double하한 제한 와일드카드 (? super Type)
지정된 타입 또는 그 상위 클래스 중 하나를 수용합니다. 제네릭 구조체에 데이터를 써야(write) 할 때 사용합니다:
public static void addIntegers(List<? super Integer> list) {
list.add(1);
list.add(2);
}
List<Number> numbers = new ArrayList<>();
addIntegers(numbers); // Works - Number is a supertype of IntegerPECS 원칙
Producer Extends, Consumer Super: 데이터를 읽기만 할 때(생산자)는 extends를 사용하고, 쓰기만 할 때(소비자)는 super를 사용하세요.
직접 해보기
import java.util.Scanner;
import java.util.ArrayList;
import java.util.List;
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
String meatName = scanner.nextLine();
String vegetableName = scanner.nextLine();
// TODO: ArrayList<Food>를 생성하고 Meat와 Vegetable 객체를 추가하세요
// TODO: "Food inventory:"를 출력한 후 printInventory를 호출하세요
// TODO: 빈 줄을 출력한 다음, calculateTotalFood를 사용하여 "Total food items: [count]"를 출력하세요
// TODO: meatStock이라는 이름의 새로운 ArrayList<Food>를 생성하세요
// TODO: 빈 줄을 출력한 다음, "Adding to meat stock..."을 출력하세요
// TODO: meatStock과 "Beef"를 인자로 하여 addMeatToStock을 호출하세요
// TODO: "Meat stock after adding:"을 출력한 후 meatStock에 대해 printInventory를 호출하세요
}
}
이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.
객체 지향 프로그래밍의 모든 레슨
4상속
상속의 기초 (extends)super 키워드메서드 오버라이딩 (@Override)생성자 체이닝Object 클래스단일 및 다중 레벨 상속다중 클래스 상속이 불가능한 이유요약 - 직원 계층 구조