본문 바로가기
Java, Kotlin, Spring/Java

공변, 반공변이란 무엇일까

by Wordbe 2022. 10. 27.
728x90

공변, 반공변이란 무엇일까

변성(Variance)


자바 제네릭은 기본적으로 불변

  • StringObject 의 서브타입이다.
  • 하지만, List<String>List<Object> 의 서브타입이 아니다. 즉 자바의 제네릭 타입은 불변(invariant) 이다. 변성(variance)이 없다.


불변임으로 인해서 나타나는 아래 문제점을 보자.

뭔가 이상한 addAll 코드

class Collection<E> {
  void addAll(Collection<E> items) {
    for (E e: src) {
      push(e);
    }
  }
}
void copyAll(Collection<Object> to, Collection<String> from) {
    to.addAll(from); // compile error 
}

copyAll메소드는 컴파일에러가 발생한다. 사실 copyAll 메소드는 아래와 같이 있어도 아무 문제가 없다. String 이 Object 로 들어갈 수 있기 때문이다. 하지만 자바의 제네릭은 불변이고 컴파일러가 변성을 판단할 수 없어 타입이 안전한지 모르므로 컴파일 오류를 낸다.


아래와 같아야 컴파일에러가 나지 않음

class Collection<E> {
  void addAll(Collection<? extends E> src) {
    for (E e: src) {
      push(e);
    }
  }
}

이 경우는 extends 키워드로 src 파라미터를 공변으로 만들어주었다. 즉, 인풋으로 들어오는 Collection<String>Collection<Object> 의 하위타입임을 컴파일러가 알게하면서 타입 안정성을 제공했다. 이를 공변(Covariance)이라 한다.



위 콜렉션에, popAll 메소드가 있어, 인풋파라미터에 내가 가진 값을 값 옮겨주는 메소드가 있다고 해보자. (이런 메소드를 많이 만들진 않겠지만)

class Collection<E> {
  void popAll(Collection<? super E> dst) {
    while(!isEmpty()) {
      dst.add(pop());
    }
  }
}

이 경우는 super 키워드로 dst 파라미터를 반공변으로 만들어 주었다. 즉, 인풋으로 들어오는 Collection<Object>Collection<String> 의 상위타입임을 컴파일러가 알게하면서 타입 안정성을 제공했다. 이를 반공변(Contravariance)이라 한다.



다소 헷갈리는 위 개념을 쉽게 기억하기 위해 Joshua Bloch 는 Effective Java 에서 PECS 라는 암기법을 제안했다.

PECS (Producer-Extends, Consuer-Super)

  • 인풋 파라미터를 읽기만 하는 메소드를 가진 객체는 Producer 라 명명하고, 인풋 파라미터에 extends 를 사용해서 공변을 만든다.
  • 인풋 파라미터를 쓰기만 하는 메소드를 가진 객체는 Consumer 라 명명하고, 인풋 파라미터에 super 를 사용해서 반공변을 만든다.

자바의 제네릭에 타입 안정성을 제공하고 싶다면, PECS 를 기억하자.

728x90

댓글