선언 지점 변성과 사용 지점 변성

Jaychy·2021년 6월 22일
0

알아가는 것

목록 보기
8/11
post-thumbnail

Github 코틀린으로 개발하면서 궁금했던 점에 대해 고찰하는 곳.

개요

자바나 코틀린에서 제네릭을 다룰 때는 변성이라는 개념이 중요합니다.
제네릭에서의 상위 타입과 하위 타입은 일반적인 개념과 조금 다르기 때문입니다.

자바에서는 사용 지점 변성 (use-site variance)만 가능한 반면에,
코틀린에서는 변성을 계속 지정해야 하는 사용 지점 변성의 단점을 개선하기 위해서
선언 지점 변성 (declaration site variance)을 통해
변성을 지정할 수 있도록 지원합니다.
물론 사용 지점 변성도 가능합니다.

변성이란?

변성은 제네릭 타입 간의 상위 타입과 하위 타입을 구분 짓기 위한 개념입니다.
일반적으로 어떠한 타입 TAny? 타입의 하위 타입이라는 것을 알 수 있습니다.
하지만 List<T> 타입이 List<Any?> 타입의 하위 타입이라는 것은 확신할 수 없습니다.

왜요?

만약 다음과 같은 함수가 있다고 생각해봅시다.

fun addString(targetList: MutableList<Any`>`) {
    targetList.add("String")
}

만약 MutableList<Integer> 타입이 MutableList<Any>의 하위 타입이라면
addString() 메소드에 인자로 MutableList<Integer> 타입의 변수를
보낼 수 있을 것입니다.

그러면 Integer 타입만 저장할 수 있는 리스트에 String 타입을 저장함으로써
타입 불일치 에러가 발생하게 될 것입니다.

그래서 어떤 제네릭 타입이 다른 어떠한 제네릭 타입의 상위 타입인지 하위 타입인지를
구분할 수 있도록 지정해주는 것이 변성이라는 개념입니다.

자바에서 제공하는 사용 지점 변성

자바에서는 변성을 사용 지점 변성을 이용하여 지정합니다.
자바에서는 클래스 내부의 메소드에 제네릭을 적용할 때마다 타입의 변성을 지정합니다.
어떠한 타입 T의 공변성을 허용하려면 <? extend T>를 사용하고
어떠한 타입 T의 반공변성을 허용하려면 <? super T>를 사용합니다.

공변성이란 어떠한 타입 T에 대해 그 타입과 그의 서브 타입을 허용하는 것을 말합니다.
반공변성은 어떠한 타입 T에 대해 그 타입과 그의 수퍼 타입을 허용하는 것을 말합니다.

코틀린에서는 사용 지점 변성을 outin 키워드를 이용해 지원합니다.
자바에서의 <? extend T>가 코틀린에서는 <out T>이고,
자바에서의 <? super T>가 코틀린에서는 <in T>입니다.

코틀린에서 제공하는 선언 지점 변성

만약 자바에서 제공하는 사용 지점 변성을 사용하여 모든 메소드에 공변성을 적용하려면
공변성이 적용되는 제네릭 타입을 사용하는 모든 메소드에 위 키워드들을 작성해야 합니다.

이러한 불편함을 해결하고 in 위치out 위치라는 개념을 통해
더 안전하고 쉬운 개발을 할 수 있도록 도와줍니다.

선언 지점 변성은 클래스를 선언할 때 공변성을 지정할 수 있게 하는 기능입니다.

class Box<out T>(val data: T)

이런 간단한 클래스가 있다고 했을 때
여기서 <out T>는 사용 지점 변성이 아니라
클래스 전역에 설정하는 선언 지점 변성입니다.

그래서 T 타입은 out 위치에서만 사용할 수 있도록 제한합니다.

out 위치는 보통 반환할 때의 위치 즉, 반환 타입을 말하는데,
반환 타입 말고도 out 위치를 특정하는 곳이 있습니다.
정확한 정보는 코틀린 공식 문서를 참조해주세요.

val box: Box<Number> = Box(10)

이렇게 생성한 Box 객체는 Number의 하위 타입인
Int, Double 타입만 가질 수 있습니다.

이는 클래스를 객체로 만들어 사용할 때도 적용할 수 있습니다.
MutableListadd() 메소드처럼 in 위치에 타입 파라미터가 있는 메소드도 있고,
get() 메소드처럼 out 위치에 타입 파라미터가 있는 메소드도 있습니다.

val mutableList: MutableList<out String> = mutableListOf("a", "b")

/*
mutableList.add("c")
Type mismatch. Required: Nothing Found: String
*/
println(mutableList[0])

이렇게 out 위치에 타입 파라미터가 있는 메소드는 사용할 수 있지만,
in 위치에 타입 파라미터가 있는 메소드는 사용할 수 없고
타입 파라미터 대신에 Nothing으로 치환됩니다.

결론

코틀린에서는 자바에서 지원하지 않는 선언 지점 변성이라는 기능을 지원합니다.
이 선언 지점 변성을 이용하게 되면 더 안전한 제네릭 프로그래밍을 할 수 있을 것 같습니다.

profile
아름다운 코드를 꿈꾸는 백엔드 주니어 개발자입니다.

0개의 댓글