본문 바로가기

이펙티브 자바

[이펙티브 자바] 아이템 31. 한정적 와일드카드를 사용해 API 유연성을 높여라

와일드카드 : 제네릭 타입에서 타입 매개변수의 값을 미리 지정하지 않고 사용할 수 있는 기능.

한정적 와일드카드는 이러한 와일드카드를 특정한 타입의 하위 타입으로 제한하는 방법 중 하나이다. 이를 통해 API의 유연성을 높일 수 있다.

 

import java.util.List;

class Animal {
    // 동물 클래스
}

class Dog extends Animal {
    // 개 클래스
}

class Cat extends Animal {
    // 고양이 클래스
}

public class AnimalProcessor {
    // 모든 동물의 리스트를 처리하는 메서드
    public static void processAnimals(List<? extends Animal> animals) {
        for (Animal animal : animals) {
            // 동물 처리 로직
            System.out.println(animal.getClass().getSimpleName() + " is being processed.");
        }
    }

    public static void main(String[] args) {
        List<Dog> dogs = List.of(new Dog(), new Dog());
        List<Cat> cats = List.of(new Cat(), new Cat());

        // processAnimals 메서드는 List<Dog>와 List<Cat>을 모두 처리할 수 있음
        processAnimals(dogs); // 개 리스트 처리
        processAnimals(cats); // 고양이 리스트 처리
    }
}

 

한정적 와일드카드는 메서드 시그니처에서 사용되고 있습니다. 메서드 processAnimals의 파라미터 타입인

List<? extends Animal>에서 한정적 와일드카드가 사용되었다. 이것은 Animal 클래스를 상속하는 모든 클래스(Animal 클래스를 포함하여)의 리스트를 받을 수 있음을 의미합니다. 따라서 List<Dog>List<Cat> 모두 이 메서드의 인자로 사용될 수 있습니다.

 

 

PECS(Producer Extends, Consumer Super) : 제네릭 타입을 다룰 때 사용되는 원칙 중 하나.

  • Producer Extends: 생산자(Producer) 역할을 하는 메서드에서는 와일드카드의 extends를 사용한다. 이는 해당 제네릭 타입을 "읽기 전용"으로 사용한다는 것을 의미한다. 즉, 데이터를 가져오는 역할을 한다.
  • Consumer Super: 소비자(Consumer) 역할을 하는 메서드에서는 와일드카드의 super를 사용한다. 이는 해당 제네릭 타입을 "쓰기 전용"으로 사용한다는 것을 의미한다. 즉, 데이터를 추가하거나 변경하는 역할을 한다.
import java.util.ArrayList;
import java.util.List;

class Animal {
    public void makeSound() {
        System.out.println("Animal is making a sound.");
    }
}

class Dog extends Animal {
    @Override
    public void makeSound() {
        System.out.println("Dog is barking.");
    }
}

class Cat extends Animal {
    @Override
    public void makeSound() {
        System.out.println("Cat is meowing.");
    }
}

public class Main {
    public static void main(String[] args) {
        List<Dog> dogs = new ArrayList<>();
        dogs.add(new Dog());
        dogs.add(new Dog());

        List<Cat> cats = new ArrayList<>();
        cats.add(new Cat());
        cats.add(new Cat());

        // Producer - extends
        // List<Dog>를 처리하는 메서드에는 List<? extends Animal>을 사용
        processAnimals(dogs);

        // Consumer - super
        // List<Animal>를 받아서 처리하는 메서드에는 List<? super Dog>를 사용
        addDogToList(cats);
        processAnimals(cats);
    }

    /**
     * 동물 리스트를 처리하는 메서드.
     * 
     * @param animals 처리할 동물 리스트
     */
    public static void processAnimals(List<? extends Animal> animals) {
        for (Animal animal : animals) {
            animal.makeSound();
        }
    }

    /**
     * 개를 리스트에 추가하는 메서드.
     * 
     * @param animals 개를 추가할 리스트
     */
    public static void addDogToList(List<? super Dog> animals) {
        animals.add(new Dog());
    }
}

 

이 코드에서 processAnimals 메서드는 List<? extends Animal>을 인자로 받아서 동물 리스트를 처리한다. 이 경우 List<Dog>와 List<Cat> 모두 processAnimals 메서드의 인자로 사용될 수 있도록 한정적 와일드카드를 사용한다.

addDogToList 메서드는 List<? super Dog>를 인자로 받아서 개 객체를 리스트에 추가합니다. 이 경우 List<Animal>을 포함하여 Animal 클래스의 상위 클래스를 인자로 받을 수 있도록 한정적 와일드카드를 사용한다.

 

결론 : 조금 복잡하더라도 와일드카드 타입을 적용하면 API가 훨씬 유연해진다. 그러니 널리 쓰일 라이브러리를 작성한다면 반드시 와일드카드 타입을 적절히 사용해줘야 한다. PECS공식을 기억하자. 즉, 생산자(producer)는 extends를 소비자(consumer)는 super를 사용한다.