이 포스팅은 <Kotlin in Action>, 드미트리 제메로프 & 스베트라나 이사코바, 에이콘출판사(2017)을 읽고 개인 학습용으로 정리한 글입니다.
제네릭스를 사용하면 타입 파라미터를 받는 타입 정의 가능
제네릭 타입의 인스턴스를 만들려면 타입 파라미터를 구체적인 타입 인자로 치환해야
제네릭 타입의 타입 인자 컴파일러 추론 가능
-> 타입 인자 추론 근거 없는 경우(빈 List) 직접 타입 인자 명시해야
리스트를 만들 때
-> 변수 타입 지정 가능
-> 변수를 만드는 함수의 타입 지정 가능
val list1 : MutableList<String> = mutableListOf()
val list2 = mutableListOf<String>()
fun <T> List<T>.slice(indices: IntRange):List<T>
//리스트의 마지막 원소 바로 앞에 있는 원소 반환
val <T> List<T>.penultimate: T
get() = this[size-2]
타입 파라미터 제약(type parameter constraint)
어떤 타입을 제네릭 타입의 파라미터에 대한 상한(upper bound)로 지정
-> 타입 인자는 반드시 그 상한 타입이거나 그 상한 타입의 하위 타입
fun <T:Number> oneHalf(value:T):Double{
return value.toDouble() / 2.0
}
fun <T> ensureTrailingPeriod(seq:T) where T : CharSequence, T:Appendable{
if(!seq.endsWith('.')){
seq.append('.'
}
}
val list1: List<String> = listOf("a", "b", "c")
val list2: List<Int> = listOf(1, 2, 3)
컴파일러는 두 리스트를 서로 다른 타입으로 인식
실행 시점에서는 두 리스트 서로 다른 타입으로 인식 X
-> 오직 List 객체로만 봄, List 객체가 어떤 타입의 원소 저장하는지 알 수 X
실행 시점에서 is로 타입 인자로 지정한 타입 검사 -> 컴파일 오류
실행 시점에서 as/as?로 타입 캐스팅 -> 성공하지만 경고(unchecked cast)
인자를 알 수 없는 제네릭 타입 표현 -> 스타 프로젝션 사용
//컴파일 오류
if(value is List<String>){...}
//스타 프로젝션 사용
if(value is List<*>){...}
인라인 함수의 파라미터는 실체화됨
-> 함수 inline으로 바꾸고 파라미터 reified로 지정
-> 실행 시간에 인라인 함수의 타입 인자 알 수 O
컴파일러 인라인 함수의 본문을 함수가 호출되는 모든 지점에 삽입할 때
-> 실체화한 인자 타입을 이용해 타입 인자로 쓰인 구체적인 클래스를 참조하는 바이트 코드 생성하여 삽입
-> 실행 시점에 벌어지는 타입 소거 영향 X
실체화된 파라미터를 사용하는 인라인 함수
-> 자신에게 전달되는 모든 람다와 함께 인라이닝 됨
제네릭 클래스가 아닌 클래스: 클래스 이름 바로 타입으로 사용 가능
-> 널이 될 수 있는 타입, 널이 될 수 없는 타입 구성 가능
제네릭 클래스: 제네릭 타입의 타입 파라미터를 구체적인 타입 인자로 바꿔줘야
-> ex. List<Int>,List<String?>, ...
어떤 타입 A의 값이 필요한 모든 장소에 어떤 타입 B의 값을 넣어도 아무런 문제가 없을 때
-> 타입 B는 타입 A의 하위 타입
간단한 경우 하위 타입은 하위 클래스와 근본적으로 같다
-> ex. Int는 Number의 하위 클래스 -> Int는 Number의 하위 타입
널이 될 수 없는 타입, 널이 될 수 있는 타입
-> 두 타입의 클래스 같음
-> 널이 될 수 없는 타입은 널이 될 수 있는 타입의 하위 타입
제네릭 타입을 인스턴스화할 때 타입 인자로 서로 다른 타입이 들어갔을 때 인스턴스 타입 사이의 하위 타입 관계 성립하지 않을 때
-> 무공변
-> T 아무 위치(in/out)에서나 사용 가능
A가 B의 하위 타입일 때 Producer<A>가 Producer<B>의 하위 타입일 때
-> 공변적
제네릭 클래스가 타입 파라미터에 대해 공변적임을 표시하려면
-> 타입 파라미터 이름 앞에 out
클래스 멤버를 선언할 때 타입 파라미터를 사용할 수 있는 지점: in, out
-> in 위치: 함수 파라미터 타입
-> out 위치: 함수 반환 타입
클래스 타입 파라미터 앞에 out 키워드를 붙이면
-> 클래스 안에서 T를 사용하는 메서드가 out 위치에서만 T 사용 허용
⭐out 키워드:
타입 파라미터를 함수 파라미터 타입/함수 반환 타입으로만 사용 가능한 건 X
-> 타입 파라미터를 다른 타입의 타입 인자로 사용 가능
MutableList<T>를 타입 T에 대해 공변적인 클래스로 선언할 수 X
-> T를 인자로 받아서 그 타입의 값을 반환하는 메서드 존재
-> T in, out 위치 동시에 쓰임
타입 파라미터가 out이라 해도 생성자 파라미터 선언에 사용 가능
val/var 키워드를 생성자 파라미터에 적는다면 게터/세터를 정의하는 것과 같음
-> 읽기 전용 프로퍼티: 게터(out 위치) 정의됨
-> 변경 가능 프로퍼티: 게터(out 위치), 세터(in 위치) 정의됨 -> out으로 표시 불가능
이런 in/out 위치 규칙은 외부에서 볼 수 있는 클래스(public, protected, internal) API에만 적용
-> private 메서드의 파라미터는 in도 아니고 out도 아니다
타입 B가 타입 A의 하위 타입인 경우 Consumer<A>가 Consumer<B>의 하위 타입일 때
-> 반공변성
-> 하위 타입 관계 뒤집힘
ex. 어떤 타입의 객체를 Comparator로 비교해야할 때
-> 그 타입이나 그 타입의 조상을 비교할 수 있는 Comparator 사용 가능
-> Comparator<Any>가 Comparator<String>의 하위 타입
제네릭 클래스가 타입 파라미터에 대해 반공변적임을 표시하려면
-> 타입 파라미터 이름 앞에 in
⭐in 키워드:
interface Function1<in P, out R>{
operator fun invoke(p:P):R
}
타입 파라미터가 있는 타입을 사용할 때마다 해당 타입 파라미터를 하위 타입이나 상위 타입 중 어떤 타입으로 대치할 수 있는지 명시
-> 사용 지점 변성(use-site variance)
-> 함수 정의 시 파라미터에 변성 변경자 추가
이때 타입 프로젝션이 일어난다
제네릭 타입 인자 정보 없음을 표현하기 위해 사용
타입 파라미터를 시그니처에 전혀 언급하지 않거나 데이터를 읽기는 하지만 그 타입에 관심이 없는 경우에도 사용
MutableList<>는 MutableList<Any?>와 같지 않다
-> MutableList<Any?>: 모든 타입의 원소를 담을 수 있음
-> MutableList<>: 어떤 정해진 구체적인 타입의 원소만 담을 수 있음, 그 원소의 타입을 정확히 모름
컴파일러는 MutableList<>를 MutableList<Any?>의 아웃 프로젝션 타입으로 인식
-> 어떤 리스트의 원소 타입을 모르면 그 리스트에 원소를 넣을 수 X
-> 어떤 리스트의 원소 타입을 모르더라도 그 리스트에서 Any?타입의 원소 꺼내올 수 O
- A type projection is a type that has been limited in certain ways in order to gain variance characteristics.
- When you want subtyping to apply to your generic types, Kotlin gives you two options:
- Declaration-site variance - you can specify the variance within the definition of the class or interface.
- Use-site variance - you can specify the variance at the places in your code where you’re using an instance of a generic.
- Type projection is the result of use-site variance.
- When you apply use-site variance to an object, that object’s type is projected to a more limited view of the type.