코틀린 언어 정리 7-1

Generics(제네릭)

개념 이해

타입 파라미터를 입력하여 타입을 포함하는 코드를 만들어 낼 수 있는 타입입니다. 이해를 돕기 위해 배경지식을 조금 추가하여 설명하겠습니다.


일반적으로 사람(개발자)이 직접 생성(작성)한 코드(여기서는 “일반코드”라는 용어로 지칭)는 보통 컴파일 하여 바이너리코드로 변환됩니다. 대부분의 컴퓨터 개발 언어의 최종 산출물은 바이너리코드입니다.

일반코드 → 바이너리코드


그런데 제네릭으로 작성한 코드(여기서는 “제네릭코드”라는 용어로 지칭)는 내부적으로 사람이 작성할 수 있는 일반적인 코드로 변환된 후 바이너리 코드로 변환됩니다.

제네릭코드 → 일반코드 → 바이너리코드


바이너리코드는 사람이 이해하기 어렵고, 일반코드는 사람이 이해하기 쉽습니다. 그리고 제네릭코드는 사람이 이해하는 코드를 효율적으로 생성할 수 있습니다.(예를 들면 제네릭코드로 함수를 하나 선언해 놓으면 여러가지 타입에 대해 각각 함수를 구현할 필요가 없습니다.)


이런 상황을 보면, 제네릭은 코드를 효율적으로 생성하는 코드로 볼 수 있습니다.


컴퓨터 개발 언어에서의 정의는 조금 더 구체적입니다. 아마도 대부분의 언어에서 비슷한 정의를 가질 것으로 예상되지만 코틀린에서는 다음의 내용으로 정의하는 것 같습니다.


타입 파라미터를 가지는 클래스 또는 함수


여기서는 제네릭에 대한 일반적인 내용을 폭넓게 확인해 보고 불변이나 공변, 반공변 등의 조금 어려운 개념은 별도의 챕터에서 확인하겠습니다.


종류

제네릭(Generic class)

예제

class ClassA < T > {

   var obj: T? = null

}


제네릭 펑션/함수(Generic functions)

예제

fun < T > func1 ( obj: T ): T {

   var obj1 = obj

   return obj1

}



Generic constraints

Upper bounds(상한)

타입 파라미터(type parameter)를 통해 입력 되는 타입 인수(타입 아규먼트 / type argument)의 상속 계층을 제한할 수 있는 방법입니다.


예제

class ClassA {}


fun <T: ClassA> func1 ( obj: T ): T {

   return obj

}

<T:ClassA>로 명시한 부분은 T가 ClassA 의 하위 타입 이어야 한다는 것을 의미합니다.

타입 매개변수와 타입 인수( type parameters and type arguments )

예제

fun <T> func1 ( obj: T ): T {

   return obj

}

위와 같은 제네릭 함수를 기준으로 보았을 때, 타입 매개변수(타입 파라미터) T에 Int 타입이 입력되는 경우 Int를 타입 인수(type argument)라고 지칭합니다.

다중 상한

기본적인 상한 문법으로는 1개의 타입 매개변수에 대해 1개의 상속 계층 제한만 명시할 수 있지만 where를 사용하면 타입 매개변수에 원하는 만큼의 상속 계층을 명시할 수 있습니다.

where

예제

interface IFace1 {

   fun func1() {

       println("IFace1::func1")

   }

}


open class ClassA ( var value1: Int, var value2: String ){

}


// where 문 사용 예제...상한 제한을 사용할 다수의 Type들을 열거

fun <T, K, R> func1(a: T, b: K): R? where T: ClassA, T: IFace1, K: ClassA, K: IFace1 {

   var obj: R? = null

   println("func1")

   return obj

}


class ClassB <T, K, R> where T: ClassA, T: IFace1, K: ClassA, K: IFace1 {

}


class ClassC <T, K, R> constructor ( val value: String )

       where T: ClassA, T: IFace1, K: ClassA, K: IFace1 {

}


//fun main (args: Array<String> ) {

fun test () {

}



타입 변형(type variance)

타입 변형은 제네릭 타입을 선언할 때 공변/반공변 등을 in / out 등으로 지정하여 적용할 수 있습니다. SubA가 SuperA을 상속 받았다고 했을 때 제네릭 클래스인 GenClassA<SubA>, GenClassA<SuperA> 에도 동일한 또는 반대의 상속 관계를 적용해 주는 것을 말합니다.. 제네릭 타입을 선언할 때 타입 매개변수 앞에 in / out 등의 키워드를 명시하여 적용할 수 있습니다.



타입 투영(type projection)

이미 선언된 제네릭 타입으로 변수 / 매개변수 등을 선언할 때 공변/반공변 등을 in / out / * 키워드로 지정하여 적용할 수 있습니다. 참고로, 타입 변형은 타입 투영의 효과를 포함합니다.

Star projections

동일한 제네릭 타입에 "in / out + T(타입 매개변수)" 대신에 "*" 을 사용하면 T에 입력된 타입에 상관 없이 자동으로 "in / out + Nothing / Any?" 로 대체 됩니다. 즉 in / out으로 인한 컴파일 에러가 쉽게 해결됩니다.



타입 소거(type erasure)

JVM의 한계로 제네릭 타입의 타입 정보가 컴파일 후 제거 됩니다. 따라서 제네릭 타입에서는 타입 파라미터에 대해 is, as 와 같은 타입 체크 연산자를 사용할 수 없습니다.



타입 구체화(reified)

코틀린 컴파일 결과 JVM의 한계로 제네릭 함수의 타입 정보가 유실되는데 inline Generic 함수에 reified 키워드를 사용하면 실행시간에 제네릭 타입의 타입 정보 관련 연산자(is, as)를 사용할 수 있습니다.

인라인 제네릭 함수가 인라인화 되면서 인라인화 되는 주변 코드의 타입 정보를 참조할 수 있기 때문입니다.



Nothing, Any? Type

Nothing은 모든 타입의 하위 타입이고 Any?는 모든 타입의 상위 타입입니다. 공변 / 반공변 처리 시 in / out 대신 자동 처리를 할 때 Star-projections("*") 을 사용할 수 있는데 이 때 내부적으로 Nothing이나 Any? 를 사용합니다.



대수적 데이터 타입

sealed class와 같이 닫혀 있는(제시된 값만 가질 수 있는) 타입을 말합니다.



@UnsafeVariance

type variance(타입 변형) 관련 에러 발생 시 강제로 해결할 수 있게 해주는 애노테이션입니다.


+ Recent posts