타입 제네릭

우선 타입 제네릭이라고 해도 무조건 아무거나 넣을 수 있는 것이 아니다.
제네릭은 특정 정해진 범위(특정 타입의 객체나 데이터 등) 내에서 제약없이 다룰 수 있다고 이해해야 한다.
간혹 들어갈 수 없는 객체나 데이터를 담을 경우 컴파일 에러가 뜰 수도 있기 때문에 타입 제너릭은 만능이 아니다라는 사실을 꼭 기억해두어야한다.

공식문서에서의 타입 제네릭

처음 공식문서나 스택 오버플로우를 찾다보면 내가 알던 내용도 모르는 것처럼 다가올 수 있는데, 그 이유가 바로 타입 제네릭과 같이 약속된 규범들을 알지 못하기 때문일 것이다.

제네릭의 사용법

  • <T> : 타입을 의미한다. 주로 해당 블럭에서만 유효성을 가지며 코틀린의 공식문서에서는 자료형이 들어간다고 이해하면 쉽다.
    fun <T> Iterable<T>.filter(
      predicate: (T) -> Boolean
    ): List<T>
    
  • <E>: Element즉 요소를 의미한다.
    예를 들어서 Set을 공식문서로 찾으면 interface Set<out E> : Collection<E>으로 정의되어 있다.
    이 코드를 뜯어보자면, Set은 어떠한 요소들이 입력되는데 이 요소들은 하나의 컬렉션을 확장하여 일반적인 상위 타입인 Set으로 변환된다고 이해하면 된다.
  • <K>와 <V>: 키 값과 value값을 의미한다.
    코틀린 공식문서의 Map을 보면 interface Map<K, out V>으로 설명 되어있다.
    즉 Map은 입력값을 키와 value로 짝지어서 반환해주는 인터페이스라는 이해가 가능하다.
    또한 value에만 out이 붙은 이유는 공변성과 관계있는데, 간단하게 설명하면 key는 안전성 보장을 위해 들어온 형태를 그대로 유지해야하기 때문이다.
  • <N>:넘버를 의미한다.
    즉 N이 타입제너릭으로 되어있다면, 숫자형만 들어올 수 있고 string과 같은 다른 자료형은 들어올 수 없다.

    공변성이란

    Dog가 Animal의 서브타입이면 ` C`는 `C `의 서브타입임을 의미한다.
    즉 하위 타입 관계를 유지하면서 상위 타입으로의 캐스팅을 허용하는 특성을 의미하는데, 이러한 공변성은 읽기 작업을 위한 유연성을 제공해준다.

주의할 점은 쓰기작업에 있어서는 무공변성이 유지된다.
따라서 읽기작업에서 서브타입의 관계가 유지되었다고해서, 애니멀에 Dog를 할당하면 안전성을 보장할 수 없다.

제네릭 타입의 클래스 만들기

class ClassName<E> {
    private var element: E? = null

    fun set(element: E) {
        this.element = element
    }

    fun get(): E? {
        return element
    }
}

여기 이 코드를 읽으면서 처음 들었던 생각은 이정도라면 코틀린의 requireinit구문으로 조건을 주어 코드를 작성하는 방법도 있지 않을까라는 것이었다.
그러나, 타입 제네릭의 기본적인 목적중의 하나는 범용성이다.
따라서 제약이나 초기화를 위해 사용되는 require이나 init구문과는 애초에 목적 자체가 다르다고 할 수 있다.
즉 제네릭은 범용성과 코드의 재사용성을 높여주고, require과 init은 조건 검사를 통해 특정 상황에서의 안정성을 높여주는 것이 목적이다.

왜 사용할까

우선 타입 제네릭의 효과 중 하나는 명시와 범용성이다.
E의 경우 요소를 의미하는 만큼 생각보다 많은 객체가 들어갈 수 있다.
특정 타입에 의존하지 않는 독특한 형태의 클래스를 만듦으로써 다른 곳에서 필요할 때마다 가져다 쓸 수 있는 범용적인 기능을 만드는 역할을 할 수 있다고 보면 된다.

범용성과 명시성은 반대 개념 아닌가?

주로 범용성은 코드의 재사용성과 함께 언급이 되고, 명시성은 코드의 안정성과 함께 언급되는 경우가 많다.
물론 제네릭은 범용성에 가까운 성격을 가지고 있지만, 내가 명시라는 단어를 언급한 이유는 그 제네릭 역시 아무것이나 다 집어넣어서는 안되고 우리가 약속한 규약 속에서 내용물을 집어넣어야하기 때문이다.
또한 나 역시 이 둘이 이론적으로는 반대의 개념이 아닌가 싶었으나, 지나친 코드의 자율성과 코드의 재사용은 안정성의 저하를 일으킬 수도 있다.
바로 이런 부분을 보완하여 주는 것이 명시성을 주는 작업들이다.
즉 범용성과 명시성은 서로 상반된 개념이 아닌 상충 관계를 조절해야하는 상호보완적 개념에 가깝다.

댓글남기기