타입 별칭
타입 별칭 (Type aliases)
타입 별칭(type alias)은 기존 타입에 대체 이름을 붙여 줍니다. 길거나 자주 쓰는 타입 표현식을 더 짧고 이해하기 쉽게 만들어 주죠. 예를 들어 제네릭 타입, 함수 타입, 중첩·내부 클래스에 대해 별칭을 만들 수 있어요.
// 제네릭 타입
typealias UserIndex = Map<Long, User>
typealias FileTable<K> = MutableMap<K, MutableList<File>>
// 함수 타입
typealias RequestHandler = (Request) -> Response
typealias Predicate<T> = (T) -> Boolean
// 내부 및 중첩 클래스
class Database {
inner class Transaction
}
typealias DatabaseTransaction = Database.Transaction
출처: Kotlin 공식 문서
본문
타입 별칭은 새로운 타입을 만드는 게 아니라, 기존 타입에 다른 이름을 도입하는 것뿐이에요. 그래서 별칭과 그 근본 타입(underlying type)은 서로 언제든 바꿔 쓸 수 있습니다. 예를 들어 typealias Predicate<T>를 선언하고 Predicate<Int>를 쓰면, 컴파일러는 그것을 (Int) -> Boolean로 펼쳐요(expand). 별칭으로 선언한 값은 근본 타입이 기대되는 자리 어디서든 쓸 수 있고, 그 반대도 마찬가지예요.
typealias Predicate<T> = (T) -> Boolean
fun evaluate(predicate: Predicate<Int>) = predicate(42)
fun main() {
val isPositive: (Int) -> Boolean = { it > 0 }
println(evaluate(isPositive))
// true
val isValid: Predicate<Int> = { it > 0 }
println(listOf(1, -2).filter(isValid))
// [1]
}
타입 별칭 선언하기
타입 별칭은 두 곳에서 선언할 수 있어요.
함수 안이나 람다 표현식 같은 지역 스코프에서는 타입 별칭을 선언할 수 없어요.
별칭의 선언 위치가 그 스코프를 결정하고, 가시성이 어떤 코드가 접근할 수 있는지를 결정합니다. 기본적으로 타입 별칭은 public이에요. 중첩 타입 별칭은 그 포함 클래스·인터페이스·객체에 접근할 수 있는 곳에서만 접근 가능합니다. 예를 들어 internal 클래스 안의 public 별칭은 모듈 밖에서 접근할 수 없죠.
타입 별칭은 자신보다 더 제한적인 가시성의 근본 타입을 노출할 수 없어요. 예를 들어 public 타입 별칭은 private 클래스를 가리킬 수 없습니다.
최상위 타입 별칭
최상위 타입 별칭은 패키지 수준의 선언이에요. 같은 패키지 안에서는 정규화되지 않은 이름(unqualified name)으로 별칭을 참조할 수 있고, 다른 패키지에서 쓰려면 별칭을 import하거나 정규화된 이름으로 참조합니다.
// UserId.kt
package org.example.users
typealias UserId = Long
// 같은 패키지 안에서 정규화되지 않은 이름으로 별칭을 참조한다
fun createUser(id: UserId) {
// ...
}
// UserService.kt
package org.example.services
import org.example.users.UserId
// import한 별칭을 정규화되지 않은 이름으로 사용한다
fun findUser(id: UserId) {
// ...
}
// 완전히 정규화된 이름을 사용한다
fun deleteUser(id: org.example.users.UserId) {
// ...
}
중첩 타입 별칭
중첩 타입 별칭은 캡슐화를 개선하고, 패키지 수준의 어수선함을 줄이며, 내부 구현을 단순화해서 더 깔끔하고 유지보수하기 쉬운 코드를 만들어 줍니다. 중첩 타입 별칭은 중첩 클래스와 같은 스코프·이름 해석 규칙을 따릅니다.
대체 이름이 특정 선언의 맥락에서만 의미가 있을 때, 그 클래스·인터페이스·객체 안에 타입 별칭을 선언해 보세요. 그러면 별칭이 그것을 쓰는 코드 가까이 유지되고, 패키지 스코프에 이름이 하나 더 늘어나는 걸 피할 수 있어요.
포함하는 선언 안에서는 별칭을 정규화되지 않은 이름으로 참조할 수 있고, 선언 밖에서는 포함하는 선언의 이름으로 정규화해 참조합니다.
class UserRepository {
typealias UserIndex = Map<UserId, User>
// UserRepository 안에서 정규화되지 않은 이름으로 별칭을 참조한다
fun saveAll(users: UserIndex) {
// ...
}
}
// UserRepository 밖에서 정규화된 이름으로 별칭을 참조한다
fun synchronizeUsers(users: UserRepository.UserIndex) {
// ...
}
중첩 타입 별칭은 Kotlin Multiplatform의
expect/actual선언에서는 지원되지 않아요.
타입 파라미터
중첩 타입 별칭에서 타입 파라미터를 쓰려면, 별칭 선언에 그것을 추가하면 됩니다.
class Graph<Node> {
typealias Path<T> = List<T>
}
val cityPath: Graph.Path<String> = listOf("London", "Berlin")
이 예시에서 Path는 자신의 타입 파라미터 T를 선언합니다. Graph.Path<String>에서 String은 T에 대한 타입 인자이며, Graph가 선언한 Node 타입 파라미터와는 독립적이에요.
포함하는 클래스·인터페이스가 선언한 타입 파라미터를 별칭 안에서 참조하면 컴파일러가 오류를 보고합니다.
class Graph<Node> {
typealias Path = List<Node>
// Unresolved reference 'Node'.
}
여기서 Path는 자신의 타입 파라미터를 선언하는 대신 Graph의 Node를 참조하고 있어요.