Android 개발/Kotlin

Kotlin에서 활용하는 Generic 총정리

Developer88 2019. 10. 6. 01:41
반응형

Java에서와 마찬가지로,

Kotlin도 Genenric을 지원하는데요.

오늘은 Kotlin에서의 Generic에 대해 정리하겠습니다.

1.  Class에서의 Generic

1-1. Generic

Generic은,

class 혹은 함수를 사용하는 시점에,

사용할 타입을 지정하도록 하는 것 입니다.

 

코드를 보면서 이해해 볼까요?

아래 코드를 보면,

클래스이름 다음에 <T>라는 타입을 붙인것이 보입니다.

Generic타입임을 의미하는 것 인데요. 

Return값의 타입을 나타내는 곳과는 다른 특이한 위치에 있지요.

 

이것은 단 한줄로,

Generic 타입의 Person클래스를 정의한 것인데요.

사용시점에 타입을 정하므로,

String 또는 int타입으로 객체화하였네요.

 

 

 

객체화 하는 시점에, 타입을 다르게 정한 것이지요.

(원래 Java에서는 기본 데이터타입은 올 수 없었는데,

Kotlin에서는 모두가 객체로 참조형이기에 그런 제한이 없습니다)

 

1-2. Generic 2개 사용하기

아래와 같이 Generic을 두 개 사용하는 것도 가능합니다.

마치 함수의 인자 넣는 것처럼요.

세번째 코드에서 볼 수 있듯이,

추측할 수 있는 타입에 대해서는,

객체생성시에 Generic타입마저도 생략할 수 있습니다.

 

 

1-3. Generic Naming Convention

Generic에 사용되는 기호는 보통 T를 많이 사용하는데요.

T, S, U, V 를 차례로 사용하는 경우가 많습니다.

 

이 순서를 지키지 않는다고 컴파일 에러가 나지는 않고요.

그냥 K가 Key, V가 Value, R이 Receiver로 사용되는 것과 같이,

관습적인 사용일 뿐입니다.

 

2. Function에서의 Generic

Function에서도 Generic 사용이 가능한데요.

Generic을 붙이는 위치가, Java와는 조금 다릅니다.

 

Class에서와 마찬가지로 추정할 수 있는 타입에 대해서는,

아래의 getItPrint()와 같이 호출시 생략할 수도 있습니다.

 

 

이번에는 조금 복잡해보이지만,

소스코드를 한번 보겠습니다.

<T, R>이라는 두개의 Generic타입을 사용하였는데요.

인자로 T타입의 Generic을 받아서,

Result클래스로 Wrapping된 R타입의 값을 반환해 주는 군요.

 

 

온통 Generic 타입이네요.

Generic을 모르면 이 짧은 코드를 이해하기 어렵습니다.

3. Generic에 있어서의 제한

Generic을 사용하면서 생기는 장점은,

Class사용시에 타입을 정의할 수 있다는 것 인데요.

타입의 범위를 제한하는 방법들이 있습니다.

 

3-1. In과 Out

In과 Out을 이해하기 위해,

공장에서 물건을 생산하는 경우와 소비자가 물건을 구매하는 경우를 생각해 보면 좋은데요.

out은 공장에서 물건을 만들어 내는 것처럼 데이터를 생산하는 역할이고,

in은 가게에서 물건을 받아서 판매하는 소비자의 역할이 됩니다.

  Out In
역할 생산자(출력시만 사용, 꺼낼때만 사용) 소비자(입력시만 사용, 넣을때만 사용)
규칙 T타입으로 출력할 수만 있고,
넣을 수는 없다.
(출력시만 자유로운 타입사용가능)
T타입으로 입력할 수만 있고,
꺼낼 수는 없다.
(입력시만 자유로운 타입사용가능)

 

 

interface Producer<out T> {
    fun produce(): T  // T를 반환만 가능 (꺼내기)
}

val fruitProducer: Producer<Fruit> = AppleProducer()  // Apple은 Fruit의 하위 타입
val fruit: Fruit = fruitProducer.produce()

 

3-2. out의 예

아래에서 사과상자에는 Fruit타입에는 Generic으로 아무 타입이나 꺼낼 수 있지만,

반대로 넣을 수는 없습니다.

 

val appleBox: Box<out Fruit> = AppleBox()  
val fruit: Fruit = appleBox.getItem() // 사과를 과일로 받음 (O)
// appleBox.putItem(Fruit())  // ERROR! 과일을 넣을 수 없음

 

한가지 더 예를 보면 다음과 같습니다.

 

interface Consumer<in T> {
    fun consume(item: T)  // T를 파라미터로 받기만 가능 (넣기)
}

val appleConsumer: Consumer<Apple> = FruitConsumer()  // Fruit은 Apple의 상위 타입
appleConsumer.consume(Apple())

 

 

3-3. In

아래에서 쓰레기 통에,

사과든 무엇이든 마음껏 넣을 수 있지요.

이 때, 사과 뿐만 아니라 모든 사과의 부모 타입(Fruit, Any)을 넣을 수 있습니다.

하지만, in으로 

val trash: TrashCan<in Apple> = FruitTrashCan()
trash.putItem(Apple())  // 사과를 넣음 (O)
trash.putItem(Fruit())  // 과일도 넣을 수 있음 (O)
// val item: Apple = trash.getItem()  // ERROR! 꺼낼 수 없음

 

 

 

3-2. Upper Bounds

Generic타입뒤에 ":"을 붙이고 그 뒤에 타입을 넣어주면,

그 타입이거나 그 타입의 자식들만 올 수 있도록 할 수 있습니다.

이것을 UpperBounds라고 합니다.

 

 

아래 코드의 경우, 함수에 UpperBounds generic을 사용하였습니다.

R과 T 두개의 Generic타입중 T의 경우, R타입이거나 R타입의 자식타입들만 사용할 수 있도록 한 것이지요.

 

 

3-3. Where Bounds

UpperBounds이외에도 다른 제한을 두고자 한다면 where문을 사용하면 됩니다.

개인적으로는 아직 이정도를 사용해 보지는 못했지만,

우선은 아래와 같은 사용도 가능하다 정도로 알고 있으면 될 것 같습니다.

 

 

이상으로 Kotlin에서의 Generic사용의 기본적인 부분들에 대해서 정리해 보았습니다.

좀 더 필요한 내용이나 더욱 좋은 내용이 있다면 이 글에 추가하도록 하겠습니다.

 

728x90