타입 시스템 고급 — 스마트 캐스트, typealias, Nothing
스마트 캐스트 (Smart Cast)
is 검사 후 컴파일러가 자동으로 타입을 캐스팅합니다.
fun describe(obj: Any) {
if (obj is String) {
println(obj.length) // String으로 자동 캐스트
println(obj.uppercase())
}
when (obj) {
is Int -> println("정수: ${obj + 1}") // Int로 캐스트
is String -> println("문자열: ${obj.trim()}") // String으로 캐스트
is List<*> -> println("리스트: ${obj.size}")
else -> println("알 수 없음: $obj")
}
}&&, || 에서도 동작
fun process(obj: Any?) {
if (obj is String && obj.length > 5) { // && 이후 String으로 캐스트
println(obj.uppercase())
}
if (obj !is String || obj.length > 5) { // || 이후 String으로 캐스트 (반대)
return
}
println(obj.lowercase()) // 여기서 String
}스마트 캐스트 불가 케이스
class Example {
var value: Any = "hello" // var — 다른 스레드가 변경 가능
fun process() {
if (value is String) {
// println(value.length) // 컴파일 에러 — var는 스마트 캐스트 불가
val v = value // val로 복사 후 사용
if (v is String) println(v.length) // OK
}
}
}스마트 캐스트 가능 조건:
val로컬 변수val프로퍼티 (커스텀 getter 없는)lateinit val
K2 컴파일러 개선 (Kotlin 2.0)
// Kotlin 2.0 이전 — 람다 내 스마트 캐스트 불가
fun process(obj: Any, condition: Boolean) {
if (obj is String) {
if (condition) {
println(obj.length) // 2.0 이전: 에러, 2.0 이후: OK
}
}
}
// 멤버 함수 내 흐름 분석 개선
class Container(val value: Any) {
fun validate() = value is String
fun process() {
if (validate()) {
// 2.0: value가 String임을 추론 (멤버 함수 흐름 분석)
}
}
}as / as? — 명시적 캐스트
val obj: Any = "hello"
// as — 실패 시 ClassCastException
val str: String = obj as String
// as? — 실패 시 null
val str2: String? = obj as? String
val num: Int? = obj as? Int // null — String을 Int로 캐스트 불가typealias — 타입 별칭
복잡한 타입에 의미 있는 이름을 부여합니다.
// 함수 타입 별칭
typealias Predicate<T> = (T) -> Boolean
typealias Transform<A, B> = (A) -> B
typealias EventHandler = (event: String, data: Any?) -> Unit
fun filter(list: List<Int>, predicate: Predicate<Int>): List<Int> =
list.filter(predicate)
// 복잡한 제네릭 타입
typealias UserMap = Map<String, List<User>>
typealias ResultList<T> = List<Result<T>>
// 멀티플랫폼 별칭
typealias OnClick = (View) -> Unit// 의미 있는 이름
typealias Milliseconds = Long
typealias Port = Int
typealias Hostname = String
fun connect(host: Hostname, port: Port, timeout: Milliseconds) { ... }
// connect("localhost", 8080, 5000) — 더 읽기 쉬움주의: typealias는 새 타입이 아닙니다. 타입 안전성이 필요하면 value class를 사용합니다.
typealias UserId = Long // UserId와 Long은 완전히 같은 타입 — 혼용 가능
@JvmInline value class UserId(val value: Long) // 다른 타입 — 컴파일러가 구분Nothing — 정상 반환 없음
Nothing은 절대 값을 반환하지 않는 함수의 반환 타입입니다. 모든 타입의 서브타입입니다.
fun fail(message: String): Nothing {
throw IllegalStateException(message)
}
fun infiniteLoop(): Nothing {
while (true) { }
}
// TODO — 미구현 마커, 호출 시 NotImplementedError
fun notImplemented(): String = TODO("아직 미구현")타입 추론에서의 역할
Nothing은 모든 타입의 서브타입이므로, 타입 추론에서 “아무 타입이나 될 수 있음”을 의미합니다.
// when 식의 else가 Nothing 반환 — 타입 추론에 영향
fun getLength(value: Any): Int = when (value) {
is String -> value.length
is List<*> -> value.size
else -> fail("지원하지 않는 타입: ${value::class}")
// fail()이 Nothing을 반환하므로 else 분기가 Int를 반환하지 않아도 됨
}
// Elvis 연산자와 조합
fun findUser(id: Long): User =
userMap[id] ?: throw NoSuchElementException("User $id not found")
// ?: 오른쪽이 Nothing → 전체 표현식은 User 타입sealed class의 Nothing
sealed class Result<out T> {
data class Success<T>(val data: T) : Result<T>()
// Nothing — 값이 없지만 Result<T> 어디든 사용 가능
data class Failure(val error: String) : Result<Nothing>()
data object Loading : Result<Nothing>()
}
val result: Result<String> = Result.Failure("오류") // Result<Nothing>을 Result<String>에 대입 가능교차 타입 — T & Any
Kotlin 1.7+. nullable 타입 파라미터를 비-nullable로 강제합니다.
// Java에서 온 T는 nullable 여부가 불명확 (플랫폼 타입)
fun <T> processNonNull(value: T & Any): T & Any {
// value는 반드시 non-null
return value
}
// 제네릭 경계 표현
fun <T : Any> nonNullable(value: T): T = value // T는 Any의 서브타입 — non-null
fun <T> nullable(value: T): T = value // T는 nullable 가능공변 반환 타입
오버라이드 시 반환 타입을 더 구체적으로 좁힐 수 있습니다.
open class Animal {
open fun create(): Animal = Animal()
}
class Dog : Animal() {
override fun create(): Dog = Dog() // Animal보다 구체적인 Dog 반환 — OK
}
val animal: Animal = Dog()
val created: Animal = animal.create() // 런타임엔 Dog타입 별칭 vs value class vs data class
// typealias — 새 타입 아님, 문서화 목적
typealias EmailString = String
// value class — 새 타입, 런타임 오버헤드 최소, 단일 값
@JvmInline value class Email(val value: String)
// data class — 새 타입, 여러 필드, equals/copy/toString
data class EmailAddress(val local: String, val domain: String)정리
- 스마트 캐스트:
is후 자동 캐스트,val에서만 완전히 동작, K2에서 개선 as: 강제 캐스트 (실패 시 예외) /as?: 안전 캐스트 (실패 시 null)typealias: 타입에 별명, 새 타입 아님 — 문서화/가독성 목적Nothing: 절대 반환 안 함, 모든 타입의 서브타입,throw/TODO/무한루프T & Any: nullable 파라미터를 non-null로 강제 (Kotlin 1.7+)