일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 |
- android
- LRU
- 쌓기게임
- DynamicProgramming
- 제곱근
- stack
- 코인거스름돈
- synergy
- 형변환
- 동적프로그래밍
- Java
- MFC
- Dokka
- DataStructure
- 보늬밤
- Collection
- WebView
- Dialog
- memory
- FirebaseAuth
- devicedriver
- AfxMessageBox
- SPI
- 피보나치
- math
- 리틀포레스트
- 피요모리2
- Kotlin
- darkmode
- QoS
- Today
- Total
퉁탕퉁탕 만들어보자
Kotlin Generic (in, out) 본문
Kotlin의 Generic은 기본적으로 Java와 동일한 방식으로 사용가능합니다.
class Box<T>(t: T) {
var value = t
}
val box: Box<Int> = Box<Int>(1)
val box = Box(1)
명시적으로 타입을 써줄수도 있지만, 추론이 가능한경우에는 그냥 값을 넣어줄 수도 있습니다.
Java의 WildCard type
Java type 시스템에는 와일드카드 type이 있습니다.
- <? extends T>
- T 또는 를 상속받는 type만 가능.
- T로 읽기(리턴타입)가능. T로 인수(argument) 전달 불가능
- <? super T>
- T 또는 T의 super type만 가능.
- T로 읽기(리턴타입) 불가능. T로 인수(argument)전달 가능.
T를 매개변수로 사용하는 메서드가 없고 T를 반환하는 메서드만 있는 일반 인터페이스 Source<T>가 있다고 가정해 보겠습니다.
// Java
interface Source<T> {
T nextT();
}
그런 다음 Source<Object> 유형의 변수에 Source<String> 인스턴스에 대한 참조를 저장하는 것은 완벽하게 안전합니다. 호출할 소비자 메서드가 없습니다. 그러나 Java는 이것을 알지 못하며 여전히 금지합니다.
// Java
void demo(Source<String> strs) {
Source<Object> objects = strs; // !!! Not allowed in Java
// ...
}
이 문제를 해결하려면 Source<? extends Object>를 선언해야 합니다. 이렇게 하는 것은 의미가 없습니다. 이전과 같은 변수에 대해 동일한 메서드를 모두 호출할 수 있으므로 더 복잡한 유형에 의해 추가되는 값이 없기 때문입니다. 그러나 컴파일러는 그것을 모릅니다.
Kotlin에는 이런 종류의 것을 컴파일러에게 설명하는 방법이 있습니다. 이를 declaration-site variance라고 합니다. Source<T>의 멤버에서만 반환(생성)되고 소비되지 않도록 Source의 형식 매개 변수 T에 주석을 추가할 수 있습니다. 이렇게 하려면 out 을 사용합니다.
// KOTLIN
interface Source<out T> {
fun nextT(): T
}
fun demo(strs: Source<String>) {
val objects: Source<Any> = strs // This is OK, since T is an out-parameter
// ...
}
클래스 C의 type 매개변수 T가 out으로 선언되면 C 멤버의 out-position에서만 발생할 수 있지만 그 대가로 C<Base>는 C<Derived>의 안전하게 상위 유형이 될 수 있습니다.
C는 T의 소비자가 아니라 T의 생산자라고 생각할 수 있습니다.
out 외에도 Kotlin은 in 도 제공합니다. 소비만 가능하고 생산은 절대 불가능합니다. 좋은 예는 Comparable입니다.
interface Comparable<in T> {
operator fun compareTo(other: T): Int
}
fun demo(x: Comparable<Number>) {
// 1.0 은 double 타입임, Number의 subtype임. 하위 타입으로 인수 받는것이 가능
x.compareTo(1.0)
// 따라서 x(Comparable<Number>) 상위타입의 객체를 y(Comparable<Double>) 하위타입에 assign이 가능하다.
val y: Comparable<Double> = x // OK!
}
Type projections
use-site-variance: type 예측
유형 매개변수 T를 out으로 선언하고 사용하는 곳에서 하위타입 입력 문제를 피하는 것은 매우 쉽지만 일부 클래스는 실제로 T만 반환하도록 제한할 수 없습니다! 이에 대한 좋은 예는 Array입니다.
class Array<T>(val size: Int) {
operator fun get(index: Int): T { ... }
operator fun set(index: Int, value: T) { ... }
}
fun copy(from: Array<Any>, to: Array<Any>) {
assert(from.size == to.size)
for (i in from.indices)
to[i] = from[i]
}
이 함수는 한 배열에서 다른 배열로 항목을 복사해야 합니다. 실제로 적용해 보겠습니다.
val ints: Array<Int> = arrayOf(1, 2, 3)
val any = Array<Any>(3) { "" }
copy(ints, any)
// ^ type is Array<Int> but Array<Any> was expected
여기에서 친숙한 문제가 발생합니다. Array<T>는 T에서 불변이므로 Array<Int>나 Array<Any> 모두 다른 쪽의 하위 유형이 아닙니다. 왜 안 돼? 왜냐하면 copy가 예기치 않은 동작을 가질 수 있기 때문입니다. 예를 들어, 문자열을 from에 쓰고 실제로 Int 배열을 전달하면 나중에 ClassCastException이 발생합니다.
복사 기능이 from에 쓰는 것을 금지하려면 다음을 수행할 수 있습니다.
fun copy(from: Array<out Any>, to: Array<Any>) { ... }
이것은 type projection이며, 이는 from이 단순한 배열이 아니라 제한된(투영된) 배열임을 의미합니다. 형식 매개변수 T를 반환하는 메서드만 호출할 수 있습니다. 이 경우 get()만 호출할 수 있습니다. 이것은 use-site 편차에 대한 우리의 접근 방식이며 Java의 Array<? extends Object> 는 약간 더 단순합니다.
in을 사용하여 유형을 투영할 수도 있습니다.
fun fill(dest: Array<in String>, value: String) { ... }
Array<in String>은 Java의 Array<? super String>에 대응됩니다. 이것은 CharSequence 배열 또는 Object 배열을 fill() 함수에 전달할 수 있음을 의미합니다.
Star-projections(*)
Kotlin은 스타 프로젝션 구문을 제공합니다.
Foo<out T : TUpper>의 경우 T는 상한 TUpper가 있는 공변 유형 매개변수이며 Foo<*>는 Foo<out TUpper>와 동일합니다. 이것은 T를 알 수 없을 때 Foo<*>에서 TUpper의 값을 안전하게 읽을 수 있음을 의미합니다.
Foo<in T>의 경우 T가 반공변 유형 매개변수인 경우 Foo<*>는 Foo<in Nothing>과 동일합니다. 이것은 T를 알 수 없을 때 안전한 방법으로 Foo<*>에 쓸 수 있는 것이 없음을 의미합니다.
Foo<T : TUpper>의 경우, T는 상한 TUpper가 있는 불변 유형 매개변수이며, Foo<*>는 값 읽기의 경우 Foo<out TUpper> 및 값 쓰기의 경우 Foo<in Nothing>과 동일합니다.
제네릭 형식에 여러 형식 매개 변수가 있는 경우 각 매개 변수를 독립적으로 프로젝션할 수 있습니다. 예를 들어 유형이 인터페이스 Function<in T, out U>로 선언된 경우 다음 스타 프로젝션을 사용할 수 있습니다.
- Function<*, String>은 Function<in Nothing, String>을 의미합니다.
- Function<Int, *>는 Function<Int, out Any?>를 의미합니다.
- Function<*, *>은 Function<in nothing, out Any?>를 의미합니다.
Star-projections는 Java의 원시 유형과 매우 유사하지만 안전합니다.
Generic functions
클래스는 형식 매개변수를 가질 수 있는 유일한 선언이 아닙니다. function도 가능합니다. 유형 매개변수는 함수 이름 앞에 배치됩니다.
fun <T> singletonList(item: T): List<T> {
// ...
}
fun <T> T.basicToString(): String { // extension function
// ...
}
일반 함수를 호출하려면 호출하는 곳에서 함수 이름 뒤에 형식 인수를 지정합니다.
val l = singletonList<Int>(1)
컨텍스트에서 유추할 수 있는 경우 형식 인수를 생략할 수 있으므로 다음 예제도 작동합니다.
val l = singletonList(1)
Generic constraints
주어진 type 매개변수를 대체할 수 있는 모든 가능한 type 집합은 일반 제약 조건에 의해 제한될 수 있습니다.
상한
가장 일반적인 유형의 제약 조건은 Java의 extends 키워드에 해당하는 상한입니다.
fun <T : Comparable<T>> sort(list: List<T>) { ... }
콜론 뒤에 지정된 형식은 상한으로, Comparable<T>의 하위 형식만 T를 대체할 수 있음을 나타냅니다. 예를 들면 다음과 같습니다.
sort(listOf(1, 2, 3)) // OK. Int is a subtype of Comparable<Int>
sort(listOf(HashMap<Int, String>())) // Error: HashMap<Int, String> is not a subtype of Comparable<HashMap<Int, String>>
기본 상한(아무 것도 지정되지 않은 경우)은 Any?입니다. 꺾쇠 괄호 안에는 하나의 상한만 지정할 수 있습니다. 동일한 유형 매개변수에 둘 이상의 상한이 필요한 경우 별도의 where 절이 필요합니다.
fun <T> copyWhenGreater(list: List<T>, threshold: T): List<String>
where T : CharSequence,
T : Comparable<T> {
return list.filter { it > threshold }.map { it.toString() }
}
전달된 형식은 where 절의 모든 조건을 동시에 충족해야 합니다. 위의 예에서 T 유형은 CharSequence와 Comparable을 모두 구현해야 합니다.
'Computer > Kotlin' 카테고리의 다른 글
Dokka 에서 SourceSet 설정하기 (0) | 2023.05.14 |
---|---|
Multi Module 프로젝트에서 Dokka 적용하기 (0) | 2023.05.13 |
Sealed class (0) | 2022.04.17 |
Data class (0) | 2022.04.17 |
loops (0) | 2022.03.31 |