본문 바로가기

Programming/Java

자바 제네릭의 공변, 반공변, 무공변 완벽 이해

반응형

제네릭(Generics)은 자바에서 타입의 안전성을 보장하고 코드 재사용성을 높이기 위해 도입된 중요한 기능입니다. 하지만 제네릭을 사용할 때 공변(Covariance), 반공변(Contravariance), 무공변(Invariance) 개념을 이해하지 못하면, 와일드카드(? extends T, ? super T)나 특정 타입 제한을 설정할 때 혼란스러울 수 있습니다. 이번 글에서는 자바에서의 제네릭 변성(Variance)에 대해 쉽게 정리해보겠습니다.

1. 공변(Covariance) - ? extends T

공변이란 서브타입 관계가 유지되는 것을 의미합니다. 즉, List<Cat>List<Animal>의 하위 타입으로 간주될 수 있는 경우입니다.

예제 코드

class Animal {
    void speak() { System.out.println("Animal speaks"); }
}
class Cat extends Animal {
    void meow() { System.out.println("Meow"); }
}

public class CovarianceExample {
    static void makeAnimalsSpeak(List<? extends Animal> animals) {
        for (Animal animal : animals) {
            animal.speak(); // Animal의 메서드는 호출 가능
        }
    }
    public static void main(String[] args) {
        List<Cat> cats = List.of(new Cat());
        makeAnimalsSpeak(cats); // List<Cat>을 List<? extends Animal>로 전달 가능
    }
}

특징

  • ? extends T를 사용하면 읽기(read)는 가능하지만 쓰기(write)는 불가능합니다.
  • makeAnimalsSpeak(List<? extends Animal> animals)에서 animals.add(new Animal()) 같은 코드는 컴파일 오류가 발생합니다.
  • 이유는 animalsList<Dog>일 수도 있기 때문입니다. Dog의 리스트에 Animal을 추가하면 타입 안정성이 깨질 수 있습니다.

정리: ? extends T는 "읽기 전용"을 허용하지만, 타입 안전성을 위해 새로운 객체 추가는 금지됩니다.


2. 반공변(Contravariance) - ? super T

반공변이란 슈퍼타입 관계가 유지되는 것을 의미합니다. 즉, List<Animal>List<Cat>의 상위 타입으로 간주될 수 있는 경우입니다.

예제 코드

import java.util.*;

class Animal {
    void speak() { System.out.println("Animal speaks"); }
}
class Cat extends Animal {
    void meow() { System.out.println("Meow"); }
}

public class ContravarianceExample {
    static void addCats(List<? super Cat> cats) {
        cats.add(new Cat());
    }
    public static void main(String[] args) {
        List<Animal> animals = new ArrayList<>();
        addCats(animals); // List<Animal>을 List<? super Cat>으로 전달 가능
    }
}

특징

  • ? super T를 사용하면 쓰기(write)는 가능하지만 읽기(read)는 제한적입니다.
  • cats.add(new Cat())는 가능하지만, cats.get(0).meow() 같은 코드는 컴파일 오류가 발생합니다.
  • 이유는 catsList<Object>일 수도 있기 때문입니다. Object 타입으로 반환하면 meow() 메서드를 보장할 수 없습니다.

정리: ? super T는 "쓰기 전용"을 허용하지만, 가져올 때는 Object로만 사용할 수 있습니다.


3. 무공변(Invariance) - T (기본적인 제네릭)

무공변이란 서브타입 관계가 유지되지 않는 것을 의미합니다. 즉, List<Cat>List<Animal>로 자동 변환되지 않습니다.

예제 코드

List<Animal> animals = new ArrayList<Cat>(); // ❌ 컴파일 오류
  • List<T>는 기본적으로 무공변이므로, List<Cat>List<Animal>에 할당할 수 없습니다.
  • 이를 해결하려면 ? extends T 또는 ? super T를 사용해야 합니다.

정리: 제네릭을 사용할 때 명시적인 변성을 지정하지 않으면 무공변이 기본적으로 적용됩니다.


4. 공변, 반공변, 무공변 비교

 

변성 유형 키워드  읽기 쓰기 예제
공변(Covariance) ? extends T ✅ 가능 ❌ 불가능 List<? extends Animal>
반공변(Contravariance) ? super T ⚠️ 제한적 ✅ 가능 List<? super Cat>
무공변(Invariance) T ✅ 가능 ✅ 가능 List<T>

5. 정리 및 활용 팁

  • ? extends T: 읽기 전용 리스트를 만들 때 사용 (예: List<? extends Number>)
  • ? super T: 쓰기 전용 리스트를 만들 때 사용 (예: List<? super Integer>)
  • T: 일반적인 제네릭 타입으로 사용 (예: List<String>, List<Integer>)

 

제네릭 변성을 이해하면 자바의 컬렉션 프레임워크나 API를 활용할 때 더욱 안전하고 유연한 코드를 작성할 수 있습니다.

반응형