Java에서와 마찬가지로 Kotlin도 Genenric을 지원하는데요.

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

1.  Class에서의 Generic

Generic이라고 하는 것은,

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

class 또는 함수에서 사용할 타입을 지정할 수 있도록 하는 것 인데요.

 

코드를 보면서 알아보도록 하겠습니다.

아래 코드를 보면, 먼저 클래스이름 다음에 <T>라는 타입을 정해서 붙여 주었는데요.

여기가 Generic타입을 정하는 위치입니다. Return값의 타입을 나타내는 곳과는 다른 곳 이지요.

 

클래스를 정의한 다음, Person클래스를 객체화 String혹은 Int로 객체화 하였는데요.

처음에는 외부에서 String이라는 타입을 정해 주고 "goodBoy"라는 값을 넘겨 주었습니다.

다음에는 Int라는 타입을 정해서 사용한 다음, 88을 넘겨 주었습니다.

객체화 하는 시점에 객체화할 타입을 정의할 수 있게 된 것이지요.

(원래 Java에서는 기본 데이터타입은 올 수 없었는데요, Kotlin에서는 모두가 객체로 참조형이기에 그런 제한은 없습니다)

 

 

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

마치 함수의 인자 넣는 것처럼 사용하는 것이지요.

또한 세번째 코드에서 볼 수 있듯이, 추측할 수 있는 타입에 대해서는 객체생성시에 Generic을 생략해 주어도 됩니다.

 

 

<참고> Generic의 Naming Convention

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

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

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

그냥 K가 Key, V가 Value, R이 Receiver로 사용되는 것과 같이 관습적인 것일 뿐이긴 합니다.)

 

 

2. Function에서의 Generic

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

Generic을 붙이는 위치가 Java에서와는 다르므로 주의가 필요합니다.

Class에서와 마찬가지로 추정할 수 있는 타입에 대해서는 마지막 코드에서 볼 수 있듯이,

호출시 생략이 가능합니다.

 

 

이번에는 아래 소스코드를 보도록 하겠습니다.

복잡해 보이기는 하지만, fun키워드를 이용한 함수이구요.

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

인자로 T타입의 Generic을 받아서, Result클래스로 Wrapping된 R타입의 값을 반환해 주는 군요.

 

 

3. Generic에 있어서의 제한

Generic을 사용하면서 생기는 장점은 Class사용시에

타입을 정의할 수 있다는 것 인데요.

다양한 타입을 정의할 수 있도록 하면서도, 그 범위를 제한하는 방법들이 있습니다.

 

3-1. In/Out

만약 Java에서 wildcard type의 Generic을 사용해 보았다면,

Generic의 In그리고 Out에 대해서 좀 더 이해하기 쉬울텐데요.

잠시 삼천포로 빠져서 Java의 Wildcard와 비교해 가며 정리해 보도록 하겠습니다.

 

<E>라는 타입의 Generic은 읽기쓰기 모두에 적용될 수 있는 타입인데요.

반면에 와일드카드(?)를 사용하는 Java에서의 아래의 두가지 타입을 사용하면,

읽고 쓰는데 제한을 할 수 있습니다.

 

A. Java에서의 <? extends E> 와 Kotlin에서의 out

Read(읽기) 전용. E 또는 E의 자식타입들만 받아들여서 읽어들인다는 것 입니다.

참고로 producer와 consumer라는 개념을 사용하여서,

producer로부터 읽을 때 사용한다고 할 수 있습니다.

upper bound(extends-bound)라고 표현하기도 합니다.

 

여기에 대응할 수 있는 Kotlin의 generic은 어떻게 표현할까요?

out 을 사용할 수 있습니다.

코드를 보면서 정리해 보겠습니다.

가장 흔하게 볼 수 있는 함수인 onRequestPermissionsResult를 보도록 하겠습니다.

유저에게 요청한 Permission의 결과에 따라서 결과를 주는 콜백함수인데요.

permissions를 보면, Out키워드를 볼 수 있습니다.

String과 그 자식들만 받아들이는 Array타입의 permission라고 하는 것을 알 수 있습니다.

 

 

한가지 더 보면, 아래는 List와 MutableList의 코드인데요.

E타입 혹은 그의 subtype을 read전용으로 가져올 수 있다고 Generic을 이용해서 기술되어 있습니다.

따라서, List에는 아이템을 추가할 수 있는 add()나 그와 유사한 메소드가 제공되지 않습니다.

 

 

 

반면 MutableList<E>는 E와 그 subtype으로 읽고 쓰기가 자유롭게 가능한 타입이지요.

당연히 add나 remove가 가능합니다.

 

 

 

B. <? super E >

위와는 반대로 write(쓰기)전용의 타입입니다.

E또는 E의 자식들만 쓸 수 있다는 것 이지요.

쓴다는 표현이 이해가 않가신다면,

List<? super String>의 경우와 같이 Collection에 add혹은 put한다고 생각하면 좋을 것 같습니다.

producer와 consumer개념을 기준으로,

consumer에 write할 때 사용한다고 할 수 있습니다.

Kotlin에서는 in 키워드를 이용합니다.

 

이번에도 실제 소스코드에서 사용하는 예를 잠깐 보도록 하겠습니다.

Comparable인터페이스에 in키워드를 사용해서, 쓰기전용의 Generic타입T를 사용하였습니다.

 

 

C. in과 out의 구분

구분하기 어려울 때는 공식문서에 나오는 것처럼,

consumer in, producer out이라고 생각하면 좋을 것 같습니다.

즉 consumer에 write할 때는 in하고, producer로부터 read할 때는 out한다고 말이지요.

 

3-2. Upper Bounds

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

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

이것을 UpperBounds라고 합니다.

 

 

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

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

 

 

3-3. Where Bounds

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

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

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

 

 

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

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

 

+ Recent posts