is and !is operators
오브젝트의 타입을 런타임에서 확인하려면 !is 를 사용하시오.
if (obj is String) {
print(obj.length)
}
if (obj !is String) { // !(obj is String)와 같은 결과
print("Not a String")
} else {
print(obj.length)
}
Smart casts
대부분의 경우 컴파일러가 is - 체크와 명시적 형변환을 추적하기 때문에 Kotlin에서 명시적 형변환 연산자를 사용할 필요가 없다. 변경할 수 없는 값의 경우 필요할 때 자동으로 (안전한) 형변환을 삽입한다.
fun demo(x: Any) {
if (x is String) {
print(x.length) // x는 String으로 자동 형변환 된다.
}
}
컴파일러는 위에서 나쁜 결과가 반환될 경우 안전하게 형변환 할 만큼 똑똑하다.
if (x !is String) return
print(x.length) // x는 String으로 자동적으로 형변환 된다.
또는 &&, || 을 우측에 둔다.
// `||`의 오른쪽에 있는 x는 자동으로 String으로 형변환 된다.
if (x !is String || x.length == 0) return
// `&&`의 오른쪽에 있는 x는 자동으로 String으로 형변환 된다.
if (x is String && x.length > 0) {
print(x.length) // x는 String으로 자동으로 형변환 된다.
}
이러한 스마트 캐스트는 when 표현식과 while 루프에서도 작동한다.
when (x) {
is Int -> print(x + 1)
is String -> print(x.length + 1)
is IntArray -> print(x.sum())
}
스마트 캐스트는 컴파일러가 검사와 사용 사이에 변수가 변경되지 않음을 보장할 수 있는 경우에만 작동한다. 보다 구체적으로, 스마트 캐스트는 다음 규칙에 따라 적용된다.
- val 지역 변수 : 항상 지역 위임 프로퍼티를 제외한다.
- val 프로퍼티 : 프로퍼티가 private 혹은 internal이거나 프로퍼티가 선언된 동일한 모듈에서 검사가 수행되는 경우. 스마트 캐스트는 사용자 지정 getter가 있는 프로퍼티 또는 open 프로퍼티에 적용할 수 없다.
- var 지역 변수 - 검사와 사용 사이에 변수가 수정되지 않은 경우 수정하는 람다에서 캡처되지 않으며 지역 위임 속성이 프로퍼티가 아닐 때.
- var 프로퍼티 - 변수가 다른 코드에 의해 언제든지 수정될 수 있기 때문에 절대 안된다.
"Unsafe" cast operator
일반적으로 캐스트 연산자는 캐스트가 불가능한 경우 예외를 throw한다. 따라서 안전하지 않다고 한다. Kotlin의 안전하지 않은 캐스트는 중위 연산자 as에 의해 수행된다.
val x: String = y as String
이 유형은 nullable이 아니므로 null을 String으로 캐스팅할 수 없다. y가 null이면 위의 코드에서 예외가 발생한다. 이러한 코드를 null 값에 맞게 수정하려면 캐스트의 오른쪽에 nullable 타입을 사용하시오.
val x: String? = y as String?
"Safe" (nullable) cast operator
예외를 피하려면 safe 캐스트 연산자 as? 를 사용한다. 실패시 null을 반환한다.
val x: String? = y as? String
as? null이 아닌 타입 String인 경우, 캐스트 결과는 nullable이다.
Type erasure and generic type checks
Kotlin은 컴파일 타입에 제네릭과 관련된 연산의 타입 안전성을 보장하는 반면, 런타임에 제네릭 타입의 인스턴스는 실제 타입 인수에 대한 정보를 보유하지 않는다. 예를 들어 List<Foo>는 List<*>로만 지워진다. 일반적으로 인스턴스가 런타임에 특정 타입 인수를 가진 제네릭 타입에 속하는지 확인할 방법이 없다.
이를 감안할 때 컴파일러는 is를 금지한다. - 유형 삭제로 인해 런타임에 수행할 수 없는 검사, 예를 들어 ints "is List<Int>" 또는 "list is T"(타입 파라미터) 같은 경우일 때
if (something is List<*>) {
something.forEach { println(it) } // 이 아이템들은 "Any?" 타입이다.
}
마찬가지로 인스턴스의 타입 인수가 이미 정적으로 확인된 경우(컴파일 시) is - 타입의 제네릭이 아닌 부분을 포함하는 검사 또는 캐스트. 이 경우 꺾쇠 괄호가 생략 된다.
fun handleStrings(list: List<String>) {
if (list is ArrayList) {
// `list`는 `ArrayList<String>`으로 스마트 캐스트 된다.
}
}
생략된 타입 인수가 있는 동일한 구문은 타입 인수를 고려하지 않는 캐스트에 사용할 수 있다. list as ArrayList.
구체화된 타입 파라미터가 있는 인라인 함수에는 각 호출 사이트에 인라인된 실제 타입 인수가 있다. 이렇게 하면 arg is T가 타입 파라미터를 확인할 수 있지만 arg가 제네릭 타입 자체의 인스턴스인 경우 해당 타입 인수는 여전히 지워진다.
inline fun <reified A, reified B> Pair<*, *>.asPairOf(): Pair<A, B>? {
if (first !is A || second !is B) return null
return first as A to second as B
}
val somePair: Pair<Any?, Any?> = "items" to listOf(1, 2, 3)
val stringToSomething = somePair.asPairOf<String, Any>()
val stringToInt = somePair.asPairOf<String, Int>()
val stringToList = somePair.asPairOf<String, List<*>>()
val stringToStringList = somePair.asPairOf<String, List<String>>() // 컴파일하지만 타입 안전성을 깨뜨린다!
fun main() {
println("stringToSomething = " + stringToSomething)
println("stringToInt = " + stringToInt)
println("stringToList = " + stringToList)
println("stringToStringList = " + stringToStringList)
//println(stringToStringList?.second?.forEach() {it.length}) // 리스트 아이템이 String이 아니기 때문에 ClassCastException이 발생한다.
}
Unchecked casts
위에서 말했듯이 타입 삭제는 런타임에 제네릭 타입 인스턴스의 실제 타입 인수를 확인하는 것을 불가능하게 만든다. 또한 코드의 제네릭 타입은 컴파일러가 타입 안전성을 보장할 만큼 충분히 밀접하지 않게 서로 연결될 수 있다.
그럼에도 불구하고 때때로 타입 안전성을 암시하는 대신에 높은 수준의 프로그램 로직을 가질 수 있다.
예를 들어
fun readDictionary(file: File): Map<String, *> = file.inputStream().use {
TODO("임의의 요소에 대한 문자열 매핑 읽기")
}
// 'Int'가 있는 맵을 해당 파일에 저장
val intsFile = File("ints.dictionary")
// 경고: 체크되지 않은 캐스트: `Map<String, *>` to `Map<String, Int>`
val intsDictionary: Map<String, Int> = readDictionary(intsFile) as Map<String, Int>
마지막 줄의 캐스트에 대한 경고가 나타난다. 컴파일러는 런타임에 이를 완전히 확인할 수 없으며 Map의 값이 Int임을 보장하지 않는다.
확인되지 않은 캐스트를 피하기 위해 프로그램 구조를 재설계할 수 있다. 위의 예에서 DictionaryReader<T> 및 DictionaryWriter<T> 인터페이스를 다양한 타입에 대한 type-safe 구현과 함께 사용할 수 있다. 확인되지 않은 캐스트를 호출 코드에서 구현 세부 정보로 이동하기 위해 합리적인 추상화를 도입할 수 있다. 일반 분산을 적절히 사용하는 것도 도움이 될 수 있다.
일반 함수의 경우 구체화된 타입 파라미터를 사용하면 arg 의 타입에 지워지는 자체 타입 인수가 없는 한 arg as T와 같은 캐스트가 확인된다.
확인되지 않은 캐스트 경고는 @Suppress("UNCHECKED_CAST") 로 발생하는 문 또는 선언에 어노테이션을 추가하여 없앨 수 있다.
inline fun <reified T> List<*>.asListOfType(): List<T>? =
if (all { it is T })
@Suppress("UNCHECKED_CAST")
this as List<T> else
null
On JVM: 배열 타입(Array<Foo>)은 요소의 지워진 타입에 대한 정보를 유지하고 배열 타입으로의 타입 캐스트는 부분적으로 확인된다. 엘리먼트 타입의 null 허용 여부 및 실제 타입 인수는 여전히 지워진다. 예를 들어 foo가 null 허용 여부에 관계없이 List<*>를 포함하는 배열이면 foo를 Array<List<String>?>로 캐스팅하면 성공한다.
원문
Type checks and casts | Kotlin
kotlinlang.org
'프로그래밍 > Android' 카테고리의 다른 글
[Kotlin] Returns and jumps (0) | 2021.09.05 |
---|---|
[Kotlin] Conditions and loops (조건문 및 반복문) (0) | 2021.09.05 |
[Kotlin] Basic types (기본 타입) (0) | 2021.08.29 |
[Kotlin] Coding conventions (0) | 2021.08.26 |
[Kotlin] Idioms (관용구) (0) | 2021.08.25 |