제네릭이란?
제네릭은 클래스, 인터페이스, 메서드에서 사용될 수 있으며, 타입 매개변수를 통해 일반적인 데이터 타입 대신 실제 타입을 지정할 수 있다. 이를 통해 코드를 보다 일반화할 수 있고, 재사용성을 높일 수 있다.
T | 일반적으로 사용되는 타입 매개변수 |
E | 요소(Element)의 타입을 나타내는 매개변수. 주로 컬렉션(Collection)에서 사용 |
K | 맵(Map)에서 키(Key)의 타입을 나타내는 매개변수 |
V | 맵(Map)에서 값(Value)의 타입을 나타내는 매개변수 |
N | 숫자(Number) 타입을 나타내는 매개변수 |
R | 반환(Return) 타입을 나타내는 매개변수입 |
S, U, V 등 | 여러 개의 타입 매개변수가 필요한 경우, 추가적으로 사용될 수 있는 일반적인 알파벳 |
제네릭을 사용하지 않았을 경우
우리가 String 문자열을 저장하는 리스트를 생성한다고 생각 해보자. 우리는 다음과 같이 코드를 작성할 것이다.
val stringList: List<String> = listOf("Hello", "World") // 문자열을 저장하는 리스트 생성
이제 이 문자열의 요소를 출력해주는 메소드를 만들려고 한다.
fun printStringList(list: List<String>) {
for (item in list) {
println(item)
}
}
그렇다면 우리는 위와 같이 메소드를 작성해야 할 것이다. 이번엔 Int 문자열을 저장하는 리스트를 생성하고 이를 출력해주는 메소드를 만들어보겠다.
val integerList: List<Int> = listOf(10, 20) // 정수를 저장하는 리스트 생성
fun printIntList(list: List<Int>) {
for (item in list) {
println(item)
}
}
위의 코드들을 한번에 나타내면 다음과 같다.
fun main() {
val stringList: List<String> = listOf("Hello", "World") // 문자열을 저장하는 리스트 생성
printStringList(stringList) // 문자열 리스트 출력
val integerList: List<Int> = listOf(10, 20) // 정수를 저장하는 리스트 생성
printIntList(integerList) // 정수 리스트 출력
}
fun printStringList(list: List<String>) {
for (item in list) {
println(item)
}
}
fun printIntList(list: List<Int>) {
for (item in list) {
println(item)
}
}
매우 불편한 코드이다. 2개의 메소드는 똑같은 로직을 수행하고 있다. 다만 파라미터의 타입만 다를 뿐이다. 불필요한 중복이 발생하고 있다. 이 때 사용할 수 있는것이 바로 제네릭이다.
제네릭을 사용했을 경우
fun main() {
val stringList: List<String> = listOf("Hello", "World") // 문자열을 저장하는 리스트 생성
printList(stringList) // 문자열 리스트 출력
val integerList: List<Int> = listOf(10, 20) // 정수를 저장하는 리스트 생성
printList(integerList) // 정수 리스트 출력
}
fun <T> printList(list: List<T>) {
for (item in list) {
println(item)
}
}
제네릭을 사용한 코드이다. 중복을 한번에 해결한 것을 알 수 있다. 메소드 이름 왼쪽에 꺾쇠괄호가 오고 그 뒤에 데이터 유형의 자리표시자 이름, 오른쪽 꺽쇠괄호가 온다. 그럼 이 메소드는 List<T>의 파라미터를 받고, 리스트의 요소를 출력한다. T는 타입 매개변수로 실제 사용될 때 구체적인 타입으로 대체된다.
T(type의 약자)라는 일반 유형이 표시되거나, 클래스에 여러 일반 유형이 있는 경우 다른 대문자가 표시되는 경우가 많다. 하지만 이에 대한 명확한 규칙은 없으며 일반 유형에 더 구체적인 이름을 사용해도 된다.
클래스에서의 제네릭
클래스에서도 제네릭을 사용할 수 있다. 클래스에서는 아래와 같은 형식을 가진다.
클래스의 이름 오른쪽에 일반 유형 매개변수를 추가하고, 클래스의 속성에 추가한 일반 유형 매개변수를 지정할 수 있다. 이렇게 지정된 속성의 타입은 클래스를 인스턴스화 할 때 데이터 유형이 지정된다.
아래와 같은 예시를 봐보자.
class Question<T>(
val questionText: String,
val answer: T,
val difficulty: String
)
위의 클래스에서는 일반 유형 매개변수를 T로 받고 answer의 타입을 T로 지정해주고 있다.
fun main() {
val question1 = Question<String>("Quoth the raven ___", "nevermore", "medium")
val question2 = Question<Boolean>("The sky is green. True or false", false, "easy")
val question3 = Question<Int>("How many days are there between full moons?", 28, "hard")
}
인스턴스를 생성할 때, 클래스의 이름 오른쪽에 answer에 들어갈 데이터 유형을 지정해주면 하나의 클래스로 3가지의 answer 타입을 갖는 코드를 만들 수 있다.
Reference
'Android > Kotlin' 카테고리의 다른 글
Sealed Class와 Enum Class 뭘 사용해야 하나요? (0) | 2023.09.04 |
---|---|
Scope Function (범위 지정 함수) with let, run, with, apply, also (0) | 2023.06.30 |
[2] MVVM Repository에 Room과 Hilt 사용하기 (0) | 2023.06.19 |
[1] MVVM Repository에 Room과 Hilt 사용하기 (0) | 2023.06.19 |
[Kotlin Coroutine] (6) - 채널 2 (0) | 2023.06.08 |