Rooted In Develop

Kotlin - 제네릭스 본문

Back End/Kotlin

Kotlin - 제네릭스

RootedIn 2023. 6. 10. 22:34

제네릭 함수와 프로퍼티

fun <T> List<T>.slice(indices: IntRange): List<T>
>>> println(letters.slice<Char>(0..2))
[a, b, c]
>>> println(letters.slice(10..13))
[k, l, m, n]

val authors = listOf("Dmitry", "Svetlana")
val readers = mutableListOf<String>(/* ... */)
fun <T> List<T>.filter(predicate: (t) -> Boolean): List<T>
>>> readers.filter { it !in authors }

val <T> List<T>.penultimate: T get() = this[size-2]
>>> println(listOf(1, 2, 3, 4).penultimate)
3

제네릭 클래스

interface List<T> {
  operator fun get(index: Int) : T
}
class StringList: List<String> {
  override fun get(index: Int) : String = /* ... */
}
class ArrayList<T>: List<T> {
  override fun get(index: Int) : T = /* ... */
}

interface Comparable<T> {
  fun compareTo(other: T) : Int
}
class String : Comparable<String> {
  override fun compareTo(other: String) : Int = /* ... */
}

타입 파라미터 제약

fun <T : Number> List<T>.sum() : T
fun <T : Number> oneHalf(value: T) : Double {
  return value.toDouble() / 2.0
}
fun <T: Comparable<T>> max(first: T, second: T) : T {
  return if (first > second) first else second
}
fun <T> ensureTrailingPeriod(seq: T)
  where T : charSequence, T : Appendable {
  if (!seq.endsWith('.') {
    seq.append('.')
  }
}

타입 파라미터를 널이 될 수 없는 타입으로 제한

class Processor<T : Any> {
  fun process(value: T) {
    value.hashCode()
  }
}

제네릭스 동작

실행 시점 : 타입 검사, 캐스트

if (value is List<String>) { ... } // ERROR
if (value is List<*>) { ... }

fun printSum(c: Collection<*>) {
  val intList = c as? List<Int> // Int가 아닌 경우 ClassCastExcpetion
    ?: throw IllegalArgumentException("List is expected")
  println(intList.sum())
}
fun printSum(c: Collection<Int>) {
  if (c is List<Int>) {
    println(c.sum())
  }
}

실제화한 타입 파라미터를 사용

- 인라인 함수로 만들고 타입 파라미터를 reified로 지정

inline fun <reified T> isA(value: Any) = value is T
>>> println(isA<String>("abc"))
true
>>> println(isA(String>(123))
false

>>> val items = listOf("one", 2, "three")
>>> println(items.filterIsInstance<String>()) // 표준 라이브러리 함수 filterIsInstance
[one, three]

하위 타입 / 상위 타입

- 타입 A의 값이 필요한 모든 장소에 어떤 타입 B의 값을 넣어도 문제가 없는 경우 타입 B는 타입 A의 하위 타입
- A는 A?의 하위 타입이지만 A?는 A의 하위 타입이 아님

공변성 : 하위 타입 관계를 유지

- A가 B의 하위 타입일때 Producer<A>가 Producer<B>의 하위 타입이면 Producer는 공변적(하위 타입 관계가 유지된다)
- 제네릭 클래스가 타입 파라미터에 대해 공변적임으로 표시하려면 타입 파라미터 이름 앞에 out을 붙임

interface Producer<out T> {
  fun produce() : T
}
open class Animal {
  fun feed() { ... }
}
class Herd<T : Animal> {
  val size: Int get() = ...
  operator fun get(i: Int) : T { ... }
}
fun feedAll(animals: Herd<Animal>) {
  for (i in 0 until animals.size) {
    animals[i].feed()
  }
}
class Cat : Animal() {
  fun cleanLitter() { ... }
}
fun takeCareOfCats(cats: Herd<Cat>) {
  for (i in 0 until cats.size) {
    cats[i].cleanLitter()
  }
  // feedAll(cats) // Herd<Cat> <-> Herd<Animal> 간의 에러 발생
}

// 공변적 컬렉션 역할로 변경
class Herd<out T : Animal> {
}
fun takeCareOfCats(cats: Herd<Cat>) {
  for (i in 0 until cats.size) {
    cats[i].cleanLitter()
  }
  feedAll(cats)
}

- 공변적이기 위해서는 out 위치에만 T가 존재해야함

interface List<out T> : Collection<T> {
  operator fun get(index: Int) : T // out 위치
}

interface List<out T> : Collection<T> {
  fun subList(fromIndex: Int, toIndex: Int) : List<T> // out 위치
}

interface MutableList<T> : List<T>, MutableCollection<T> {
  override fun add(element: T) : Boolean // in 위치이므로 MutableList는 T에 대해 공변적일 수 없음
}

- 읽기 전용 프로퍼티 : out
- 변경 가능 프로퍼티 : out, in

반공변성 : 뒤집힌 하위 타입 관계

interface Comparator<in T> {
  fun compare(e1: T, e2: T) : Int { ... } // in 위치
}
공변성 반공변성 무공변성
Producer<out T> Producer<in T> MutableLisit<T>
타입 인자의 하위 타입 관계가 유지됨 타입 인자의 하위 타입 관계가 뒤집힘 하위 타입 관계가 성립하지 않음
Producer<C>은 Producer<A>의 하위 타입 Consumer<A>은 Consumer<C>의 하위 타입  
T를 out 위치에서만 사용 T를 in 위치에서만 사용 T를 아무 위치에서나 사용

사용 지점 변성 : 타입이 언급되는 지점에서 변성 지정

// out-projection 타입 파라미터
fun <T> copyData(source: MutableList<out T>, destination: MutableList<T>) {
  for (item in source) {
    destination.add(item)
  }
}
>>> val list: MutableList<out Number> = ...
>>> list.add(42)
ERROR

// in-projection 타입 파라미터
fun <T> copyData(source: MutableList<T>, destination: MutableList<in T>) {
  for (item in source) {
    destination.add(item)
  }
}

- Kotlin의 MutableList<out T> 는 Java의 MutableList<? extends T> 와 같음
- Kotlin의 MutableList<in T> 는 Java의 MutableList<? super T>와 같음

스타 프로젝션 : 타입 인자 대신 * 사용

- MutableList<*>는 MutableList<Any?>와 같지 않음
- MutableList<*>는 구체적인 특정 타입의 원소만 담을 수 있다는 의미
- 스타 프로젝션을 위한 검증기 구현

object Validators {
  private val validators = mutableMapOf<KClass<*>, FieldValidator<*>>()
  fun <T: Any> registerValidator(kClass: KClass<T>, fieldValidator: FieldValidator<T>) {
    validators[kClass] = fieldValidator
  }
  @Suppress("UNCHECKED_CAST")
  operator fun <T: Any> get(kClass: KClass<T>) : FieldValidator<T> =
    validators[kClass] as? FieldValidator<T>
      ?: throw IllegalArgumentException("No validator for ${kClass.simpleName}")
}

>>> Validators.registerValidator(String::class, DefaultStringValidator)
>>> Validators.registerValidator(Int::calss, DefaultIntValidator)
>>> println(Validators[String::class].validate("Kotlin"))
true
>>> println(Validators[Int::class].validate(42))
true

'Back End > Kotlin' 카테고리의 다른 글

Kotlin - DSL  (0) 2023.06.24
Kotlin - annotation, reflection  (2) 2023.06.17
Kotlin - 고차 함수  (0) 2023.06.03
Kotlin - 연산자 오버로딩  (0) 2023.05.27
Kotlin - 타입 시스템  (0) 2023.05.06
Comments