Kotlin-In-Action | #9. 제네릭스

보람·2022년 5월 24일
0

Kotlin-In-Action

목록 보기
10/12

제네릭 타입 파라미터

  • 제네릭스를 사용하면 타입 파라미터를 받는 타입 정의 가능
    • 이 변수는 문자열을 담는 리스트다 라고 말할 수 있는 것
  • 제네릭 타입의 인스턴스를 만들려면 타입 파라미터를 구체적인 타입 인자로 치환해야 함
  • Map<K, V> -> Map<String, Person>
  • listOf("Dmitry", "Svetlana") : List<String> 임을 추론

제네릭 함수와 프로퍼티

  • 모든 타입을 저장하는 리스트(제네릭 리스트)를 다룰 수 있기를 원한다.
    • 컬렉션을 다루는 라이브러리 함수는 대부분 제네릭 함수
fun <T> List<T>.slice(indices: IntRange) : List<T>
  타입파라미터 | 타입파라미터가 수신 객체와 반환 타입에 사용
  
val letters = ('a'..'z').toList()
println(letters.slice<Char>(0..2)) //[a, b, c] => 타입 인자를 명시적으로 지정
println(letters.slice(10..13)) //[k, l, m, n] => 컴파일러는 여기서 T가 Char라는 사실 추론
  • 함수의 타입 파라미터 T가 수신 객체와 반환 타입에 쓰인다. => 수신&반환 타입 모두 List<T>
val <T> List<T>.penultimate: T
	get() = this[size-2] 
    
println(listOf(1, 2, 3, 4).penultimate) //3
  • 제네릭 함수를 정의할 때와 마찬가지 방법으로 제네릭 확장 프로퍼티를 선언 할 수 있다.
  • 위 예제에서 T는 Int로 추론

제네릭 클래스 선언

interface List<T> {
	operator fun get(index: Int) : T
    //..
}
  • 클래스(인터페이스)도 제네릭하게 만들 수 있다.
  • T를 인터페이스 안에서 일반 타입처럼 사용 가능
class StringList: List<String> {
    override fun get(index: Int): String = ...
}

class ArrayList<T> : List<T> {
    override fun get(index: Int): T = ...
}
  • 제네릭 클래스를 확장하는 클래스를 정의하려면 기반 타입의 제네릭 파라미터에 대해 타입 인자를 지정해야 한다.
    • 기반 타입이란 상위 타입을 의미
  • StringList 클래스는 구체적인 타입 인자로 String 을 지정해 List를 구현
    • 하위 클래스에서 상위 클래스에 정의된 함수를 오버라이드하거나 사용하려면 타입 인자 T를 구체적 타입 String 으로 치환해야 한다.
    • Comparable 인터페이스를 구현하는 클래스가 이런 패턴의 예다.

Comparable 인터페이스

interface Comparable<T> {
    fun compareTo(other: T): Int
}

class String : Comparable<String> {
    override fun compareTo(other: String): Int = /*...*/
}
  • String 클래스는 제네릭 Comparable 인터페이스를 구현하면서 그 인터페이스의 타입 파라미터 T로 String 자신을 지정한다.

타입 파라미터 제약

  • 타입 인자를 제한하는 기능
    • ex) sum 함수 : 숫자 타입만을 허용
    • 해당 숫자 타입이 상한 타입
  • 상한으로 지정하면 그 제네릭 타입을 인스턴스화할 때 사용하는 타입 인자는 반드시 그 상한or하위타입이어야 함
println(listOf(1, 2, 3).sum()) //6 
  • Int 가 Number 를 확장하므로 합법적

null이 될 수 없는 타입 파라미터

  • 상한 타입 지정 X : Any? 와 같다(가장 큰 타입 개념)
  • <T:Any> : not null로 지정됨

소거 타입, 실체 파라미터

  • 제네릭스는 보통 타입 소거를 사용해 구현
    • 타입 소거(type erasure) : 컴파일시에만 존재하고 실행시 타입 없음
  • 타입을 갖고 싶다면? inline + reify(실체화)

