본문 바로가기
프로그래밍/JAVA

초보 자바 프로그래밍(36) - 공변성과 반공변성 (Covariance and Contravariance)

by 머니테크리더 2023. 5. 2.
반응형

공변성과 반공변성 대표 이미지
공변성과 반공변성 대표 이미지

🔖 INDEX

     

     

    공변성 (Covariance)

    공변성은 하위 타입 간의 관계를 유지하는 것을 의미합니다. 예를 들어, 클래스 A가 클래스 B를 상속하고 있을 때, List<A>와 List<B> 사이의 관계를 고려해봅시다. 공변성이 허용되는 경우, List<B>는 List<A>의 하위 타입으로 간주됩니다. 그러나 자바의 제너릭은 기본적으로 불변성을 따르므로, 이러한 관계가 자동으로 성립하지 않습니다. 자바에서 공변성을 처리하려면 와일드카드를 사용해야 합니다. 와일드카드를 사용하여 공변성을 지원하는 제너릭 타입을 선언할 수 있습니다.

    List<? extends A> covariantList;

    위 코드에서 List<? extends A>는 A와 A의 모든 하위 타입을 포함하는 리스트를 가리킵니다. 따라서 이 경우에는 List<B>를 List<? extends A>에 할당할 수 있습니다.

     

    반공변성 (Contravariance)

    반공변성은 상위 타입 간의 관계를 유지하는 것을 의미합니다. 예를 들어, 클래스 A가 클래스 B를 상속하고 있을 때, Consumer<A>와 Consumer<B> 사이의 관계를 고려해봅시다. 반공변성이 허용되는 경우, Consumer<A>는 Consumer<B>의 하위 타입으로 간주됩니다. 자바에서 반공변성을 처리하려면 와일드카드를 사용해야 합니다. 와일드카드를 사용하여 반공변성을 지원하는 제너릭 타입을 선언할 수 있습니다.

    List<? super B> contravariantList;

    위 코드에서 List<? super B>는 B와 B의 모든 상위 타입을 포함하는 리스트를 가리킵니다. 따라서 이 경우에는 List<A>를 List<? super B>에 할당할 수 있습니다.

     

    이처럼 공변성과 반공변성의 사용은 매개변수화된 타입을 안전하게 사용할 수 있도록 돕습니다. 이 두 가지 개념은 특히 메소드의 매개변수 및 반환 타입에 대한 타입 제한을 지정할 때 유용합니다. 이를 통해 개발자들은 제너릭 코드를 더 유연하게 사용할 수 있으며, 이로 인해 실행 시간에 발생할 수 있는 타입 오류를 컴파일 시간에 방지할 수 있습니다.

     

     

    공변성과 반공변성 사용 예제

    아래 예제에서 공변성과 반공변성을 사용하여 메소드 매개변수 및 반환 타입에 대한 타입 제한을 지정하는 방법을 살펴보겠습니다.

    import java.util.List;
    import java.util.ArrayList;
    
    class Animal {}
    class Mammal extends Animal {}
    class Dog extends Mammal {}
    
    public class GenericsExample {
        // 공변성을 사용하여 동물 리스트를 처리하는 메소드
        // <? extends Animal>을 사용하여 Animal의 서브타입을 허용
        public static void processAnimalList(List<? extends Animal> animalList) {
            for (Animal animal : animalList) {
                System.out.println(animal);
            }
        }
    
        // 반공변성을 사용하여 동물 리스트에 동물을 추가하는 메소드
        // <? super Mammal>을 사용하여 Mammal의 슈퍼타입을 허용
        public static void addAnimalToList(List<? super Mammal> mammalList, Mammal mammal) {
            mammalList.add(mammal);
        }
    
        public static void main(String[] args) {
            List<Animal> animalList = new ArrayList<>();
            List<Mammal> mammalList = new ArrayList<>();
            List<Dog> dogList = new ArrayList<>();
    
            // 공변성을 사용하여 다양한 동물 리스트를 처리
            // processAnimalList는 Animal의 서브타입 리스트를 처리할 수 있음
            processAnimalList(animalList);
            processAnimalList(mammalList);
            processAnimalList(dogList);
    
            // 반공변성을 사용하여 동물 리스트에 동물을 추가
            // addAnimalToList는 Mammal의 슈퍼타입 리스트에 Mammal 객체를 추가할 수 있음
            addAnimalToList(animalList, new Mammal());
            addAnimalToList(mammalList, new Mammal());
            
            // 반공변성 때문에 컴파일 오류 발생
            // dogList는 Mammal의 서브타입 리스트이기 때문에 Mammal 객체를 추가할 수 없음
            // addAnimalToList(dogList, new Mammal()); // 컴파일 오류
        }
    }

    위 예제에서 processAnimalList 메소드는 공변성을 사용하여 Animal의 하위 타입을 포함하는 리스트를 처리할 수 있습니다. 반면 addAnimalToList 메소드는 반공변성을 사용하여 Mammal의 상위 타입을 포함하는 리스트에 동물을 추가할 수 있습니다.

     

    이러한 방식으로 공변성과 반공변성을 사용하면 제너릭 코드를 더 유연하고 안전하게 사용할 수 있습니다.

     

     

    댓글