kotlin in action 책을 통한 공부입니다.
1. 제네릭 타입 파라미터
1. 타입 파라미터 T
- <T> : 타입파라미터 선언
- 수신/반환<T> : 수신객체와 반환타입에 쓰인다.
- 타입 파라미터 X : 이 변수는 리스트다.
- 타입 파라미터 사용 : 이 변수는 문자열 리스트다 (명확)
→ 코틀린에서, 제네릭 타입 인자를
- 프로그래머가 명시하거나,
- 컴파일러가 추론할 수 있어야 한다.
fun <T> List<T>.slice(indices: IntRange) : List<T>
val letters = ('a'...'z').toList()
letters.slice<Char>(0..2) // 프로그래머가 명시
letters.slice(10..13) // 컴파일러가 타입 인자를 추론. T가 Char 라는 사실을 추론
- 확장 프로퍼티 선언도 가능하다.
val <T> List<T>.penultimate: T
get() = this[size - 2]
2. 제네릭 클래스 : 클래스 이름 뒤 T
interface List<T> {
operator fun get(index: Int): T // 인터페이스 안에서 T를 일반 타입처럼 사용할 수 있다.
}
- 클래스가 자기 자신을 타입 인자로 참조할 수도 있다.
interface Comparable<T> {
fun compareTo(other: T): Int
}
class String : Comparable<String> {
override fun compareTo(other: String): Int = ...
}
3. 타입 파라미터 제약
클래스나 함수에 사용할 수 있는 타입 인자를 제한한다.
- 상한 타입에 정의된 메서드를 호출할 수 있다.
// T(타입 파라미터)에 대한 상한(upper bound) 타입을 Number 로 지정한 것.
fun <T : Number> List<T>.sum() : T
// Int 타입은 Number를 확장함. sum() 함수 사용 가능
listOf(1,2,3).sum()
- 여러 제약 넣을 수 있다. where 제약목록
fun <T> ensureTrailingPeriod(seq: T) where T: CharSequence, T : Appendable {
...
}
4. 타입파라미터 Null이 될 수 없는 타입으로 한정 <T : Any>
- nullable
class Processor<T> {
fun process(value: T) {
value?.hashCode() // value 는 null이 될 수 있기에 안전한 호출을 사용해야 함
}
}
- not null
class Processor<T : Any> {
fun process(value: T) {
value.hashCode() // value 는 null 이 될 수 없다.
}
}
2. 타입파라미터의 소거, 실체화
결론
- 함수를 inline으로 선언하고,
- reified 타입 파라미터를 사용하면 타입 인자가 지워지지 않는다.
1. 타입소거의 장점, 한계
타입소거 : 실행시점에 제네릭 클래스의 인스턴스에 T의 타입인자정보가 들어있지 않다는 뜻
- 장점
- 저장해야 하는 정보의 크기가 줄어들어서 전반적인 메모리 사용량이 줄어든다
- 한계
- 실행 시점에 타입 인자를 검사 할 수 없다.
fun <T> isA(value: Any) = value is T // 오류 발생. // Error: Cannot check for instance of erased type: T
2. 실체화한 타입파라미터 reified
inline fun <reified T> isA(value: Any) = value is T
isA<String>("abc") // true
isA<String>(123) // false
3. 인라인 함수에서만 reified 타입 인자를 쓸 수 있는 이유
- 인라인 함수 : 바이트코드 호출, 구체적인 클래스를 참조하는 바이트코드 생성해 삽입함.
- 타입파라미터가 아니라 구체적인 타입을 사용하므로 타입소거의 영향을 받지 않는다.
성능을 좋게 하려면 인라인 함수의 크기를 계속 관찰해야 한다. 함수가 커지면 실체화한 타입에 의존하지 않는 부분을 별도의 일반 함수로 뽑아내는 편이 낫다.
inline fun <reified T> loadService() {
return ServiceLoader.load(T::class.java) // ::class 로 타입 파라미터의 클래스를 가져온다.
}
4. reified 제약
- 사용가능
- 타입 검사, 캐스팅 (is, as)
- 코틀린 리플렉션 API (::class)
- java.lang.Class 를 얻기 (::class.java)
- 다른 함수를 호출할 때 타입 인자로 사용
- 불가능
- 타입 파라미터 클래스의 인스턴스 생성하기
- 타입 파라미터 클래스의 동반 객체 메소드 호출하기
- 실체화한 타입 파라미터를 요구하는 함수를 호출하면서 실체화하지 않은 타입 파라미터로 받은 타입을 타입 인자로 넘기기
- 클래스, 프로퍼티, 인라인 함수가 아닌 함수의 타입 파라미터를 reified로 지정
3. 제네릭과 하위타입
변성 : List<String>, List<Any>와 같이
- 기저타입이 같고
- 타입인자가 다른 여러 타입이
서로 어떤 관계가 있는지 설명하는 개념
1. 타입인자가 다른 경우를 인자로 넘기지 말자.
타입인자가 다른 경우 안전하지 않다. 호출금지
- MutableList<Any>가 필요한 곳에 MutableList<String>을 넘기면 안 된다
val strings = mutableListOf("abc", "bac")
addAnswer(strings) // list.add(42)
ClassCastException: Integer cannot be cast to String // 예외 발생
2. 하위타입, 공변성(하위타입 관계유지) out
공변적 : A가 B의 하위 타입일 때 Producer<A>가 Producer<B>의 하위 타입이면 Peoducer는 공변적이다.
- 공변적으로 만들면 함수 정의에 사용한 파라미터 타입과 타입 인자의 타입이 정확히 일치하지 않더라도 그 클래스의 인스턴스를 함수 인자나 반환값으로 사용할 수 있다.
interface Producer<out T> { // 클래스가 T에 대해 공변적이라고 선언한다.
fun produce(): T
}
class Herd<out T : Animal> { ... } // out을 해주지 않으면, feedAll에서 타입 불일치.
class Cat : Animal() { ... } // Cat은 Animal의 하위타입
fun takeCareOfCats(cats: Herd<Cat>) {
for (i in 0 until cats.size) {
cats[i].cleanLitter()
}
feedAll(cats) // Herd<Cat> -> Herd<Animal>
}
fun feedAll(animals: Herd<Animal>) {
for (i in 0 until animals.size) {
animals[i].feed()
}
}
3. 반공변성 : 뒤집힌 하위타입관계 in
무공변 공변성 반공변성
MutableList<T> | Producer<out T> | Consumer<in T> |
하위 타입 관계가 성립 X | 타입 인자의 하위 타입 관계가 제네릭 타입에서도 유지된다. | 타입 인자의 하위 타입 관계가 제네릭 타입에서 뒤집힌다. |
Producer<Cat>은 Producer<Animal>의 하위 타입 | Consumer<Animal>은 Consumer<Cat>의 하위 타입 | |
T를 아무 위치에서나 사용할 수 있다. | T를 out 위치에서만 사용 가능 | T를 in위치에서만 사용 가능 |
- 예시
interface Functional<In P, out R> {
operator fun invoke(p: P) : R
}
4. 스타 프로젝션 : 타입인자 대신 *
제네릭 타입 인자 정보가 없음을 표현하기 위해 스타 프로젝션(*)을 사용
- 원소 타입이 알려지지 않은 리스트 : List<*>
- MutableList<*> ≠ MutableList<Any?>
- • MutableList<*> : 어떤 정해진 구체적인 타입의 원소만을 담는 리스트지만, 그 원소의 타입을 정확히 모른다
- • MutableList<Any?> : 모든 타입의 원소를 담을 수 있다
'공부 > Kotlin' 카테고리의 다른 글
11장 DSL 만들기 (0) | 2022.07.18 |
---|---|
10장 애노테이션과 리플렉션 (0) | 2022.07.18 |
[Kotlin] 파라미터와 반환 값으로 람다 사용해보기 (0) | 2022.07.02 |
[Kotlin] 연산자 오버로딩과 기타 관례 사용해보기 (0) | 2022.06.20 |
[Kotlin] 코틀린 타입 시스템 사용해보기 (0) | 2022.06.20 |
댓글