안드로이드 개발을 하면서 Generic를 써봤지만 제대로 이해하고 쓴 적이 없는 것 같다.
그래서 이번 기회에 무슨 맛인지만 볼려고 한다.

제네릭이란 간단하게 말하자면 타입을 일반화해서 재사용성을 높이는 문법이다.
특히 타입 안정성과 유연성이 좋기 때문에 많이 사용된다.

제네릭은 총 5가지에 타입이 있다.
기본 형태는
class Box<T>(val value : T)
이런식으로 사용한다.
간단한 예시를 들자면
내가 Log를 남기기 위해 Log 남기는 함수를 만든다고 생각해보자.
근데 파라미터의 값이 Int도 있고 String도 있다고 가정해보자.
그럼 타입별로 함수를 만들어야된다.
// Int형
fun printInt(value : Int) {
println(value
}
// String형
fun printInt(value : String) {
println(value
}
하지만 제네릭을 쓰면
fun <T>print(value : T) {
println(value)
}
하나로 해결이 가능하다.
하지만 별도에 타입별로 처리를 안해줘도 되는 이유는
println의 파라미터 타입이 Any?이기 때문이다.
/** Prints the given [message] and the line separator to the standard output stream. */
public actual fun println(message: Any?) {
printlnImpl(message?.toString())
}
그렇기 때문에 타입 캐스팅을 안해줘도 되는 것이다.
만약 파라미터 타입이 정해져있으면 타입 캐스팅을 별도 처리 해줘야된다.
제네릭을 사용할 때 타입을 제한할 수도 있다. 예를 들면
fun <T : Number> sun(a : T, b : T) : Double {
return a.toDouble() + b.toDouble()
}
Number의 하위 타입만 받을 수 있다는 뜻이다. 그래서
Number을 상속받는 Int, Long, Float, Double만 받을 수 있고 나머지 타입은 모두 컴파일 에러가 뜬다.
공변성과 반공변성에 대해서도 알고 싶으면 부족하지만 나의 이전글을 확인해도 좋다
지금 현재 회사에서는 Composable한 함수를 재사용되기 위해 많이 사용되는데
보통 앱을 만들 때 각 회사마다 디자인 시스템이 있다. 지금 만들고 있는 앱도 디자인 시스템이 있다. 그래서 공통적으로 쓰는 컴포넌트가 있는데 대표적으로 Tabs에 대해 소개해보고 싶다.
앱 개발자라면 Tabs를 많이 사용해봤을 것이다.

요런 느낌인데 이런 컴포넌트들이 앱내에 공통적으로 사용된다.
근데 각 화면에 같은 디자인들을 매번 코드를 작성하기엔 비효율적이다.
그래서 보통 이런 디자인시스템에서 공통 컴포넌트들은 재사용 가능하게끔 만든다.
interface Tabs {
val title : String // Tab 화면에 보여질 텍스트
}
Tabs에선 보통 title하나면 충분하기 때문에 하나만 정의했다.
@Composable
fun <T : Tabs> TabBar(
tabs: List<T>,
selectedTab : T,
onSelectedTab : (T) -> Unit
) {
...
}
제네릭 타입을 T를 받되 타입 제한을 지정하여 Tabs를 상속받은 구현체만 받도록 했다.
// Tabs 구현
enum class MainTabs(override val title : String) : Tabs {
HOME("홈"),
MY("마이"),
SEARCH("검색")
}
// MainScreen.kt
TabBar(
tabs = MainTabs.entries,
selectedTab = MainTabs.HOME,
onSelectedTab = viewModel::onSelectedTab
)
이렇게 하면 Tabs를 각 화면에 맞게 생성만 해준다면 번거롭게 매번 로직을 생성 안해도 재사용 할 수 있게된다.
그게 바로 제너릭에 장점인 것 같다.
제너릭은 개발을 하면서 꼭 필요한 문법인 것 같다. 화면을 만들때도 최대한 재사용을 할려고 노력한다. 앞으로 제너릭을 더 활용하는 개발자가 되고싶다.