실행 시점의 제네릭 : 타입 검사와 캐스트

  • 실행 시점에 List<String>List<Int> 는 완전히 같은 타입의 객체
  • 타입 소거의 한계
    • is 불가능
  • List<String>, List<Int> 가 아닌 리스트야! 라는 것을 알고 싶다면? 스타 프로젝션(List<*>)을 사용하자

실체 파라미터 : inline + reified

inline fun <refied T> isA(value: Any) = value is T

println(isA<String>("abc")) //true 

println(isA<String>(123)) // false 
  • 타입 인자를 실행 시점에 알 수 있기 때문에 is 가능
    • 각 원소가 타입 인자로 지정한 클래스의 인스턴스인지 검사 가능
  • reified 키워드는 이 타입 파라미터가 실행 시점에 지워지지 않음을 표시

왜 인라인 함수에서만 실체화가 가능하쥬?🥸

  • (8장 인라인 함수 : 람다의 부가 비용 없애기)
  • 컴파일러가 인라인 함수의 본문을 구현한 바이트 코드를 그 함수가 호출되는 모든 지점에 삽입
  • 인라이닝함으로써 얻는 이익이 더 큰 경우에만 사용하기
  • 함수가 계속 커진다면 비추천
inline fun <reified T> loadService() {
	return ServiceLoader.load(T::class.java)
}
  • reified 로 타입 파라미터 실체화
  • T::class 로 타입 파라미터의 클래스를 가져온다.

reified 타입 파라미터 제약

  • 아래에서 사용 가능
    • is, !is, as, as? 가능
    • 리플렉션 API
    • ::class.java 로 클래스 얻기
    • 다른 함수를 호출할 때 타입 인자로 사용
  • 아래는 안 됨
    • 타입 파라미터 클래스의
      • 인스턴스 생성
      • 동반 객체 메서드 호출하기
    • 실체화한 타입 파라미터를 요구하는 함수를 호출하면서 실체화하지 않은 타입 파라미터로 받은 타입을 타입 인자로 넘기기
    • inline이 아닌 함수에 reified로 지정하기

변성: 제네릭과 하위 타입

  • 변성 : 같인 기저 타입에 다른 여러 타입들간의 관계를 설명하는 개념

변성이 있는 이유

  • 인자를 함수에 넘기기
  • 공변성질 : List<Any> 를 받는 함수에 List<String> 을 넘기는 것은 절대로 안전!
    • 하위타입->상위타입
  • 반공변성질 : MutableList<Any> 를 받는 함수에 MutableList<String> 을 넘기는 것은 컴파일 불가능!
    • 상위타입->하위타입
    • Any가 상위타입인데 String을 Any에게 보내려 하고 있어서 불가능

하위 타입

fun test(i: Int) {
	val n: Number = i
    fun f(s:String) { /*...*/ }
    f(i)
}
  • Number > Int(하위)
  • Int가 String의 하위 타입이 아니어서 컴파일 불가능
  • Int? > Int
  • A? > A
  • Int < Int?

공변성, 반공변성, 무공변성

  • 읽기 전용 List 인터페이스는 공변적이다. 따라서 List<String>List<Any>의 하위 타입이다.
  • 함수 인터페이스에서 함수 타입은 함수 파라미터 타입에 대해서는 반공변적이며 함수 반환 타입에 대해서는 공변적이다.
  • MutableList는 읽기 쓰기가 가능하기 때문에 무공변적이다.

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

  • 타입 인자 정보가 없음 or 타입 인자 정보가 중요하지 않을 때를 표현
  • MutableList<Any?> != MutableList<*>
    • MutableList<Any?> : 모든 타입의 원소 담기 가능
    • MutableList<*> : 어떤 정해진 구체적인 타입의 원소를 담는 리스트, 정확한 원소 타입 모름
profile
백엔드 개발자

0개의 댓글