본문 바로가기

안드로이드/알아두기

[안드로이드] 제네릭 함수, 클래스 정의하고 사용해보기

코드를 보면서 시작해보자

val temp  :List<String> = listOf()

 

위 코드를 보면, 'List안에 String을 집어 넣어서 리스트를 구성할게!' 라고 선언을 해줬다. 그렇다면 String 말고 다른건 넣을 수 있냐? 물론 넣을 수 있다. Int를 집어넣으면 Int를 집어넣는 리스트가, Double을 집어넣으면 Double을 집어넣는 리스트가 된다. 좀 자세히 보면, 저 리스트 타입 지정해주는 꺽세 안에는 무슨 타입만 들어가면 상관없는건가? 하고 Collections.kt를 뒤져보니까 아래처럼 나온다. 

 

public interface List <out E> : Collections<E>

 

 

 

public interface 그래 여기는 알지 그치 인터페이스라고? 근데 그 다음은 뭐냐 List 그래 이건 인터페이스 이름이잖아. 자 그리고 옆에 저 꺽세는 뭐야?? out은 뭐야? 어디 나가? 뭐야 E는 또 뭐야....? 라고 처음에 생각해서 궁금해서 찾아봤다. 그리고 저런 E와 같이 알파벳을 적어두고 나중에 선언할 때 타입을 설정해주는게 제네릭스라고 한다.

 

제네릭스가 뭘까요 그럼?

위에서 제네릭을 대충 언급했지만, 조금 더 멋들어진 말로 설명하자면, 제네릭스(Generics)클래스나 함수를 정의할 때 파라미터값이나 매개변수 혹은 반환값의 타입을 지정하지 않고 선언할 당시에 타입을 지정해주는 것을 말한다. 

 

 

아 그럼 나 오버로딩 안해도 됐던거네? 라는 생각이 들어서 오버로딩 했던 찬란한 시간들이 주마등처럼 스쳐지나갔지만, 지금이라도 오버로딩 안하고 그저 제네릭으로 휘갈겨서 쓰면, 덜 귀찮아질 거라는 생각에 행복하기 그지 없었다. 그렇다면 제네릭스가 뭔지도 알았으니까 제네릭스를 어떻게 쓸지, 그리고 어떤 유형으로 써먹을 수 있는지 그리고 실질적으로 어떻게 적용해볼지 고민해서 올려봤다.

 

뭐 공변성이네, 불변성이네 in을 쓰네 out을 쓰네.... 솔직히 까고 말해보면 저런 공변성이네, 불변성이네 사실 지금 저 제네릭스라는 거를 알아내서 기쁜마음으로 쓸건데, 괜한 문과의 찬란한 유산이 낳아낸 어려운 단어를 쓰지 말고 실질적으로 어떻게 쓸 수 있는지나 알아보자.

 

1. 제네릭으로 클래스 정의 하기

1) 제네릭 타입 1개만 지정하기
class generic<T> (val param1:T, val param2 :T){}

class generic<T> (val param1:T, val param2 :T):T{}

class generic<T> (val param1:T, val param2 :Double){}

class generic<T> (val param1:Double, val param2 :T){}

class generic<T> (val param1:Double, val param2 :Double):T{}


2) 클래스에서 선언된 제네릭 타입 함수에서 사용하기
class generic<T> (val param1:T, val param2:T){
	fun <A> genTest (first : T, second : A = this.second){}
}


3) 제네릭 타입 여러개 지정하기
class generic<T, C> (val param1:T, val param2:C):T{}

 

위 예시들을 주르륵 나열해봤다. 오우 정말 제네릭은 변수의 타입이 될 수 있으면 엉간하면 다 들어간다 생각이 들어서 클래스에서 선언한걸 함수에도 써먹어 봤는데 아주 잘 된다. 확장성이 정말 어마어마하다. 이어서 함수에서는 어떻게 쓰는지 한번 보자.

 

 

2. 제네릭으로 함수 정의 하기

1) 제네릭 타입 1개만 지정하기
fun <T> test1(param: T, param2: T): T {}

2) 제네릭 타입 1개 이상 지정하기
fun <T, C> test2(param: T, param2: C): C {}

3) 클래스의 제네릭 타입 가져오기
fun test3(param: A) {}

4) 제네릭 타입 1개 지정하고 클래스에서 제네릭 타입 가져오기
fun <T>test4(param:T, param2:A= this.second){}

 

참 별개 다 된다. 정말 짱이다. 제네릭스 진짜 아직까지 안쓴게 좀 후회되긴 하지만, 지금이라도 잘 써먹으면 되지 않겠는가 흐흐흐흐

 

3. 그럼 어디다 써먹을까?

첫 번째는 당연히 API Call Response다. 보자마자 이건 무조건 API Response에다가 써먹어야 한다고 생각했다.

data class apiTest<A>(
    @SerializedName("message") var message: String = "",
    @SerializedName("code") var code: Int,
    @SerializedName("args") var args: A? = null
)

 

위와 같은 방식으로 제네릭으로 타입을 지정하면, message와 code는 항상 동일하다 가정하면 args가 약간씩 달라진다고 가정했을 때 다른 데이터 클래스 만들필요 없이, 하나의 틀을 두고 제네릭으로 args 변수 타입만 지정해서 써먹어주면, 재활용이 너무 편할 것 같다. 

 

두 번째는 매개변수로 Int나 Double 둘 중 하나를 필요로 하는 함수가 있다면, 당연히 제네릭을 써서 해야 한다고 생각한다.

 

fun <T> getRectangle(width: T, height: T) {}

 

아주 유용하지 않을까 싶다.