Kotlin 1.9.0의 새로운 기능(What's new in Kotlin 1.9.0)
Kotlin 1.9.0의 새로운 기능(What's new in Kotlin 1.9.0)
Kotlin 1.9.0이 나왔고, JVM용 K2 컴파일러가 이제 Beta 단계에 들어갔어요. 그 외 주요 변경점들도 함께 소개할게요.
본문
출시일: 2023년 7월 6일
- 새로운 Kotlin K2 컴파일러 업데이트
- enum 클래스 values 함수의 안정적인 대체
- 개방 범위(open-ended range)용
..<연산자 안정화 - 정규식 캡처 그룹을 이름으로 가져오는 새로운 공통 함수
- 상위 디렉터리를 만드는 새 경로 유틸리티
- Kotlin Multiplatform의 Gradle 설정 캐시 프리뷰
- Kotlin Multiplatform의 Android 타깃 지원 변경
- Kotlin/Native의 커스텀 메모리 할당기 프리뷰
- Kotlin/Native의 라이브러리 링키지(linkage)
- Kotlin/Wasm의 크기 관련 최적화
이 업데이트에 대한 짧은 개요는 다음 동영상에서도 확인할 수 있어요.
Kotlin의 릴리스 주기에 대한 자세한 내용은 Kotlin 릴리스 과정에서 확인할 수 있어요.
IDE 지원
1.9.0을 지원하는 Kotlin 플러그인은 다음 환경에서 사용할 수 있어요.
| IDE | 지원 버전 |
|---|---|
| IntelliJ IDEA | 2022.3.x, 2023.1.x |
| Android Studio | Giraffe (223), Hedgehog (231)* |
*Kotlin 1.9.0 플러그인은 다가오는 릴리스에서 Android Studio Giraffe (223)과 Hedgehog (231)에 포함될 예정이에요.
Kotlin 1.9.0 플러그인은 다가오는 릴리스에서 IntelliJ IDEA 2023.2에 포함될 예정이에요.
Kotlin 아티팩트와 의존성을 다운로드하려면 Gradle 설정이 Maven Central 저장소를 사용하도록 구성해 주세요.
새로운 Kotlin K2 컴파일러 업데이트
JetBrains의 Kotlin 팀은 K2 컴파일러를 계속 안정화하고 있고, 1.9.0 릴리스가 또 다른 발전을 소개해요. JVM용 K2 컴파일러가 이제 Beta 단계에 있어요.
이제 Kotlin/Native와 multiplatform 프로젝트에 대한 기본 지원도 생겼어요.
K2 컴파일러와 kapt 컴파일러 플러그인의 호환성
K2 컴파일러와 함께 kapt 플러그인을 프로젝트에서 사용할 수 있지만 몇 가지 제한이 있어요. languageVersion을 2.0으로 설정해도 kapt 컴파일러 플러그인은 여전히 기존 컴파일러를 사용해요.
languageVersion이 2.0으로 설정된 프로젝트에서 kapt 컴파일러 플러그인을 실행하면, kapt가 자동으로 1.9로 전환하고 특정 버전 호환성 검사를 비활성화해요. 이 동작은 다음 커맨드 인자를 포함하는 것과 같아요.
-Xskip-metadata-version-check-Xskip-prerelease-check-Xallow-unstable-dependencies
이 검사는 kapt 태스크에서만 배타적으로 비활성화돼요. 다른 모든 컴파일 태스크는 계속 새 K2 컴파일러를 사용해요.
K2 컴파일러와 함께 kapt를 사용할 때 문제가 생기면 이슈 트래커에 보고해 주세요.
프로젝트에서 K2 컴파일러 사용해 보기
1.9.0부터 Kotlin 2.0 릴리스까지, gradle.properties 파일에 kotlin.experimental.tryK2=true Gradle 프로퍼티를 추가하면 K2 컴파일러를 쉽게 테스트할 수 있어요. 다음 명령도 실행할 수 있어요.
./gradlew assemble -Pkotlin.experimental.tryK2=true
이 Gradle 프로퍼티는 언어 버전을 자동으로 2.0으로 설정하고, 현재 컴파일러 대비 K2 컴파일러로 컴파일된 Kotlin 태스크 수를 빌드 리포트로 업데이트해요.
##### 'kotlin.experimental.tryK2' results (Kotlin/Native not checked) #####
:lib:compileKotlin: 2.0 language version
:app:compileKotlin: 2.0 language version
##### 100% (2/2) tasks have been compiled with Kotlin 2.0 #####
Gradle 빌드 리포트
Gradle 빌드 리포트는 이제 코드를 컴파일할 때 현재 컴파일러를 사용했는지 K2 컴파일러를 사용했는지 보여줘요. Kotlin 1.9.0에서는 이 정보를 Gradle 빌드 스캔에서 볼 수 있어요.
프로젝트에서 사용된 Kotlin 버전은 빌드 리포트에서 바로 찾을 수도 있어요.
Task info:
Kotlin language version: 1.9
Gradle 8.0을 사용한다면, 특히 Gradle 설정 캐시가 활성화된 경우 빌드 리포트에 몇 가지 문제가 생길 수 있어요. 이는 알려진 문제이며 Gradle 8.1 이상에서 수정됐어요.
현재 K2 컴파일러의 제한 사항
Gradle 프로젝트에서 K2를 활성화하면 몇 가지 제한이 따르는데, 다음 경우에 Gradle 8.3 미만 버전을 사용하는 프로젝트에 영향을 줄 수 있어요.
buildSrc에서 소스 코드를 컴파일하는 경우.- 포함된 빌드에서 Gradle 플러그인을 컴파일하는 경우.
- Gradle 8.3 미만 버전을 사용하는 프로젝트에서 다른 Gradle 플러그인을 사용하는 경우.
- Gradle 플러그인 의존성을 빌드하는 경우.
위 문제 중 하나라도 겪는다면 다음 단계로 해결할 수 있어요.
buildSrc, 모든 Gradle 플러그인, 그리고 그 의존성에 언어 버전을 설정해요.
kotlin {
compilerOptions {
languageVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_1_9)
apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_1_9)
}
}
- 사용 가능해지면 프로젝트의 Gradle 버전을 8.3으로 업데이트해요.
새 K2 컴파일러에 대한 피드백 남기기
여러분의 피드백을 정말 환영해요!
- Kotlin's Slack의 K2 개발자에게 직접 피드백을 주세요 – 초대받기 후 #k2-early-adopters 채널에 참여하세요.
- 새 K2 컴파일러에서 겪은 문제는 이슈 트래커에 보고해 주세요.
- Send usage statistics 옵션을 켜서 JetBrains가 K2 사용에 관한 익명 데이터를 수집하도록 허용해 주세요.
언어
Kotlin 1.9.0에서 이전에 도입된 몇 가지 새 언어 기능을 안정화해요.
- enum 클래스 values 함수의 대체
- 데이터 클래스와 대칭을 이루는 데이터 객체
- 인라인 값 클래스에서 본문이 있는 보조 생성자 지원
enum 클래스 values 함수의 안정적인 대체
1.8.20에서 enum 클래스의 entries 프로퍼티가 Experimental 기능으로 도입됐어요. entries 프로퍼티는 합성 values() 함수의 현대적이고 성능 좋은 대체품이에요. 1.9.0에서 entries 프로퍼티가 Stable이 됐어요.
values() 함수는 계속 지원되지만, entries 프로퍼티를 사용할 것을 권장해요.
enum class Color(val colorName: String, val rgb: String) {
RED("Red", "#FF0000"),
ORANGE("Orange", "#FF7F00"),
YELLOW("Yellow", "#FFFF00")
}
fun findByRgb(rgb: String): Color? = Color.entries.find { it.rgb == rgb }
enum 클래스의 entries 프로퍼티에 대한 자세한 내용은 Kotlin 1.8.20의 새로운 기능을 참고하세요.
데이터 클래스와 대칭을 이루는 안정적인 데이터 객체
Kotlin 1.8.20에서 도입된 데이터 객체 선언이 이제 Stable이 됐어요. 여기에는 데이터 클래스와 대칭을 이루기 위해 추가된 toString(), equals(), hashCode() 함수가 포함돼요.
이 기능은 특히 sealed 계층(예: sealed class 또는 sealed interface 계층)에서 유용해요. data object 선언을 data class 선언과 함께 편리하게 사용할 수 있거든요. 이 예에서 EndOfFile을 일반 object 대신 data object로 선언하면 수동으로 재정의하지 않아도 자동으로 toString() 함수를 갖게 돼요. 이는 함께 쓰이는 데이터 클래스 정의와 대칭을 유지해줘요.
sealed interface ReadResult
data class Number(val number: Int) : ReadResult
data class Text(val text: String) : ReadResult
data object EndOfFile : ReadResult
fun main() {
println(Number(7)) // Number(number=7)
println(EndOfFile) // EndOfFile
}
자세한 내용은 Kotlin 1.8.20의 새로운 기능을 참고하세요.
인라인 값 클래스에서 본문이 있는 보조 생성자 지원
Kotlin 1.9.0부터 인라인 값 클래스에서 본문이 있는 보조 생성자 사용이 기본으로 가능해요.
@JvmInline
value class Person(private val fullName: String) {
// Allowed since Kotlin 1.4.30:
init {
check(fullName.isNotBlank()) {
"Full name shouldn't be empty"
}
}
// Allowed by default since Kotlin 1.9.0:
constructor(name: String, lastName: String) : this("$name $lastName") {
check(lastName.isNotBlank()) {
"Last name shouldn't be empty"
}
}
}
이전에는 Kotlin이 인라인 클래스에서 public 기본 생성자만 허용했어요. 그 결과 기본값을 캡슐화하거나 특정 제약이 있는 값을 나타내는 인라인 클래스를 만들 수 없었어요.
Kotlin이 발전하면서 이런 문제가 해결됐어요. Kotlin 1.4.30이 init 블록 제한을 완화했고, Kotlin 1.8.20이 본문이 있는 보조 생성자 프리뷰를 가져왔어요. 이제 기본으로 사용 가능해졌어요. Kotlin 인라인 클래스의 개발에 대해 더 자세히 알고 싶다면 이 KEEP을 참고하세요.
Kotlin/JVM
1.9.0부터 컴파일러가 JVM 20에 해당하는 바이트코드 버전의 클래스를 생성할 수 있어요. 또한 JvmDefault 애너테이션과 레거시 -Xjvm-default 모드의 deprecation이 계속돼요.
JvmDefault 애너테이션과 레거시 -Xjvm-default 모드 deprecation
Kotlin 1.5부터 JvmDefault 애너테이션 사용은 더 새롭고 나은 -Xjvm-default 모드인 all과 all-compatibility를 선호하는 방향으로 deprecated됐어요. Kotlin 1.4의 JvmDefaultWithoutCompatibility와 Kotlin 1.6의 JvmDefaultWithCompatibility 도입으로 이 모드들은 DefaultImpls 클래스 생성에 대한 포괄적인 제어를 제공하며, 이전 Kotlin 코드와의 원활한 호환성을 보장해요.
결과적으로 Kotlin 1.9.0에서 JvmDefault 애너테이션은 더 이상 의미가 없어졌고 deprecated로 표시되어 오류가 발생해요. 결국 Kotlin에서 제거될 예정이에요.
Kotlin/Native
이 릴리스는 다른 개선들과 함께 Kotlin/Native 메모리 매니저의 견고성과 성능을 높여줄 추가 발전을 가져와요.
- 커스텀 메모리 할당기 프리뷰
- 메인 스레드에서의 Objective-C 또는 Swift 객체 해제(deallocation) 훅
- Kotlin/Native에서 상수 값 접근 시 객체 초기화 없음
- iOS 시뮬레이터 테스트용 standalone 모드 구성 기능
- Kotlin/Native의 라이브러리 링키지
커스텀 메모리 할당기 프리뷰
Kotlin 1.9.0은 커스텀 메모리 할당기의 프리뷰를 소개해요. 그 할당 시스템은 Kotlin/Native 메모리 매니저의 런타임 성능을 개선해요.
Kotlin/Native의 현재 객체 할당 시스템은 효율적인 가비지 컬렉션 기능이 없는 범용(general-purpose) 할당기를 사용해요. 이를 보완하기 위해 GC(가비지 컬렉터)가 단일 목록으로 병합하기 전까지(스위핑 중에 반복할 수 있는) 할당된 모든 객체의 스레드-로컬 연결 리스트를 유지해요. 이 접근 방식에는 몇 가지 성능 단점이 있어요.
- 스위핑 순서가 메모리 지역성(locality)이 부족해서 흩어진 메모리 접근 패턴이 잦아지고 잠재적 성능 문제로 이어져요.
- 연결 리스트는 객체마다 추가 메모리가 필요해서, 특히 작은 객체가 많을 때 메모리 사용량이 늘어나요.
- 할당된 객체의 단일 목록은 스위핑을 병렬화하기 어렵게 만들어, 뮤테이터 스레드가 GC 스레드가 수집하는 것보다 빠르게 객체를 할당하면 메모리 사용 문제가 생길 수 있어요.
이런 문제를 해결하기 위해 Kotlin 1.9.0은 커스텀 할당기의 프리뷰를 소개해요. 시스템 메모리를 페이지로 나누어 연속된 순서로 독립적인 스위핑을 할 수 있게 해요. 각 할당은 페이지 안의 메모리 블록이 되고, 페이지는 블록 크기를 추적해요. 서로 다른 페이지 타입이 다양한 할당 크기에 최적화돼 있어요. 메모리 블록의 연속적인 배치 덕분에 모든 할당 블록을 효율적으로 반복할 수 있어요.
스레드가 메모리를 할당하면 할당 크기에 따라 적합한 페이지를 찾아요. 스레드는 서로 다른 크기 범주별 페이지 집합을 유지해요. 보통 주어진 크기의 현재 페이지가 할당을 수용할 수 있어요. 그렇지 않으면 스레드가 공유 할당 공간에서 다른 페이지를 요청해요. 이 페이지는 이미 사용 가능하거나, 스위핑이 필요하거나, 먼저 생성해야 할 수도 있어요.
새 할당기는 동시에 여러 독립적인 할당 공간을 가질 수 있게 해주는데, 이는 Kotlin 팀이 성능을 더 개선하기 위해 다양한 페이지 레이아웃을 실험할 수 있게 해줘요.
새 할당기 설계에 대한 자세한 내용은 이 README를 참고하세요.
활성화 방법
-Xallocator=custom 컴파일러 옵션을 추가하면 돼요.
kotlin {
macosX64("native") {
binaries.executable()
compilations.configureEach {
compilerOptions.configure {
freeCompilerArgs.add("-Xallocator=custom")
}
}
}
}
피드백 남기기
커스텀 할당기를 개선하기 위해 YouTrack에서 피드백을 기다릴게요.
메인 스레드에서의 Objective-C 또는 Swift 객체 해제 훅
Kotlin 1.9.0부터 객체가 메인 스레드에서 Kotlin으로 전달되면 Objective-C 또는 Swift 객체의 해제 훅이 메인 스레드에서 호출돼요. Kotlin/Native 메모리 매니저가 이전에 Objective-C 객체에 대한 참조를 처리하던 방식은 메모리 누수로 이어질 수 있었어요. 새 동작이 메모리 매니저의 견고성을 개선할 것이라고 생각해요.
Kotlin 코드에서 참조되는 Objective-C 객체를 생각해 보세요. 예를 들어 인자로 전달되거나, 함수가 반환하거나, 컬렉션에서 꺼내온 경우가요. 이 경우 Kotlin은 Objective-C 객체에 대한 참조를 보유하는 자신만의 객체를 만들어요. Kotlin 객체가 해제되면 Kotlin/Native 런타임은 그 Objective-C 참조를 해제하는 objc_release 함수를 호출해요.
이전에는 Kotlin/Native 메모리 매니저가 특수 GC 스레드에서 objc_release를 실행했어요. 그것이 마지막 객체 참조라면 객체가 해제돼요. Objective-C 객체에 dealloc 메서드(Objective-C)나 deinit 블록(Swift) 같은 커스텀 해제 훅이 있을 때 문제가 생길 수 있었는데, 그 훅들이 특정 스레드에서 호출되기를 기대하기 때문이에요.
메인 스레드에 있는 객체의 훅은 보통 메인 스레드에서 호출되기를 기대하므로, Kotlin/Native 런타임은 이제 objc_release도 메인 스레드에서 호출해요. 이는 Objective-C 객체가 메인 스레드에서 Kotlin으로 전달되어 거기서 Kotlin 피어 객체가 생성된 경우를 다루어야 해요. 이는 메인 디스패치 큐가 처리될 때만 동작하는데, 일반 UI 애플리케이션에서는 그런 경우예요. 메인 큐가 아니거나 객체가 메인이 아닌 다른 스레드에서 Kotlin으로 전달된 경우에는 예전처럼 특수 GC 스레드에서 objc_release가 호출돼요.
opt-out 방법
문제가 생기면 gradle.properties 파일에서 다음 옵션으로 이 동작을 비활성화할 수 있어요.
kotlin.native.binary.objcDisposeOnMain=false
그런 경우 이슈 트래커에 보고해 주세요.
Kotlin/Native에서 상수 값 접근 시 객체 초기화 없음
Kotlin 1.9.0부터 Kotlin/Native 백엔드는 const val 필드에 접근할 때 객체를 초기화하지 않아요.
object MyObject {
init {
println("side effect!")
}
const val y = 1
}
fun main() {
println(MyObject.y) // No initialization at first
val x = MyObject // Initialization occurs
println(x.y)
}
이 동작은 이제 구현이 Java와 일관되고 이 경우 객체가 절대 초기화되지 않는 Kotlin/JVM과 통일됐어요. 이 변경 덕분에 Kotlin/Native 프로젝트에서도 일부 성능 개선을 기대할 수 있어요.
Kotlin/Native에서 iOS 시뮬레이터 테스트용 standalone 모드 구성 기능
기본적으로 Kotlin/Native용 iOS 시뮬레이터 테스트를 실행할 때는 수동 시뮬레이터 부팅과 종료를 피하기 위해 --standalone 플래그가 사용돼요. 1.9.0에서는 이제 Gradle 태스크에서 standalone 프로퍼티로 이 플래그 사용 여부를 구성할 수 있어요. 기본적으로 --standalone 플래그가 사용되므로 standalone 모드가 활성화돼요.
build.gradle.kts 파일에서 standalone 모드를 비활성화하는 예시는 다음과 같아요.
tasks.withType<org.jetbrains.kotlin.gradle.targets.native.tasks.KotlinNativeSimulatorTest>().configureEach {
standalone.set(false)
}
standalone 모드를 비활성화하면 시뮬레이터를 직접 부팅해야 해요. CLI에서 시뮬레이터를 부팅하려면 다음 명령을 사용할 수 있어요.
/usr/bin/xcrun simctl boot <DeviceId>
Kotlin/Native의 라이브러리 링키지
Kotlin 1.9.0부터 Kotlin/Native 컴파일러는 Kotlin 라이브러리의 링키지 문제를 Kotlin/JVM과 같은 방식으로 처리해요. 이런 문제는 써드파티 Kotlin 라이브러리 하나의 작성자가, 다른 써드파티 Kotlin 라이브러리가 사용하는 실험용 API에 호환되지 않는 변경을 할 때 겪을 수 있어요.
이제 써드파티 Kotlin 라이브러리 사이의 링키지 문제로 컴파일 중에 빌드가 실패하지 않아요. 대신 JVM에서처럼 정확히 런타임에서만 이런 오류를 만나게 돼요.
Kotlin/Native 컴파일러는 라이브러리 링키지에 문제가 있는 걸 감지할 때마다 경고를 보고해요. 컴파일 로그에서 이런 경고를 찾을 수 있어요. 예를 들어:
No function found for symbol 'org.samples/MyRemovedClass.doSomething|3657632771909858561[0]'
Can not get instance of singleton 'MyEnumClass.REMOVED_ENTRY': No enum entry found for symbol 'org.samples/MyEnumClass.REMOVED_ENTRY|null[0]'
Function 'getMyRemovedClass' can not be called: Function uses unlinked class symbol 'org.samples/MyRemovedClass|null[0]'
프로젝트에서 이 동작을 더 구성하거나 아예 비활성화할 수도 있어요.
- 컴파일 로그에 이런 경고를 보고 싶지 않다면
-Xpartial-linkage-loglevel=INFO컴파일러 옵션으로 억제할 수 있어요. - 보고된 경고의 심각도를
-Xpartial-linkage-loglevel=ERROR로 컴파일 오류까지 올릴 수도 있어요. 이 경우 컴파일이 실패하고 컴파일 로그에 모든 오류가 표시돼요. 링키지 문제를 더 자세히 조사하려면 이 옵션을 사용하세요. - 이 기능에서 예상치 못한 문제가 생기면
-Xpartial-linkage=disable컴파일러 옵션으로 언제든 opt-out할 수 있어요. 그런 경우 이슈 트래커에 보고해 주세요.
// An example of passing compiler options via Gradle build file.
kotlin {
macosX64("native") {
binaries.executable()
compilations.configureEach {
compilerOptions.configure {
// To suppress linkage warnings:
freeCompilerArgs.add("-Xpartial-linkage-loglevel=INFO")
// To raise linkage warnings to errors:
freeCompilerArgs.add("-Xpartial-linkage-loglevel=ERROR")
// To disable the feature completely:
freeCompilerArgs.add("-Xpartial-linkage=disable")
}
}
}
}
C interop 암시적 정수 변환용 컴파일러 옵션
C interop에 대해 암시적 정수 변환을 사용할 수 있게 해주는 컴파일러 옵션을 도입했어요. 신중한 고려 끝에, 이 기능은 여전히 개선의 여지가 있고 최고 품질의 API를 갖는 것이 목표이므로 의도치 않은 사용을 막기 위해 이 컴파일러 옵션을 도입했어요.
이 코드 예시에서 암시적 정수 변환 덕분에 options이 부호 없는 타입 UInt인데도 options = 0이 허용돼요. 0은 부호 있는 값이죠.
val today = NSDate()
val tomorrow = NSCalendar.currentCalendar.dateByAddingUnit(
unit = NSCalendarUnitDay,
value = 1,
toDate = today,
options = 0
)
네이티브 interop 라이브러리에서 암시적 변환을 사용하려면 -XXLanguage:+ImplicitSignedToUnsignedIntegerConversion 컴파일러 옵션을 사용하면 돼요.
이것은 Gradle build.gradle.kts 파일에서 구성할 수 있어요.
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinNativeCompile>().configureEach {
compilerOptions.freeCompilerArgs.addAll(
"-XXLanguage:+ImplicitSignedToUnsignedIntegerConversion"
)
}
Kotlin Multiplatform
Kotlin Multiplatform은 1.9.0에서 개발자 경험을 개선하기 위한 주목할 만한 업데이트를 받았어요.
- Android 타깃 지원 변경
- 새 Android 소스 세트 레이아웃 기본 활성화
- multiplatform 프로젝트에서 Gradle 설정 캐시 프리뷰
Android 타깃 지원 변경
Kotlin Multiplatform을 안정화하려는 노력을 계속하고 있어요. 필수 단계 중 하나는 Android 타깃에 대한 일급(first-class) 지원을 제공하는 거예요. 미래에는 Google의 Android 팀이 Kotlin Multiplatform에서 Android를 지원하기 위해 자체 Gradle 플러그인을 제공할 것이라고 발표해서 기쁘게 생각해요.
Google의 이 새 솔루션을 위한 길을 열기 위해 1.9.0에서 현재 Kotlin DSL의 android 블록 이름을 바꿔요. 빌드 스크립트에서 android 블록이 나타나는 모든 곳을 androidTarget으로 바꿔 주세요. 이는 다가오는 Google의 DSL을 위해 android 이름을 비워두기 위해 필요한 임시 변경이에요.
Google 플러그인은 multiplatform 프로젝트에서 Android 작업을 위한 선호 방식이 될 거예요. 준비되면 필요한 마이그레이션 지침을 제공해서 예전처럼 짧은 android 이름을 사용할 수 있게 해드릴게요.
새 Android 소스 세트 레이아웃 기본 활성화
Kotlin 1.9.0부터 새 Android 소스 세트 레이아웃이 기본값이 돼요. 이는 여러모로 헷갈리던 이전 디렉터리 이름 체계를 대체했어요. 새 레이아웃에는 몇 가지 장점이 있어요.
- 단순화된 타입 의미 – 새 Android 소스 레이아웃은 서로 다른 유형의 소스 세트를 구분하는 데 도움이 되는 명확하고 일관된 이름 규칙을 제공해요.
- 개선된 소스 디렉터리 레이아웃 – 새 레이아웃에서는
SourceDirectories배치가 더 일관성 있게 되어 코드를 구성하고 소스 파일을 찾기 쉬워져요. - Gradle 설정을 위한 명확한 이름 체계 –
KotlinSourceSets와AndroidSourceSets양쪽에서 체계가 더 일관되고 예측 가능해져요.
새 레이아웃은 Android Gradle 플러그인 7.0 이상이 필요하고 Android Studio 2022.3 이상에서 지원돼요. build.gradle(.kts) 파일에 필요한 변경을 하려면 마이그레이션 가이드를 참고하세요.
Gradle 설정 캐시 프리뷰
Kotlin 1.9.0은 multiplatform 라이브러리에서 Gradle 설정 캐시를 지원해요. 라이브러리 작성자라면 이미 개선된 빌드 성능의 이점을 누릴 수 있어요.
Gradle 설정 캐시는 이후 빌드에 구성 단계의 결과를 재사용해 빌드 과정을 빠르게 해요. 이 기능은 Gradle 8.1부터 Stable이 됐어요. 활성화하려면 Gradle 문서의 지침을 따르면 돼요.
Kotlin Multiplatform 플러그인은 여전히 Xcode 통합 태스크나 Kotlin CocoaPods Gradle 플러그인에서는 Gradle 설정 캐시를 지원하지 않아요. 이 기능은 향후 Kotlin 릴리스에서 추가할 예정이에요.
Kotlin/Wasm
Kotlin 팀은 새 Kotlin/Wasm 타깃으로 계속 실험하고 있어요. 이 릴리스는 JavaScript interop 업데이트와 함께 몇 가지 성능·크기 관련 최적화를 소개해요.
크기 관련 최적화
Kotlin 1.9.0은 WebAssembly(Wasm) 프로젝트에 상당한 크기 개선을 가져와요. 두 개의 "Hello World" 프로젝트를 비교하면, Kotlin 1.9.0의 Wasm 코드 풋프린트가 이제 Kotlin 1.8.20보다 10배 이상 작아요.
이런 크기 최적화는 Kotlin 코드로 Wasm 플랫폼을 타깃할 때 더 효율적인 리소스 활용과 개선된 성능으로 이어져요.
JavaScript interop 업데이트
이 Kotlin 업데이트는 Kotlin/Wasm의 Kotlin과 JavaScript 간 상호 운용성에 변경을 소개해요. Kotlin/Wasm은 Experimental 기능이므로 상호 운용성에 특정 제한이 적용돼요.
Dynamic 타입 제한
1.9.0부터 Kotlin은 Kotlin/Wasm에서 Dynamic 타입 사용을 더 이상 지원하지 않아요. 이제 JavaScript 상호 운용성을 용이하게 하는 새로운 범용 JsAny 타입을 선호하는 방향으로 이것은 deprecated됐어요.
자세한 내용은 Kotlin/Wasm의 JavaScript와의 상호 운용성 문서를 참고하세요.
비-외부(non-external) 타입 제한
Kotlin/Wasm은 JavaScript로 값을 주고받을 때 특정 Kotlin 정적 타입에 대한 변환을 지원해요. 지원되는 타입은 다음과 같아요.
- 부호 있는 숫자,
Boolean,Char같은 프리미티브. String.- 함수 타입.
그 외 타입은 변환 없이 불투명 참조로 전달되어 JavaScript와 Kotlin의 서브타이핑 사이에 불일치가 생겼어요.
이를 해결하기 위해 Kotlin은 JavaScript interop을 잘 지원되는 타입 집합으로 제한해요. Kotlin 1.9.0부터 Kotlin/Wasm JavaScript interop에서는 외부, 프리미티브, 문자열, 함수 타입만 지원돼요. 또한 JavaScript interop에서 사용할 수 있는 Kotlin/Wasm 객체의 핸들을 나타내는 별도의 명시적 타입인 JsReference가 도입됐어요.
자세한 내용은 Kotlin/Wasm의 JavaScript와의 상호 운용성 문서를 참고하세요.
Kotlin Playground의 Kotlin/Wasm
Kotlin Playground가 Kotlin/Wasm 타깃을 지원해요. Kotlin/Wasm을 타깃으로 하는 Kotlin 코드를 작성하고 실행하고 공유할 수 있어요. 확인해 보세요!
Kotlin/Wasm 사용에는 브라우저에서 실험 기능을 활성화하는 것이 필요해요.
import kotlin.time.*
import kotlin.time.measureTime
fun main() {
println("Hello from Kotlin/Wasm!")
computeAck(3, 10)
}
tailrec fun ack(m: Int, n: Int): Int = when {
m == 0 -> n + 1
n == 0 -> ack(m - 1, 1)
else -> ack(m - 1, ack(m, n - 1))
}
fun computeAck(m: Int, n: Int) {
var res = 0
val t = measureTime {
res = ack(m, n)
}
println()
println("ack($m, $n) = ${res}")
println("duration: ${t.inWholeNanoseconds / 1e6} ms")
}
Kotlin/JS
이 릴리스는 기존 Kotlin/JS 컴파일러 제거, Kotlin/JS Gradle 플러그인 deprecation, ES2015 실험 지원 등 Kotlin/JS에 대한 업데이트를 소개해요.
- 기존 Kotlin/JS 컴파일러 제거
- Kotlin/JS Gradle 플러그인 deprecation
- external enum deprecation
- ES2015 클래스와 모듈 실험 지원
- JS 프로덕션 배포 기본 대상 변경
- stdlib-js에서 org.w3c 선언 추출
1.9.0부터 Kotlin/JS에서도 부분 라이브러리 링키지가 활성화돼요.
기존 Kotlin/JS 컴파일러 제거
Kotlin 1.8.0에서 IR 기반 백엔드가 Stable이 됐다고 발표했었어요. 그 이후로 컴파일러를 지정하지 않는 것은 오류가 되었고, 기존 컴파일러를 사용하면 경고가 발생했어요.
Kotlin 1.9.0에서는 기존 백엔드를 사용하면 오류가 발생해요. IR 컴파일러로 마이그레이션해 주세요.
Kotlin/JS Gradle 플러그인 deprecation
Kotlin 1.9.0부터 kotlin-js Gradle 플러그인이 deprecated돼요. js() 타깃과 함께 kotlin-multiplatform Gradle 플러그인을 사용할 것을 권장해요.
Kotlin/JS Gradle 플러그인의 기능은 본질적으로 kotlin-multiplatform 플러그인을 복제했고 내부적으로 같은 구현을 공유했어요. 이런 중복은 혼란을 만들고 Kotlin 팀의 유지보수 부담을 늘렸어요.
마이그레이션 지침은 Kotlin Multiplatform 호환성 가이드를 참고하세요. 가이드에 없는 문제를 발견하면 이슈 트래커에 보고해 주세요.
external enum deprecation
Kotlin 1.9.0에서 external enum 사용은 이제 entries 같은 정적 enum 멤버가 Kotlin 밖에 존재할 수 없는 문제 때문에 deprecated될 예정이에요. 대신 객체 하위 클래스를 가진 external sealed class를 사용할 것을 권장해요.
// Before
external enum class ExternalEnum { A, B }
// After
external sealed class ExternalEnum {
object A: ExternalEnum
object B: ExternalEnum
}
객체 하위 클래스를 가진 external sealed class로 전환하면 external enum과 비슷한 기능을 얻으면서 기본 메서드와 관련된 문제를 피할 수 있어요.
Kotlin 1.9.0부터 external enum 사용은 deprecated로 표시될 거예요. 호환성과 향후 유지보수를 위해 제안된 external sealed class 구현을 사용하도록 코드를 업데이트할 것을 권장해요.
ES2015 클래스와 모듈 실험 지원
이 릴리스는 ES2015 모듈과 ES2015 클래스 생성에 대한 Experimental 지원을 소개해요.
- 모듈은 코드베이스를 단순화하고 유지보수성을 개선하는 방법을 제공해요.
- 클래스는 객체 지향 프로그래밍(OOP) 원칙을 통합해서 더 깔끔하고 직관적인 코드를 만들 수 있게 해줘요.
이 기능들을 활성화하려면 build.gradle.kts 파일을 그에 맞게 업데이트하면 돼요.
// build.gradle.kts
kotlin {
js(IR) {
useEsModules() // Enables ES2015 modules
browser()
}
}
// Enables ES2015 classes generation
tasks.withType<KotlinJsCompile>().configureEach {
kotlinOptions {
useEsClasses = true
}
}
공식 문서에서 ES2015(ECMAScript 2015, ES6)에 대해 더 알아보세요.
JS 프로덕션 배포 기본 대상 변경
Kotlin 1.9.0 이전에는 배포 대상 디렉터리가 build/distributions였어요. 그런데 이는 Gradle 아카이브의 공통 디렉터리예요. 이 문제를 해결하기 위해 Kotlin 1.9.0에서 기본 배포 대상 디렉터리를 build/dist/<targetName>/<binaryName>으로 바꿨어요.
예를 들어 productionExecutable은 build/distributions에 있었어요. Kotlin 1.9.0에서는 build/dist/js/productionExecutable에 있어요.
이 빌드 결과를 사용하는 파이프라인이 있다면 디렉터리를 업데이트해야 해요.
stdlib-js에서 org.w3c 선언 추출
Kotlin 1.9.0부터 stdlib-js는 더 이상 org.w3c 선언을 포함하지 않아요. 대신 이 선언들은 별도의 Gradle 의존성으로 옮겨졌어요. build.gradle.kts 파일에 Kotlin Multiplatform Gradle 플러그인을 추가하면 표준 라이브러리처럼 이런 선언들이 프로젝트에 자동으로 포함돼요.
수동 작업이나 마이그레이션이 필요 없어요. 필요한 조정은 자동으로 처리될 거예요.
Gradle
Kotlin 1.9.0은 새 Gradle 컴파일러 옵션과 그 외 많은 것을 가져와요.
- classpath 프로퍼티 제거
- 새 Gradle 컴파일러 옵션
- Kotlin/JVM용 프로젝트 수준 컴파일러 옵션
- Kotlin/Native 모듈 이름용 컴파일러 옵션
- 공식 Kotlin 라이브러리용 분리된 컴파일러 플러그인
- 최소 지원 버전 상향
- kapt가 즉시 태스크 생성을 일으키지 않음
- JVM 타깃 검증 모드의 프로그래매틱 구성
classpath 프로퍼티 제거
Kotlin 1.7.0에서 KotlinCompile 태스크의 classpath 프로퍼티 deprecation 주기 시작을 발표했어요. Kotlin 1.8.0에서 deprecation 수준이 ERROR로 올라갔어요. 이 릴리스에서 마침내 classpath 프로퍼티를 제거했어요. 모든 컴파일 태스크는 이제 컴파일에 필요한 라이브러리 목록에 libraries 입력을 사용해야 해요.
새 컴파일러 옵션
Kotlin Gradle 플러그인은 이제 opt-in과 컴파일러의 프로그레시브 모드에 대한 새 프로퍼티를 제공해요.
- 새 API에 opt-in하려면 이제
optIn프로퍼티를 사용해서 문자열 목록을 전달할 수 있어요:optIn.set(listOf(a, b, c))처럼요. - 프로그레시브 모드를 활성화하려면
progressiveMode.set(true)를 사용해요.
Kotlin/JVM용 프로젝트 수준 컴파일러 옵션
Kotlin 1.9.0부터 kotlin 구성 블록 안에 새 compilerOptions 블록을 사용할 수 있어요.
kotlin {
compilerOptions {
jvmTarget.set(JVM.Target_11)
}
}
이렇게 하면 컴파일러 옵션 구성이 훨씬 쉬워져요. 다만 중요한 세부사항 몇 가지를 알아두어야 해요.
- 이 구성은 프로젝트 수준에서만 동작해요.
- Android 플러그인의 경우 이 블록은 다음 블록과 같은 객체를 구성해요.
android {
kotlinOptions {}
}
android.kotlinOptions와kotlin.compilerOptions구성 블록은 서로 덮어써요. 빌드 파일에서 마지막(가장 아래) 블록이 항상 적용돼요.moduleName이 프로젝트 수준에서 구성되면 컴파일러에 전달될 때 그 값이 변경될 수 있어요.main컴파일의 경우는 아니지만, 예를 들어 테스트 소스 같은 다른 타입의 경우 Kotlin Gradle 플러그인이_test접미사를 추가해요.tasks.withType<KotlinJvmCompile>().configureEach {}(또는tasks.named<KotlinJvmCompile>("compileKotlin") { }) 안의 구성은kotlin.compilerOptions와android.kotlinOptions를 모두 덮어써요.
Kotlin/Native 모듈 이름용 컴파일러 옵션
Kotlin/Native module-name 컴파일러 옵션을 이제 Kotlin Gradle 플러그인에서 쉽게 사용할 수 있어요.
이 옵션은 컴파일 모듈의 이름을 지정하고, Objective-C로 내보내는 선언에 이름 접두사를 추가하는 데도 사용할 수 있어요.
이제 Gradle 빌드 파일의 compilerOptions 블록에서 모듈 이름을 직접 설정할 수 있어요.
tasks.named<org.jetbrains.kotlin.gradle.tasks.KotlinNativeCompile>("compileKotlinLinuxX64") {
compilerOptions {
moduleName.set("my-module-name")
}
}
tasks.named("compileKotlinLinuxX64", org.jetbrains.kotlin.gradle.tasks.KotlinNativeCompile.class) {
compilerOptions {
moduleName = "my-module-name"
}
}
공식 Kotlin 라이브러리용 분리된 컴파일러 플러그인
Kotlin 1.9.0은 공식 라이브러리용 분리된 컴파일러 플러그인을 소개해요. 이전에는 컴파일러 플러그인이 해당 Gradle 플러그인에 포함되어 있었어요. 컴파일러 플러그인이 Gradle 빌드의 Kotlin 런타임 버전보다 높은 Kotlin 버전에 대해 컴파일된 경우 호환성 문제가 생길 수 있었어요.
이제 컴파일러 플러그인은 별도의 의존성으로 추가되므로 더 이상 이전 Gradle 버전과의 호환성 문제를 겪지 않아요. 새 접근 방식의 또 다른 큰 장점은 새 컴파일러 플러그인을 Bazel 같은 다른 빌드 시스템과도 사용할 수 있다는 점이에요.
이제 Maven Central에 게시하는 새 컴파일러 플러그인 목록은 다음과 같아요.
- kotlin-atomicfu-compiler-plugin
- kotlin-allopen-compiler-plugin
- kotlin-lombok-compiler-plugin
- kotlin-noarg-compiler-plugin
- kotlin-sam-with-receiver-compiler-plugin
- kotlinx-serialization-compiler-plugin
모든 플러그인에는 해당 -embeddable 버전이 있어요. 예를 들어 kotlin-allopen-compiler-plugin-embeddable은 스크립팅 아티팩트의 기본 옵션인 kotlin-compiler-embeddable 아티팩트와 함께 동작하도록 설계됐어요.
Gradle은 이 플러그인들을 컴파일러 인자로 추가해요. 기존 프로젝트에서 변경할 필요가 없어요.
최소 지원 버전 상향
Kotlin 1.9.0부터 최소 지원 Android Gradle 플러그인 버전은 4.2.2예요.
문서에서 Kotlin Gradle 플러그인과 사용 가능한 Gradle 버전의 호환성을 확인할 수 있어요.
kapt가 Gradle에서 즉시 태스크 생성을 일으키지 않음
1.9.0 이전에는 kapt 컴파일러 플러그인이 Kotlin 컴파일 태스크의 구성된 인스턴스를 요청해서 즉시 태스크 생성(eager task creation)을 일으켰어요. 이 동작은 Kotlin 1.9.0에서 수정됐어요. build.gradle.kts 파일의 기본 구성을 사용한다면 이 변경의 영향을 받지 않아요.
커스텀 구성을 사용한다면 설정이 불리한 영향을 받을 수 있어요. 예를 들어 Gradle의 tasks API로 KotlinJvmCompile 태스크를 수정했다면, 빌드 스크립트에서 KaptGenerateStubs 태스크도 비슷하게 수정해야 해요.
예를 들어 스크립트에 KotlinJvmCompile 태스크에 대한 다음 구성이 있다면:
tasks.named<KotlinJvmCompile>("compileKotlin") { // Your custom configuration }
이 경우 같은 수정이 KaptGenerateStubs 태스크에도 포함되도록 해야 해요.
tasks.named<KaptGenerateStubs>("kaptGenerateStubs") { // Your custom configuration }
자세한 내용은 YouTrack 티켓을 참고하세요.
JVM 타깃 검증 모드의 프로그래매틱 구성
Kotlin 1.9.0 이전에는 Kotlin과 Java 사이의 JVM 타깃 비호환성 감지를 조정하는 방법이 하나뿐이었어요. 프로젝트 전체에 대해 gradle.properties에 kotlin.jvm.target.validation.mode=ERROR를 설정해야 했어요.
이제 build.gradle.kts 파일의 태스크 수준에서도 구성할 수 있어요.
tasks.named<org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile>("compileKotlin") {
jvmTargetValidationMode.set(org.jetbrains.kotlin.gradle.dsl.jvm.JvmTargetValidationMode.WARNING)
}
표준 라이브러리
Kotlin 1.9.0은 표준 라이브러리에 큰 개선이 몇 가지 있어요.
..<연산자와 시간 API가 Stable이 됐어요.- Kotlin/Native 표준 라이브러리가 철저히 검토·업데이트됐어요.
@Volatile애너테이션을 더 많은 플랫폼에서 사용할 수 있어요.- 정규식 캡처 그룹을 이름으로 가져오는 공통 함수가 생겼어요.
- 16진수를 포맷·파싱하는
HexFormat클래스가 도입됐어요.
안정적인 ..< 연산자
Kotlin 1.7.20에서 도입되고 1.8.0에서 Stable이 된 개방 범위용 새 ..< 연산자. 1.9.0에서는 개방 범위를 다루는 표준 라이브러리 API도 Stable이 됐어요.
우리 연구에 따르면 새 ..< 연산자는 개방 범위가 선언될 때 이해하기 더 쉽게 해줘요. until 중위(infix) 함수를 사용하면 상한이 포함된다고 착각하기 쉽거든요.
until 함수를 사용하는 예시는 다음과 같아요.
fun main() {
for (number in 2 until 10) {
if (number % 2 == 0) {
print("$number ")
}
}
// 2 4 6 8
}
새 ..< 연산자를 사용하는 예시는 다음과 같아요.
fun main() {
for (number in 2..<10) {
if (number % 2 == 0) {
print("$number ")
}
}
// 2 4 6 8
}
IntelliJ IDEA 2023.1.1부터 ..< 연산자를 사용할 수 있는 곳을 강조하는 새 코드 검사가 제공돼요.
이 연산자로 할 수 있는 일에 대한 자세한 내용은 Kotlin 1.7.20의 새로운 기능을 참고하세요.
안정적인 시간 API
1.3.50부터 새 시간 측정 API를 프리뷰해 왔어요. API의 지속 시간(duration) 부분은 1.6.0에서 Stable이 됐어요. 1.9.0에서 나머지 시간 측정 API가 Stable이 됐어요.
기존 시간 API는 measureTimeMillis와 measureNanoTime 함수를 제공했는데 사용이 직관적이지 않았어요. 두 함수가 다른 단위로 시간을 측정한다는 것은 분명하지만, measureTimeMillis가 벽시계(wall clock)로 시간을 측정하는 반면 measureNanoTime은 단조(monotonic) 시간 소스를 사용한다는 것은 분명하지 않았어요. 새 시간 API는 이 문제와 다른 문제들을 해결해서 API를 더 사용자 친화적으로 만들어요.
새 시간 API로 다음을 쉽게 할 수 있어요.
- 원하는 시간 단위로 단조 시간 소스를 사용해 코드를 실행하는 데 걸린 시간을 측정하기.
- 어떤 순간(moment)을 표시하기.
- 두 순간을 비교하고 그 차이를 찾기.
- 특정 순간 이후로 얼마나 시간이 지났는지 확인하기.
- 현재 시간이 특정 순간을 지났는지 확인하기.
코드 실행 시간 측정
코드 블록 실행에 걸린 시간을 측정하려면 measureTime 인라인 함수를 사용해요.
코드 블록 실행에 걸린 시간을 측정하고 코드 블록의 결과를 반환하려면 measureTimedValue 인라인 함수를 사용해요.
기본적으로 두 함수 모두 단조 시간 소스를 사용해요. 하지만 경과 실시간 소스를 사용하고 싶다면 사용할 수 있어요. 예를 들어 Android에서 기본 시간 소스 System.nanoTime()은 기기가 활성화된 동안의 시간만 셉니다. 기기가 딥 슬립에 들어가면 시간을 놓쳐요. 기기가 딥 슬립인 동안 시간을 추적하려면 SystemClock.elapsedRealtimeNanos()를 사용하는 시간 소스를 만들 수 있어요.
object RealtimeMonotonicTimeSource : AbstractLongTimeSource(DurationUnit.NANOSECONDS) {
override fun read(): Long = SystemClock.elapsedRealtimeNanos()
}
시간 표시와 차이 측정
특정 순간을 표시하려면 TimeSource 인터페이스와 markNow() 함수를 사용해 TimeMark를 만들어요. 같은 시간 소스의 TimeMark 사이의 차이를 측정하려면 뺄셈 연산자(-)를 사용해요.
import kotlin.time.*
fun main() {
val timeSource = TimeSource.Monotonic
val mark1 = timeSource.markNow()
Thread.sleep(500) // Sleep 0.5 seconds.
val mark2 = timeSource.markNow()
repeat(4) { n ->
val mark3 = timeSource.markNow()
val elapsed1 = mark3 - mark1
val elapsed2 = mark3 - mark2
println("Measurement 1.${n + 1}: elapsed1=$elapsed1, elapsed2=$elapsed2, diff=${elapsed1 - elapsed2}")
}
// It's also possible to compare time marks with each other.
println(mark2 > mark1) // This is true, as mark2 was captured later than mark1.
}
마감이 지났는지 또는 타임아웃에 도달했는지 확인하려면 hasPassedNow()와 hasNotPassedNow() 확장 함수를 사용해요.
import kotlin.time.*
import kotlin.time.Duration.Companion.seconds
fun main() {
val timeSource = TimeSource.Monotonic
val mark1 = timeSource.markNow()
val fiveSeconds: Duration = 5.seconds
val mark2 = mark1 + fiveSeconds
// It hasn't been 5 seconds yet
println(mark2.hasPassedNow())
// false
// Wait six seconds
Thread.sleep(6000)
println(mark2.hasPassedNow())
// true
}
Kotlin/Native 표준 라이브러리의 안정화 여정
Kotlin/Native용 표준 라이브러리가 계속 성장하면서, 높은 기준을 충족하는지 확인하기 위해 완전한 검토가 필요한 때라는 판단을 했어요. 그 일환으로 기존 모든 공개(public) 시그니처를 주의 깊게 검토했어요. 각 시그니처에 대해 다음을 고려했어요.
- 고유한 목적이 있는가.
- 다른 Kotlin API와 일관적인가.
- JVM용 대응물과 비슷한 동작을 하는가.
- 미래에도 유효(future-proof)한가.
이런 고려를 바탕으로 다음 중 하나의 결정을 내렸어요.
- Stable로 만들기.
- Experimental로 만들기.
private로 표시하기.- 동작 수정하기.
- 다른 위치로 옮기기.
- Deprecated하기.
- Obsolete로 표시하기.
기존 시그니처가:
- 다른 패키지로 옮겨졌다면, 원래 패키지에도 여전히 존재하지만 이제 deprecation 수준
WARNING으로 deprecated됐어요. IntelliJ IDEA는 코드 검사 시 교체 대안을 자동으로 제안할 거예요. - Deprecated됐다면, deprecation 수준
WARNING으로 deprecated됐어요. - Obsolete로 표시됐다면 계속 사용할 수 있지만 향후 교체될 예정이에요.
여기서 검토 결과를 전부 나열하지는 않을게요. 하이라이트만 몇 가지 소개할게요.
- Atomics API를 안정화했어요.
kotlinx.cinterop을 Experimental로 만들고, 이제 이 패키지를 사용하려면 서로 다른 opt-in이 필요해요. 자세한 내용은 명시적 C 상호 운용성 안정성 보장을 참고하세요.Worker클래스와 관련 API를 obsolete로 표시했어요.BitSet클래스를 obsolete로 표시했어요.kotlin.native.internal패키지의 모든publicAPI를private로 표시하거나 다른 패키지로 옮겼어요.
명시적 C 상호 운용성 안정성 보장
API의 높은 품질을 유지하기 위해 kotlinx.cinterop을 Experimental로 만들기로 결정했어요. kotlinx.cinterop은 철저히 시험되고 테스트됐지만, Stable로 만들 만큼 만족하기에는 아직 개선의 여지가 있어요. 상호 운용성을 위해 이 API를 사용하는 것을 권장하지만, 프로젝트의 특정 영역으로 사용을 한정하려고 노력해 주세요. 이렇게 하면 이 API를 Stable로 발전시키기 시작할 때 마이그레이션이 더 쉬워져요.
포인터 같은 C 스타일 외부 API를 사용하려면 @OptIn(ExperimentalForeignApi)으로 opt-in해야 해요. 그렇지 않으면 코드가 컴파일되지 않아요.
Objective-C/Swift 상호 운용성을 다루는 kotlinx.cinterop의 나머지를 사용하려면 @OptIn(BetaInteropApi)으로 opt-in해야 해요. opt-in 없이 이 API를 사용하려 하면 코드는 컴파일되지만, 어떤 동작을 기대할 수 있는지 명확히 설명하는 경고가 발생해요.
이 애너테이션들에 대한 자세한 내용은 Annotations.kt 소스 코드를 참고하세요.
이 검토의 일환으로 이루어진 모든 변경에 대한 자세한 내용은 YouTrack 티켓을 참고하세요.
여러분의 피드백을 환영해요! 티켓에 직접 댓글로 피드백을 남길 수 있어요.
안정적인 @Volatile 애너테이션
var 프로퍼티에 @Volatile을 붙이면 백킹 필드가 표시되어 이 필드에 대한 읽기나 쓰기가 원자적이고, 쓰기는 항상 다른 스레드에 보이게 돼요.
1.8.20 이전에는 kotlin.jvm.Volatile 애너테이션이 공통 표준 라이브러리에서 사용 가능했어요. 그런데 이 애너테이션은 JVM에서만 효과가 있었어요. 다른 플랫폼에서 사용하면 무시되어 오류로 이어졌어요.
1.8.20에서 JVM과 Kotlin/Native 양쪽에서 프리뷰할 수 있는 실험용 공통 애너테이션 kotlin.concurrent.Volatile을 도입했어요.
1.9.0에서 kotlin.concurrent.Volatile이 Stable이 됐어요. multiplatform 프로젝트에서 kotlin.jvm.Volatile을 사용한다면 kotlin.concurrent.Volatile로 마이그레이션할 것을 권장해요.
정규식 캡처 그룹을 이름으로 가져오는 새 공통 함수
1.9.0 이전에는 각 플랫폼이 정규식 일치에서 캡처 그룹을 이름으로 가져오는 자체 확장을 갖고 있었어요. 하지만 공통 함수는 없었어요. Kotlin 1.8.0 이전에는 표준 라이브러리가 여전히 JVM 타깃 1.6과 1.7을 지원했기 때문에 공통 함수를 가질 수 없었어요.
Kotlin 1.8.0부터 표준 라이브러리는 JVM 타깃 1.8로 컴파일돼요. 그래서 1.9.0에서 정규식 일치에 대해 그룹 내용을 이름으로 가져올 수 있는 공통 groups 함수가 생겼어요. 특정 캡처 그룹에 속한 정규식 일치 결과에 접근하려 할 때 유용해요.
city, state, areaCode 세 개의 캡처 그룹이 있는 정규식의 예시는 다음과 같아요. 이 그룹 이름들로 일치된 값에 접근할 수 있어요.
fun main() {
val regex = """\b(?<city>[A-Za-z\s]+),\s(?<state>[A-Z]{2}):\s(?<areaCode>[0-9]{3})\b""".toRegex()
val input = "Coordinates: Austin, TX: 123"
val match = regex.find(input)!!
println(match.groups["city"]?.value)
// Austin
println(match.groups["state"]?.value)
// TX
println(match.groups["areaCode"]?.value)
// 123
}
상위 디렉터리를 만드는 새 경로 유틸리티
1.9.0에는 필요한 모든 상위 디렉터리와 함께 새 파일을 만들 수 있는 새 createParentDirectories() 확장 함수가 있어요. createParentDirectories()에 파일 경로를 제공하면 상위 디렉터리가 이미 존재하는지 확인해요. 존재하면 아무 것도 하지 않아요. 존재하지 않으면 만들어줘요.
createParentDirectories()는 파일을 복사할 때 특히 유용해요. 예를 들어 copyToRecursively() 함수와 함께 사용할 수 있어요.
sourcePath.copyToRecursively(
destinationPath.createParentDirectories(),
followLinks = false
)
16진수를 포맷·파싱하는 새 HexFormat 클래스
새 HexFormat 클래스와 관련 확장 함수는 Experimental이고, 사용하려면 @OptIn(ExperimentalStdlibApi::class) 또는 컴파일러 인자 -opt-in=kotlin.ExperimentalStdlibApi로 opt-in할 수 있어요.
1.9.0에서 HexFormat 클래스와 관련 확장 함수는 숫자 값과 16진수 문자열 사이를 변환할 수 있게 해주는 Experimental 기능으로 제공돼요. 구체적으로는 확장 함수를 사용해 16진수 문자열과 ByteArrays 또는 다른 숫자 타입(Int, Short, Long) 사이를 변환할 수 있어요.
예를 들어:
println(93.toHexString()) // "0000005d"
HexFormat 클래스는 HexFormat{} 빌더로 구성할 수 있는 포맷 옵션을 포함해요.
ByteArrays로 작업한다면 프로퍼티로 구성 가능한 다음 옵션들이 있어요.
| 옵션 | 설명 |
|---|---|
upperCase |
16진수 숫자가 대문자인지 소문자인지. 기본적으로 소문자로 간주돼요. upperCase = false. |
bytes.bytesPerLine |
줄당 최대 바이트 수. |
bytes.bytesPerGroup |
그룹당 최대 바이트 수. |
bytes.bytesSeparator |
바이트 사이의 구분자. 기본값은 없음. |
bytes.bytesPrefix |
각 바이트의 두 자리 16진수 표현 바로 앞에 오는 문자열. 기본값은 없음. |
bytes.bytesSuffix |
각 바이트의 두 자리 16진수 표현 바로 뒤에 오는 문자열. 기본값은 없음. |
예를 들어:
val macAddress = "001b638445e6".hexToByteArray()
// Use HexFormat{} builder to separate the hexadecimal string by colons
println(macAddress.toHexString(HexFormat { bytes.byteSeparator = ":" }))
// "00:1b:63:84:45:e6"
// Use HexFormat{} builder to:
// * Make the hexadecimal string uppercase
// * Group the bytes in pairs
// * Separate by periods
val threeGroupFormat = HexFormat { upperCase = true; bytes.bytesPerGroup = 2; bytes.groupSeparator = "." }
println(macAddress.toHexString(threeGroupFormat))
// "001B.6384.45E6"
숫자 타입으로 작업한다면 프로퍼티로 구성 가능한 다음 옵션들이 있어요.
| 옵션 | 설명 |
|---|---|
number.prefix |
16진수 문자열의 접두사. 기본값은 없음. |
number.suffix |
16진수 문자열의 접미사. 기본값은 없음. |
number.removeLeadingZeros |
16진수 문자열에서 앞자리 0을 제거할지 여부. 기본적으로 앞자리 0은 제거되지 않아요. number.removeLeadingZeros = false |
예를 들어:
// Use HexFormat{} builder to parse a hexadecimal that has prefix: "0x".
println("0x3a".hexToInt(HexFormat { number.prefix = "0x" })) // "58"
문서 업데이트
Kotlin 문서에 주목할 만한 변경이 몇 가지 있었어요.
- Kotlin 둘러보기(tour of Kotlin) – 이론과 실습을 모두 담은 챕터로 Kotlin 프로그래밍 언어의 기초를 배울 수 있어요.
- Android 소스 세트 레이아웃 – 새 Android 소스 세트 레이아웃에 대해 배울 수 있어요.
- Kotlin Multiplatform 호환성 가이드 – Kotlin Multiplatform으로 프로젝트를 개발하면서 겪을 수 있는 호환되지 않는 변경에 대해 배울 수 있어요.
- Kotlin Wasm – Kotlin/Wasm과 그것을 Kotlin Multiplatform 프로젝트에서 사용하는 방법을 배울 수 있어요.
Kotlin 1.9.0 설치
IDE 버전 확인
IntelliJ IDEA 2022.3.3과 2023.1.1은 Kotlin 플러그인을 1.9.0으로 업데이트하도록 자동으로 제안해요. IntelliJ IDEA 2023.2에 Kotlin 1.9.0 플러그인이 포함될 예정이에요.
Android Studio Giraffe (223)과 Hedgehog (231)는 다가오는 릴리스에서 Kotlin 1.9.0을 지원할 예정이에요.
새 커맨드라인 컴파일러는 GitHub 릴리스 페이지에서 다운로드할 수 있어요.
Gradle 설정 구성
Kotlin 아티팩트와 의존성을 다운로드하려면 Maven Central 저장소를 사용하도록 settings.gradle(.kts) 파일을 업데이트하면 돼요.
pluginManagement {
repositories {
mavenCentral()
gradlePluginPortal()
}
}
저장소가 지정되지 않으면 Gradle이 폐기된 JCenter 저장소를 사용해서 Kotlin 아티팩트에 문제가 생길 수 있어요.
Kotlin 1.9.0 호환성 가이드
Kotlin 1.9.0은 기능 릴리스이므로 이전 버전의 언어로 작성된 코드와 호환되지 않는 변경이 생길 수 있어요. 이러한 변경의 상세 목록은 Kotlin 1.9.0 호환성 가이드에서 확인할 수 있어요.