Kotlin 1.7.0의 새로운 기능
Kotlin 1.7.0의 새로운 기능
Kotlin 1.7.0이 공개됐어요. 이 버전은 새 Kotlin/JVM K2 컴파일러의 Alpha 버전을 공개하고, 언어 기능을 안정화하며, JVM, JS, Native 플랫폼에 성능 개선을 가져와요. 2022년 6월 9일에 공개된 이 버전의 주요 업데이트 목록은 다음과 같아요:
- 새 Kotlin K2 컴파일러가 이제 Alpha 상태이고, 큰 성능 개선을 제공해요. JVM에서만 사용할 수 있고, kapt를 포함한 어느 컴파일러 플러그인도 이것과 함께 동작하지 않아요.
- Gradle에서 증분 컴파일에 대한 새 접근 방식. 이제 의존하는 non-Kotlin 모듈 내부의 변경에도 증분 컴파일이 지원되고 Gradle과 호환돼요.
- opt-in 요구 애노테이션, 확실히 non-null 타입, 빌더 추론을 안정화했어요.
- 타입 인자용 밑줄(underscore) 연산자가 생겼어요. 다른 타입이 지정되었을 때 인자 타입을 자동으로 추론할 수 있어요.
- 이 릴리스는 inline 클래스의 inlined 값에 대한 위임 구현을 허용해요. 이제 대부분의 경우 메모리를 할당하지 않는 가벼운 래퍼를 만들 수 있어요.
변경 사항에 대한 짧은 개요는 아래 영상에서도 확인할 수 있어요.
본문
JVM용 새 Kotlin K2 컴파일러 (Alpha)
이 Kotlin 릴리스는 새 Kotlin K2 컴파일러의 Alpha 버전을 도입해요. 새 컴파일러는 새 언어 기능의 개발을 빠르게 하고, Kotlin이 지원하는 모든 플랫폼을 통일하며, 성능 개선을 가져오고, 컴파일러 확장을 위한 API를 제공하는 것을 목표로 해요.
우리는 이미 새 컴파일러와 그 이점에 대한 몇 가지 자세한 설명을 공개했어요:
- 새 Kotlin 컴파일러로 가는 길 (The Road to the New Kotlin Compiler)
- K2 컴파일러: 탑다운 관점 (K2 Compiler: a Top-Down View)
중요한 점은, 새 K2 컴파일러의 Alpha 버전에서는 주로 성능 개선에 집중했고 JVM 프로젝트에서만 동작한다는 거예요. Kotlin/JS, Kotlin/Native 또는 다른 멀티플랫폼 프로젝트를 지원하지 않으며, kapt를 포함한 어느 컴파일러 플러그인도 이것과 함께 동작하지 않아요.
우리 벤치마크는 내부 프로젝트에서 뛰어난 결과를 보여줘요:
| 프로젝트 | 컴파일 시간 변화 |
|---|---|
| 예제 프로젝트 A | 최대 2배 빠름 |
| Kotlin 프로젝트 자체 | ~20% 빠름 |
| 각기 다른 코드베이스 | 평균 15~20% |
여러분의 JVM 프로젝트에서 성능 향상을 확인하고 옛 컴파일러의 결과와 비교할 수 있어요. Kotlin K2 컴파일러를 켜려면 다음 컴파일러 옵션을 사용하면 돼요:
-Xuse-k2
또한 K2 컴파일러는 많은 버그 수정을 포함해요. 이 목록에서 State: Open인 이슈들도 실제로는 K2에서 고쳐졌다는 점을 유의하세요.
앞으로의 Kotlin 릴리스들이 K2 컴파일러의 안정성을 개선하고 더 많은 기능을 제공할 거예요. 기대해 주세요!
Kotlin K2 컴파일러에서 성능 문제를 겪는다면 이슈 트래커로 보고해 주세요.
언어 (Language)
Kotlin 1.7.0은 위임 구현에 대한 지원과 타입 인자용 새 밑줄 연산자를 도입해요. 또한 이전 릴리스에서 프리뷰로 도입된 여러 언어 기능을 안정화해요:
inline 클래스의 inlined 값에 대한 위임 구현 (Implementation by delegation)
값이나 클래스 인스턴스의 가벼운 래퍼를 만들고 싶다면 모든 인터페이스 메서드를 손으로 구현해야 해요. 위임 구현(implementation by delegation)이 이 문제를 해결하지만, 1.7.0 이전에는 inline 클래스에서 동작하지 않았어요. 이 제한이 제거되어서, 이제 대부분의 경우 메모리를 할당하지 않는 가벼운 래퍼를 만들 수 있어요.
interface Bar {
fun foo() = "foo"
}
@JvmInline
value class BarWrapper(val bar: Bar): Bar by bar
fun main() {
val bw = BarWrapper(object: Bar {})
println(bw.foo())
}
타입 인자용 밑줄 연산자 (Underscore operator)
Kotlin 1.7.0은 타입 인자용 밑줄 연산자 _를 도입해요. 다른 타입이 지정되었을 때 타입 인자를 자동으로 추론하기 위해 사용할 수 있어요:
abstract class SomeClass<T> {
abstract fun execute(): T
}
class SomeImplementation : SomeClass<String>() {
override fun execute(): String = "Test"
}
class OtherImplementation : SomeClass<Int>() {
override fun execute(): Int = 42
}
object Runner {
inline fun <reified S: SomeClass<T>, T> run(): T {
return S::class.java.getDeclaredConstructor().newInstance().execute()
}
}
fun main() {
// SomeImplementation이 SomeClass<String>에서 파생되므로 T는 String으로 추론된다
val s = Runner.run<SomeImplementation, _>()
assert(s == "Test")
// OtherImplementation이 SomeClass<Int>에서 파생되므로 T는 Int로 추론된다
val n = Runner.run<OtherImplementation, _>()
assert(n == 42)
}
안정적인 빌더 추론 (Stable builder inference)
빌더 추론은 제네릭 빌더 함수를 호출할 때 유용한 특별한 종류의 타입 추론이에요. 람다 인자 안의 다른 호출들에 대한 타입 정보를 사용해서 호출의 타입 인자를 컴파일러가 추론하도록 도와줘요.
1.7.0부터 일반 타입 추론이 타입에 대한 충분한 정보를 얻지 못하면, 1.6.0에서 도입된 -Xenable-builder-inference 컴파일러 옵션을 지정하지 않아도 빌더 추론이 자동으로 활성화돼요.
안정적인 opt-in 요구 (Stable opt-in requirements)
opt-in 요구가 이제 Stable이고 추가 컴파일러 구성을 요구하지 않아요.
1.7.0 이전에는 opt-in 기능 자체가 경고를 피하기 위해 -opt-in=kotlin.RequiresOptIn 인자가 필요했어요. 이제 더 이상 필요하지 않아요. 하지만 다른 애노테이션이나 모듈에 opt-in하기 위해 -opt-in 컴파일러 인자를 여전히 사용할 수 있어요.
안정적인 확실히 non-null 타입 (Stable definitely non-nullable types)
Kotlin 1.7.0에서 확실히 non-null 타입이 Stable로 승격됐어요. 제네릭 Java 클래스와 인터페이스를 확장할 때 더 나은 상호 운용성을 제공해요.
새 문법 T & Any로 사용 지점에서 제네릭 타입 파라미터를 확실히 non-null로 표시할 수 있어요. 이 문법 형태는 교집합 타입(intersection types) 표기에서 왔으며, 현재 & 왼쪽에 nullable 상한을 가진 타입 파라미터, 오른쪽에 non-null Any로 제한돼요:
fun <T> elvisLike(x: T, y: T & Any): T & Any = x ?: y
fun main() {
// OK
elvisLike<String>("", "").length
// Error: 'null' cannot be a value of a non-null type
elvisLike<String>("", null).length
// OK
elvisLike<String?>(null, "").length
// Error: 'null' cannot be a value of a non-null type
elvisLike<String?>(null, null).length
}
확실히 non-null 타입에 대해 이 KEEP에서 더 알아보세요.
Kotlin/JVM
이 릴리스는 Kotlin/JVM 컴파일러에 성능 개선과 새 컴파일러 옵션을 가져와요. 또한 함수형 인터페이스 생성자에 대한 호출 가능 참조가 Stable이 됐어요. 1.7.0부터 Kotlin/JVM 컴파일의 기본 타깃 버전이 1.8이라는 점을 유의하세요.
컴파일러 성능 최적화 (Compiler performance optimizations)
Kotlin 1.7.0은 Kotlin/JVM 컴파일러에 성능 개선을 도입해요. 우리 벤치마크에 따르면 Kotlin 1.6.0과 비교해 컴파일 시간이 평균 10% 줄었어요. inline 함수를 많이 사용하는 프로젝트, 예를 들어 kotlinx.html을 사용하는 프로젝트는 바이트코드 후처리 개선 덕분에 더 빨리 컴파일돼요.
새 컴파일러 옵션: -Xjdk-release
Kotlin 1.7.0은 새 컴파일러 옵션 -Xjdk-release를 제시해요. 이 옵션은 javac의 커맨드라인 --release 옵션과 유사해요. -Xjdk-release 옵션은 타깃 바이트코드 버전을 제어하고 클래스패스의 JDK API를 지정된 Java 버전으로 제한해요. 예를 들어 kotlinc -Xjdk-release=1.8은 의존성의 JDK가 버전 9 이상이더라도 java.lang.Module을 참조하는 것을 허용하지 않아요.
이 YouTrack 티켓에 피드백을 남겨 주세요.
함수형 인터페이스 생성자에 대한 안정적인 호출 가능 참조
함수형 인터페이스 생성자에 대한 호출 가능 참조가 이제 Stable이 됐어요. 호출 가능 참조를 사용해 생성자 함수가 있는 인터페이스에서 함수형 인터페이스로 마이그레이션하는 방법을 알아보세요.
발견한 문제는 YouTrack에 보고해 주세요.
JVM 타깃 버전 1.6 제거
Kotlin/JVM 컴파일의 기본 타깃 버전은 1.8이에요. 1.6 타깃은 제거됐어요.
JVM 타깃 1.8 이상으로 마이그레이션해 주세요. JVM 타깃 버전 업데이트 방법을 알아보세요:
Kotlin/Native
Kotlin 1.7.0은 Objective-C와 Swift 상호 운용성에 변화를 포함하고 이전 릴리스에서 도입된 기능들을 안정화해요. 또한 다른 업데이트와 함께 새 메모리 매니저에 대한 성능 개선을 가져와요:
- 새 메모리 매니저에 대한 성능 개선
- JVM 및 JS IR 백엔드와 통일된 컴파일러 플러그인 ABI
- 독립 실행형 Android 실행 파일 지원
- Swift async/await 상호 운용: KotlinUnit 대신 Void 반환
- Objective-C 브리지 통한 미선언 예외 금지
- 개선된 CocoaPods 통합
- Kotlin/Native 컴파일러 다운로드 URL 재정의
새 메모리 매니저에 대한 성능 개선
새 메모리 매니저는 아직 Alpha이지만 Stable이 되는 길 위에 있어요. 이 릴리스는 특히 가비지 컬렉션(GC)에서 새 메모리 매니저에 대한 상당한 성능 개선을 제공해요. 특히 1.6.20에서 도입된 sweep 단계의 동시 구현이 이제 기본으로 켜져요. 이것은 애플리케이션이 GC를 위해 일시 정지되는 시간을 줄이는 데 도움을 줘요. 새 GC 스케줄러는 특히 더 큰 힙에서 GC 빈도를 더 잘 선택해요.
또한 디버그 바이너리를 특별히 최적화해서, 메모리 매니저의 구현 코드에 적절한 최적화 수준과 링크 타임 최적화가 사용되도록 했어요. 이것은 벤치마크에서 디버그 바이너리의 실행 시간을 약 30% 개선하는 데 도움을 줬어요.
새 메모리 매니저를 프로젝트에서 사용해서 어떻게 동작하는지 확인하고 YouTrack에 피드백을 공유해 주세요.
JVM 및 JS IR 백엔드와 통일된 컴파일러 플러그인 ABI
Kotlin 1.7.0부터 Kotlin Multiplatform Gradle 플러그인은 Kotlin/Native에 내장 가능한(embeddable) 컴파일러 jar를 기본으로 사용해요. 이 기능은 1.6.0에서 Experimental로 발표됐고, 이제 Stable이고 사용할 준비가 됐어요.
이 개선은 컴파일러 플러그인 개발 경험을 향상시키므로 라이브러리 작성자에게 매우 유용해요. 이 릴리스 이전에는 Kotlin/Native용 별도 아티팩트를 제공해야 했지만, 이제 Native와 다른 지원 플랫폼에서 같은 컴파일러 플러그인 아티팩트를 사용할 수 있어요.
독립 실행형 Android 실행 파일 지원
Kotlin 1.7.0은 Android Native 타깃에 대한 표준 실행 파일 생성을 완전히 지원해요. 이것은 1.6.20에서 도입됐고, 이제 기본으로 켜져 있어요.
Kotlin/Native가 공유 라이브러리를 생성하던 이전 동작으로 되돌리려면 다음 설정을 사용하면 돼요:
binaryOptions["androidProgramType"] = "nativeActivity"
Swift async/await 상호 운용: KotlinUnit 대신 Void 반환
Kotlin suspend 함수는 이제 Swift에서 KotlinUnit 대신 Void 타입을 반환해요. 이것은 Swift의 async/await와의 개선된 상호 운용의 결과예요. 이 기능은 1.6.20에서 도입됐고, 이 릴리스는 이 동작을 기본으로 켜요.
그런 함수에 적절한 타입을 반환하기 위해 kotlin.native.binary.unitSuspendFunctionObjCExport=proper 프로퍼티를 더 이상 사용할 필요가 없어요.
Objective-C 브리지 통한 미선언 예외 금지
Swift/Objective-C 코드에서 Kotlin 코드를 호출할 때(또는 그 반대) 그 코드가 예외를 던지면, (적절한 변환으로 언어 간 예외 전달을 특별히 허용하지 않는 한, 예를 들어 @Throws 애노테이션을 사용한 경우) 그 예외는 발생한 코드에서 처리되어야 해요.
이전에 Kotlin은 미선언 예외가 어떤 경우에 한 언어에서 다른 언어로 "새는(leak)" 의도하지 않은 또 다른 동작이 있었어요. Kotlin 1.7.0은 그 문제를 고치고, 이제 그런 경우는 프로그램 종료로 이어져요.
예를 들어 Kotlin에 { throw Exception() } 람다가 있고 Swift에서 호출한다면, Kotlin 1.7.0에서는 예외가 Swift 코드에 도달하는 즉시 종료돼요. 이전 Kotlin 버전에서는 그런 예외가 Swift 코드로 새어 나갈 수 있었어요.
@Throws 애노테이션은 이전처럼 계속 동작해요.
개선된 CocoaPods 통합
Kotlin 1.7.0부터 프로젝트에서 CocoaPods를 통합하고 싶다면 cocoapods-generate 플러그인을 설치할 필요가 없어요.
이전에는 CocoaPods를 사용하려면 CocoaPods 의존성 매니저와 cocoapods-generate 플러그인을 모두 설치해야 했어요. 예를 들어 Kotlin Multiplatform Mobile 프로젝트에서 iOS 의존성을 처리하기 위해서요.
이제 CocoaPods 통합 설정이 더 쉽고, Ruby 3 이상에서 cocoapods-generate를 설치할 수 없던 문제를 해결했어요. 이제 Apple M1에서 더 잘 동작하는 최신 Ruby 버전도 지원돼요.
초기 CocoaPods 통합 설정 방법을 알아보세요.
Kotlin/Native 컴파일러 다운로드 URL 재정의
Kotlin 1.7.0부터 Kotlin/Native 컴파일러의 다운로드 URL을 커스터마이즈할 수 있어요. CI에서 외부 링크가 금지되어 있을 때 유용해요.
기본 기본 URL https://download.jetbrains.com/kotlin/native/builds를 재정의하려면 다음 Gradle 프로퍼티를 사용하면 돼요:
kotlin.native.distribution.baseDownloadUrl=https://example.com
Kotlin/JS
Kotlin/JS는 JS IR 컴파일러 백엔드에 추가 개선과 함께 개발 경험을 더 좋게 만드는 다른 업데이트를 받고 있어요:
- 새 IR 백엔드에 대한 성능 개선
- IR 사용 시 멤버 이름에 대한 난독화(minification)
- IR 백엔드의 polyfills를 통한 이전 브라우저 지원
- js 표현식에서 JavaScript 모듈 동적 로드
- JavaScript 테스트 러너에 대한 환경 변수 지정
새 IR 백엔드에 대한 성능 개선
이 릴리스에는 개발 경험을 개선해야 할 몇 가지 주요 업데이트가 있어요:
- Kotlin/JS의 증분 컴파일 성능이 크게 개선됐어요. JS 프로젝트를 빌드하는 데 시간이 덜 걸려요. 이제 증분 리빌드는 많은 경우 레거시 백엔드와 거의 비슷할 거예요.
- 최종 아티팩트의 크기를 크게 줄였으므로 Kotlin/JS 최종 번들이 더 적은 공간을 필요로 해요. 어떤 큰 프로젝트에서는 레거시 백엔드와 비교해 프로덕션 번들 크기가 최대 20% 줄었음을 측정했어요.
- 인터페이스에 대한 타입 검사가 수십 배 개선됐어요.
- Kotlin이 더 높은 품질의 JS 코드를 생성해요.
IR 사용 시 멤버 이름에 대한 난독화
Kotlin/JS IR 컴파일러는 이제 Kotlin 클래스와 함수의 관계에 대한 내부 정보를 사용해서 더 효율적인 난독화를 적용하고, 함수, 프로퍼티, 클래스의 이름을 줄여요. 이것은 결과 번들 애플리케이션을 작게 만들어요.
이 유형의 난독화는 Kotlin/JS 애플리케이션을 프로덕션 모드로 빌드할 때 자동으로 적용되고 기본으로 켜져 있어요. 멤버 이름 난독화를 끄려면 -Xir-minimized-member-names 컴파일러 플래그를 사용하면 돼요:
kotlin {
js(IR) {
compilations.all {
compileKotlinTask.kotlinOptions.freeCompilerArgs += listOf("-Xir-minimized-member-names=false")
}
}
}
IR 백엔드의 polyfills를 통한 이전 브라우저 지원
Kotlin/JS에 대한 IR 컴파일러 백엔드는 이제 레거시 백엔드와 같은 polyfills를 포함해요. 이것은 새 컴파일러로 컴파일된 코드가 Kotlin 표준 라이브러리가 사용하는 ES2015의 모든 메서드를 지원하지 않는 이전 브라우저에서 실행될 수 있게 해줘요. 프로젝트가 실제로 사용하는 polyfills만 최종 번들에 포함되어서, 번들 크기에 대한 잠재적 영향을 최소화해요.
이 기능은 IR 컴파일러를 사용할 때 기본으로 켜져 있고, 구성할 필요가 없어요.
js 표현식에서 JavaScript 모듈 동적 로드
JavaScript 모듈로 작업할 때 대부분의 애플리케이션은 정적 import를 사용하며, 그 사용은 JavaScript 모듈 통합에서 다뤄요. 그러나 Kotlin/JS에는 런타임에 애플리케이션에서 JavaScript 모듈을 동적으로 로드하는 메커니즘이 없었어요.
Kotlin 1.7.0부터 JavaScript의 import 문이 js 블록에서 지원돼서, 런타임에 애플리케이션으로 패키지를 동적으로 가져올 수 있어요:
val myPackage = js("import('my-package')")
JavaScript 테스트 러너에 대한 환경 변수 지정
Node.js 패키지 해석을 조정하거나 Node.js 테스트에 외부 정보를 전달하기 위해, 이제 JavaScript 테스트 러너가 사용하는 환경 변수를 지정할 수 있어요. 환경 변수를 정의하려면 빌드 스크립트의 testTask 블록 안에서 키-값 쌍과 함께 environment() 함수를 사용하면 돼요:
kotlin {
js {
nodejs {
testTask {
environment("key", "value")
}
}
}
}
표준 라이브러리
Kotlin 1.7.0에서 표준 라이브러리는 다양한 변화와 개선을 받았어요. 새 기능을 도입하고, 실험적인 것을 안정화하며, Native, JS, JVM에서 명명된 캡처링 그룹에 대한 지원을 통일해요:
- min()과 max() 컬렉션 함수가 non-nullable로 반환
- 특정 인덱스에서의 정규식 매칭
- 이전 언어 및 API 버전에 대한 확장 지원
- 리플렉션을 통한 애노테이션 접근
- 안정적인 깊은 재귀 함수
- 기본 시간 소스에 대한 inline 클래스 기반 시간 마크
- Java Optionals에 대한 새 실험적 확장 함수
- JS와 Native에서 명명된 캡처링 그룹 지원
min()과 max() 컬렉션 함수가 non-nullable로 반환
Kotlin 1.4.0에서 min()과 max() 컬렉션 함수를 minOrNull()과 maxOrNull()으로 이름을 바꿨어요. 이 새 이름들은 그 동작을 더 잘 반영해요 — 수신 컬렉션이 비어 있으면 null을 반환하죠. 또한 함수의 동작을 Kotlin 컬렉션 API 전반에서 사용되는 명명 규칙과 정렬하는 데 도움을 줬어요.
Kotlin 1.4.0에서 *OrNull() 동의어를 얻은 minBy(), maxBy(), minWith(), maxWith()도 마찬가지였어요. 이 변화의 영향을 받은 옛 함수들은 점진적으로 deprecated 됐어요.
Kotlin 1.7.0은 원래 함수 이름을 되살리지만 non-nullable 반환 타입으로 가져와요. 새 min(), max(), minBy(), maxBy(), minWith(), maxWith() 함수는 이제 엄격하게 컬렉션 요소를 반환하거나 예외를 던져요.
fun main() {
val numbers = listOf<Int>()
println(numbers.maxOrNull()) // "null"
println(numbers.max()) // "Exception in... Collection is empty."
}
특정 인덱스에서의 정규식 매칭
1.5.30에서 도입된 Regex.matchAt()와 Regex.matchesAt() 함수가 이제 Stable이 됐어요. 이것은 String 또는 CharSequence의 특정 위치에서 정규식이 정확히 일치하는지 확인하는 방법을 제공해요.
matchesAt()는 일치 여부를 확인하고 불리언 결과를 반환해요:
fun main() {
val releaseText = "Kotlin 1.7.0 is on its way!"
// 정규식: 숫자 하나, 점, 숫자 하나, 점, 숫자 하나 이상
val versionRegex = "\\d[.]\\d[.]\\d+".toRegex()
println(versionRegex.matchesAt(releaseText, 0)) // "false"
println(versionRegex.matchesAt(releaseText, 7)) // "true"
}
matchAt()은 일치하는 것이 있으면 그 매치를, 없으면 null을 반환해요:
fun main() {
val releaseText = "Kotlin 1.7.0 is on its way!"
val versionRegex = "\\d[.]\\d[.]\\d+".toRegex()
println(versionRegex.matchAt(releaseText, 0)) // "null"
println(versionRegex.matchAt(releaseText, 7)?.value) // "1.7.0"
}
이 YouTrack 이슈에 피드백을 남겨 주시면 감사하겠어요.
이전 언어 및 API 버전에 대한 확장 지원
폭넓은 이전 Kotlin 버전에서 소비할 수 있도록 설계된 라이브러리를 개발하는 라이브러리 작성자를 지원하고, 주요 Kotlin 릴리스의 증가된 빈도를 다루기 위해, 우리는 이전 언어 및 API 버전에 대한 지원을 확장했어요.
Kotlin 1.7.0으로 세 개의 이전 언어 및 API 버전을 지원해요(이전에는 두 개). 이것은 Kotlin 1.7.0이 1.4.0까지의 Kotlin 버전을 타깃으로 하는 라이브러리 개발을 지원한다는 것을 의미해요. 이전 호환성에 대한 자세한 정보는 호환성 옵션을 참고하세요.
리플렉션을 통한 애노테이션 접근
처음 1.6.0에서 도입된 KAnnotatedElement.findAnnotations() 확장 함수가 이제 Stable이 됐어요. 이 리플렉션 함수는 요소에서 주어진 타입의 모든 애노테이션(개별적으로 적용된 것과 반복된 것 포함)을 반환해요.
@Repeatable
annotation class Tag(val name: String)
@Tag("First Tag")
@Tag("Second Tag")
fun taggedFunction() {
println("I'm a tagged function!")
}
fun main() {
val x = ::taggedFunction
val foo = x as KAnnotatedElement
println(foo.findAnnotations<Tag>()) // [@Tag(name=First Tag), @Tag(name=Second Tag)]
}
안정적인 깊은 재귀 함수 (Deep recursive functions)
깊은 재귀 함수는 Kotlin 1.4.0부터 실험적 기능으로 사용할 수 있었는데, Kotlin 1.7.0에서 Stable이 됐어요. DeepRecursiveFunction을 사용하면 실제 호출 스택 대신 힙에 스택을 유지하는 함수를 정의할 수 있어요. 이것은 매우 깊은 재귀 계산을 실행할 수 있게 해줘요. 깊은 재귀 함수를 호출하려면 invoke하면 돼요.
이 예시에서 깊은 재귀 함수는 이진 트리의 깊이를 재귀적으로 계산하는 데 사용돼요. 이 샘플 함수가 100,000번 재귀적으로 자신을 호출하지만 StackOverflowError는 던져지지 않아요:
class Tree(val left: Tree?, val right: Tree?)
val calculateDepth = DeepRecursiveFunction<Tree?, Int> { t ->
if (t == null) 0 else maxOf(
callRecursive(t.left),
callRecursive(t.right)
) + 1
}
fun main() {
// 깊이가 100_000인 트리 생성
val deepTree = generateSequence(Tree(null, null)) { prev ->
Tree(prev, null)
}.take(100_000).last()
println(calculateDepth(deepTree)) // 100000
}
재귀 깊이가 1000번 호출을 초과하는 코드에서 깊은 재귀 함수를 사용하는 것을 고려해 보세요.
기본 시간 소스에 대한 inline 클래스 기반 시간 마크
Kotlin 1.7.0은 TimeSource.Monotonic이 반환하는 시간 마크를 inline 값 클래스로 바꿔서 시간 측정 기능의 성능을 개선해요. 이것은 markNow(), elapsedNow(), measureTime(), measureTimedValue() 같은 함수를 호출할 때 그 TimeMark 인스턴스에 래퍼 클래스를 할당하지 않는다는 것을 의미해요. 특히 핫 경로의 일부인 코드 조각을 측정할 때, 이렇게 하면 측정의 성능 영향을 최소화하는 데 도움이 돼요:
@OptIn(ExperimentalTime::class)
fun main() {
val mark = TimeSource.Monotonic.markNow() // 반환된 `TimeMark`는 inline 클래스
val elapsedDuration = mark.elapsedNow()
}
Java Optionals에 대한 새 실험적 확장 함수
Kotlin 1.7.0은 Java의 Optional 클래스 작업을 단순화하는 새 편의 함수와 함께 와요. 이 새 함수들은 JVM에서 optional 객체를 언랩하고 변환하는 데 사용할 수 있고, Java API 작업을 더 간결하게 하는 데 도움을 줘요.
getOrNull(), getOrDefault(), getOrElse() 확장 함수는 Optional에 값이 있으면 그 값을 얻을 수 있게 해줘요. 그렇지 않으면 각각 null, 기본값, 또는 함수가 반환하는 값을 얻어요:
val presentOptional = Optional.of("I'm here!")
println(presentOptional.getOrNull())
// "I'm here!"
val absentOptional = Optional.empty<String>()
println(absentOptional.getOrNull())
// null
println(absentOptional.getOrDefault("Nobody here!"))
// "Nobody here!"
println(absentOptional.getOrElse {
println("Optional was absent!")
"Default value!"
})
// "Optional was absent!"
// "Default value!"
toList(), toSet(), asSequence() 확장 함수는 존재하는 Optional의 값을 리스트, 셋, 시퀀스로 변환하거나, 그렇지 않으면 빈 컬렉션을 반환해요. toCollection() 확장 함수는 Optional 값을 이미 존재하는 대상 컬렉션에 추가해요:
val presentOptional = Optional.of("I'm here!")
val absentOptional = Optional.empty<String>()
println(presentOptional.toList() + "," + absentOptional.toList())
// ["I'm here!"], []
println(presentOptional.toSet() + "," + absentOptional.toSet())
// ["I'm here!"], []
val myCollection = mutableListOf<String>()
absentOptional.toCollection(myCollection)
println(myCollection)
// []
presentOptional.toCollection(myCollection)
println(myCollection)
// ["I'm here!"]
val list = listOf(presentOptional, absentOptional).flatMap { it.asSequence() }
println(list)
// ["I'm here!"]
이 확장 함수들은 Kotlin 1.7.0에서 Experimental로 도입되고 있어요. Optional 확장에 대해 더 알아보려면 이 KEEP을 참고하세요. 항상 그렇듯 Kotlin 이슈 트래커에 피드백을 환영해요.
JS와 Native에서 명명된 캡처링 그룹 지원
Kotlin 1.7.0부터 명명된 캡처링 그룹이 JVM뿐 아니라 JS와 Native 플랫폼에서도 지원돼요.
캡처링 그룹에 이름을 주려면 정규식에서 (?<name>group) 문법을 사용하면 돼요. 그룹이 매치한 텍스트를 얻으려면 새로 도입된 MatchGroupCollection.get() 함수를 호출하고 그룹 이름을 전달하면 돼요.
이름으로 매치된 그룹 값 검색
도시 좌표를 매치하는 이 예시를 고려해 보세요. 정규식이 매치한 그룹 컬렉션을 얻으려면 groups를 사용해요. value를 사용해 그룹의 내용을 숫자(인덱스)로 검색하는 것과 이름으로 검색하는 것을 비교해 보세요:
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[2]?.value) // "TX" — 숫자로
}
명명된 역참조 (Backreferencing)
이제 그룹을 역참조할 때도 그룹 이름을 사용할 수 있어요. 역참조는 이전에 캡처링 그룹이 매치한 것과 같은 텍스트를 매치해요. 이를 위해 정규식에서 \k<name> 문법을 사용하면 돼요:
fun backRef() {
val regex = "(?<title>\\w+), yes \\k<title>".toRegex()
val match = regex.find("Do you copy? Sir, yes Sir!")!!
println(match.value) // "Sir, yes Sir"
println(match.groups["title"]?.value) // "Sir"
}
대체 표현식에서의 명명된 그룹
명명된 그룹 참조는 대체 표현식과 함께 사용할 수 있어요. 입력에서 지정된 정규식의 모든 발생을 대체 표현식으로 치환하는 replace() 함수와 첫 번째 매치만 바꾸는 replaceFirst() 함수를 고려해 보세요.
대체 문자열에서 ${name}의 발생은 지정된 이름을 가진 캡처된 그룹에 해당하는 부분 문자열로 대체돼요. 그룹 참조에서 이름과 인덱스로 대체를 비교할 수 있어요:
fun dateReplace() {
val dateRegex = Regex("(?<dd>\\d{2})-(?<mm>\\d{2})-(?<yyyy>\\d{4})")
val input = "Date of birth: 27-04-2022"
println(dateRegex.replace(input, "\${yyyy}-\${mm}-\${dd}")) // "Date of birth: 2022-04-27" — 이름으로
println(dateRegex.replace(input, "\$3-\$2-\$1")) // "Date of birth: 2022-04-27" — 숫자로
}
Gradle
이 릴리스는 새 빌드 리포트, Gradle 플러그인 변형 지원, kapt의 새 통계 등 많은 것을 도입해요:
- 증분 컴파일에 대한 새 접근 방식
- 컴파일러 성능 추적을 위한 새 빌드 리포트
- Gradle과 Android Gradle 플러그인의 최소 지원 버전 변화
- Gradle 플러그인 변형 지원
- Kotlin Gradle 플러그인 API 업데이트
- plugins API를 통한 sam-with-receiver 플러그인 사용 가능
- 컴파일 태스크 변화
- kapt에서 각 애노테이션 프로세서가 생성한 파일의 새 통계
- kotlin.compiler.execution.strategy 시스템 프로퍼티 deprecation
- deprecated 옵션, 메서드, 플러그인 제거
증분 컴파일에 대한 새 접근 방식
Kotlin 1.7.0에서 크로스 모듈 변경에 대한 증분 컴파일을 재작업했어요. 이제 의존하는 non-Kotlin 모듈 내부의 변경에도 증분 컴파일이 지원되고, Gradle 빌드 캐시와 호환돼요. 컴파일 회피(compilation avoidance) 지원도 개선됐어요.
빌드 캐시를 사용하거나 non-Kotlin Gradle 모듈에서 자주 변경한다면 새 접근 방식의 가장 큰 이점을 볼 수 있을 거예요. kotlin-gradle-plugin 모듈에 대한 Kotlin 프로젝트 테스트는 캐시 히트 후 변경에 대해 80% 이상의 개선을 보여줘요.
이 새 접근 방식을 시도하려면 gradle.properties에 다음 옵션을 설정하면 돼요:
kotlin.incremental.useClasspathSnapshot=true
증분 컴파일의 새 접근 방식이 내부에서 어떻게 구현되는지 이 블로그 포스트에서 알아보세요.
우리는 이 기술을 안정화하고 다른 백엔드(예: JS)와 빌드 시스템에 대한 지원을 추가할 계획이에요. 이 컴파일 방식에서 겪는 문제나 이상한 동작에 대한 보고를 YouTrack에 남겨 주시면 감사하겠어요. 감사합니다!
Kotlin 팀은 Ivan Gavrilovic, Hung Nguyen, Cédric Champeau 및 다른 외부 기여자들의 도움에 매우 감사해요.
Kotlin 컴파일러 태스크를 위한 빌드 리포트
Kotlin 1.7.0은 컴파일러 성능을 추적하는 데 도움이 되는 빌드 리포트를 도입해요. 리포트에는 각기 다른 컴파일 단계의 지속 시간과 컴파일이 증분적일 수 없었던 이유가 포함돼요.
빌드 리포트는 컴파일러 태스크 문제를 조사하고 싶을 때 유용해요. 예를 들어:
- Gradle 빌드가 너무 오래 걸리고 성능 저하의 근본 원인을 이해하고 싶을 때.
- 같은 프로젝트의 컴파일 시간이 달라서 어떤 때는 몇 초, 어떤 때는 몇 분이 걸릴 때.
빌드 리포트를 켜려면 gradle.properties에서 빌드 리포트 출력을 저장할 위치를 선언하면 돼요:
kotlin.build.report.output=file
다음 값들(및 그 조합)이 사용 가능해요:
file은 빌드 리포트를 로컬 파일에 저장해요.build_scan은 빌드 리포트를 build scan의custom values섹션에 저장해요.http는 HTTP(S)를 사용해 빌드 리포트를 게시해요. POST 메서드는 메트릭을 JSON 형식으로 보내요. 데이터는 버전마다 바뀔 수 있어요. 보내는 데이터의 현재 버전은 Kotlin 저장소에서 볼 수 있어요.
오래 걸리는 컴파일에 대한 빌드 리포트를 분석하면 해결하는 데 도움을 받을 수 있는 두 가지 일반적인 경우가 있어요:
- 빌드가 증분적이지 않았어요. 이유를 분석하고 근본 문제를 고치세요.
- 빌드가 증분적이었지만 너무 오래 걸렸어요. 소스 파일을 재구성해 보세요 — 큰 파일 나누기, 별도 클래스를 다른 파일에 저장, 큰 클래스 리팩터링, 최상위 함수를 다른 파일에 선언 등.
새 빌드 리포트에 대해 이 블로그 포스트에서 더 알아보세요.
여러분의 인프라에서 빌드 리포트 사용을 시도해 보시길 환영해요. 피드백이 있거나 문제를 겪거나 개선을 제안하고 싶으면 이슈 트래커에 보고해 주세요. 감사합니다!
최소 지원 버전 상향
Kotlin 1.7.0부터 최소 지원 Gradle 버전은 6.7.1이에요. Gradle 플러그인 변형과 새 Gradle API를 지원하려면 버전을 올려야 했어요. 앞으로는 Gradle 플러그인 변형 기능 덕분에 최소 지원 버전을 자주 올릴 필요가 없어야 해요.
또한 최소 지원 Android Gradle 플러그인 버전은 이제 3.6.4예요.
Gradle 플러그인 변형 지원
Gradle 7.0은 Gradle 플러그인 작성자를 위한 새 기능을 도입했어요 — 변형이 있는 플러그인. 이 기능은 7.1 미만 Gradle 버전에 대한 호환성을 유지하면서 새 Gradle 기능에 대한 지원을 더 쉽게 추가하게 해줘요. Gradle에서 변형 선택에 대해 더 알아보세요.
Gradle 플러그인 변형으로 우리는 다른 Gradle 버전에 대해 서로 다른 Kotlin Gradle 플러그인 변형을 제공할 수 있어요. 목표는 가장 오래된 지원 Gradle 버전에 해당하는 main 변형에서 기본 Kotlin 컴파일을 지원하는 거예요. 각 변형은 해당 릴리스의 Gradle 기능에 대한 구현을 가질 거예요. 최신 변형이 가장 넓은 Gradle 기능 집합을 지원할 거예요. 이 접근 방식으로 제한된 기능으로 이전 Gradle 버전에 대한 지원을 확장할 수 있어요.
현재 Kotlin Gradle 플러그인의 변형은 두 개뿐이에요:
main— Gradle 버전 6.7.1–6.9.3용gradle70— Gradle 버전 7.0 이상용
향후 Kotlin 릴리스에서 더 추가할 수 있어요.
빌드가 어떤 변형을 사용하는지 확인하려면 --info 로그 수준을 켜고 출력에서 Using Kotlin Gradle plugin으로 시작하는 문자열을 찾으면 돼요. 예: Using Kotlin Gradle plugin main variant.
이 YouTrack 티켓에 피드백을 남겨 주세요.
Kotlin Gradle 플러그인 API 업데이트
Kotlin Gradle 플러그인 API 아티팩트는 몇 가지 개선을 받았어요:
- 사용자 구성 가능한 입력이 있는 Kotlin/JVM 및 Kotlin/kapt 태스크용 새 인터페이스가 있어요.
- 모든 Kotlin 플러그인이 상속하는 새
KotlinBasePlugin인터페이스가 있어요. 어떤 Kotlin Gradle 플러그인(JVM, JS, Multiplatform, Native 및 다른 플랫폼)이 적용될 때마다 어떤 구성 동작을 트리거하고 싶다면 이 인터페이스를 사용하면 돼요:project.plugins.withType<org.jetbrains.kotlin.gradle.plugin.KotlinBasePlugin>() { // 여기서 동작을 구성 }KotlinBasePlugin에 대한 피드백은 이 YouTrack 티켓에 남길 수 있어요. - Android Gradle 플러그인이 스스로 안에서 Kotlin 컴파일을 구성하도록 기반을 마련했어요. 이는 더 이상 빌드에 Kotlin Android Gradle 플러그인을 추가할 필요가 없다는 뜻이에요. 추가된 지원에 대해 알아보고 시도해 보려면 Android Gradle Plugin 릴리스 공지를 팔로우하세요!
sam-with-receiver 플러그인이 plugins API를 통해 사용 가능
sam-with-receiver 컴파일러 플러그인이 이제 Gradle plugins DSL을 통해 사용 가능해요:
plugins {
id("org.jetbrains.kotlin.plugin.sam.with.receiver") version "$kotlin_version"
}
컴파일 태스크 변화
컴파일 태스크는 이 릴리스에서 많은 변화를 받았어요:
- Kotlin 컴파일 태스크는 더 이상 Gradle
AbstractCompile태스크를 상속하지 않아요.DefaultTask만 상속해요. AbstractCompile태스크는sourceCompatibility와targetCompatibility입력이 있어요.AbstractCompile태스크가 더 이상 상속되지 않으므로, 이 입력들은 Kotlin 사용자의 스크립트에서 더 이상 사용할 수 없어요.SourceTask.stableSources입력은 더 이상 사용할 수 없고,sources입력을 사용해야 해요.setSource(...)메서드는 여전히 사용 가능해요.- 모든 컴파일 태스크는 이제 컴파일에 필요한 라이브러리 목록에
libraries입력을 사용해요.KotlinCompile태스크는 여전히 deprecated Kotlin 프로퍼티classpath를 가지며, 이것은 향후 릴리스에서 제거될 거예요. - 컴파일 태스크는 여전히
PatternFilterable인터페이스를 구현해서 Kotlin 소스의 필터링을 허용해요.sourceFilesExtensions입력은PatternFilterable메서드를 사용하는 대신으로 제거됐어요. - deprecated
Gradle destinationDir: File출력은destinationDirectory: DirectoryProperty출력으로 교체됐어요. - Kotlin/Native
AbstractNativeCompile태스크는 이제AbstractKotlinCompileTool기본 클래스를 상속해요. 이것은 Kotlin/Native 빌드 도구를 다른 모든 도구에 통합하는 것을 향한 첫 단계예요.
이 YouTrack 티켓에 피드백을 남겨 주세요.
kapt에서 각 애노테이션 프로세서가 생성한 파일의 통계
kotlin-kapt Gradle 플러그인은 이미 각 프로세서에 대한 성능 통계를 보고해요. Kotlin 1.7.0부터 각 애노테이션 프로세서가 생성한 파일 수에 대한 통계도 보고할 수 있어요.
이것은 빌드의 일부로 사용되지 않는 애노테이션 프로세서가 있는지 추적하는 데 유용해요. 생성된 보고서를 사용해서 불필요한 애노테이션 프로세서를 트리거하는 모듈을 찾고, 그 모듈을 업데이트해 방지할 수 있어요.
통계는 두 단계로 켜요:
build.gradle.kts에서showProcessorStats플래그를true로 설정:kapt { showProcessorStats = true }gradle.properties에서kapt.verboseGradle 프로퍼티를true로 설정:kapt.verbose=true
통계는 info 수준의 로그에 나타나요. Annotation processor stats: 줄 다음에 각 애노테이션 프로세서의 실행 시간에 대한 통계가 보여요. 그 줄들 뒤에는 Generated files report: 줄과 각 애노테이션 프로세서가 생성한 파일 수에 대한 통계가 있어요. 예를 들어:
[INFO] Annotation processor stats:
[INFO] org.mapstruct.ap.MappingProcessor: total: 290 ms, init: 1 ms, 3 round(s): 289 ms, 0 ms, 0 ms
[INFO] Generated files report:
[INFO] org.mapstruct.ap.MappingProcessor: total sources: 2, sources per round: 2, 0, 0
이 YouTrack 티켓에 피드백을 남겨 주세요.
kotlin.compiler.execution.strategy 시스템 프로퍼티 deprecation
Kotlin 1.6.20은 Kotlin 컴파일러 실행 전략을 정의하는 새 프로퍼티를 도입했어요. Kotlin 1.7.0에서 옛 시스템 프로퍼티 kotlin.compiler.execution.strategy의 deprecation 주기가 새 프로퍼티를 위해 시작됐어요.
kotlin.compiler.execution.strategy 시스템 프로퍼티를 사용하면 경고를 받게 돼요. 이 프로퍼티는 향후 릴리스에서 삭제될 거예요. 옛 동작을 보존하려면 시스템 프로퍼티를 같은 이름의 Gradle 프로퍼티로 교체하면 돼요. gradle.properties에서 할 수 있어요. 예를 들어:
kotlin.compiler.execution.strategy=out-of-process
컴파일 태스크 프로퍼티 compilerExecutionStrategy를 사용할 수도 있어요. 컴파일러 실행 전략 페이지에서 더 알아보세요.
deprecated 옵션, 메서드, 플러그인 제거
useExperimentalAnnotation 메서드 제거
Kotlin 1.7.0에서 useExperimentalAnnotation Gradle 메서드의 deprecation 주기를 완료했어요. 모듈에서 API를 사용하도록 옵트인하려면 optIn()을 대신 사용하면 돼요.
예를 들어 Gradle 모듈이 멀티플랫폼이라면:
sourceSets {
all {
languageSettings.optIn("org.mylibrary.OptInAnnotation")
}
}
Kotlin에서 opt-in 요구 사항에 대해 더 알아보세요.
deprecated 컴파일러 옵션 제거
여러 컴파일러 옵션의 deprecation 주기를 완료했어요:
kotlinOptions.jdkHome컴파일러 옵션은 1.5.30에서 deprecated 됐고 이번 릴리스에서 제거됐어요. Gradle 빌드에 이 옵션이 있으면 이제 실패해요. Kotlin 1.5.30부터 지원된 Java toolchains를 사용할 것을 권장해요.- deprecated
noStdlib컴파일러 옵션도 제거됐어요. Gradle 플러그인은kotlin.stdlib.default.dependency=true프로퍼티를 사용해서 Kotlin 표준 라이브러리의 존재를 제어해요.
deprecated 플러그인 제거
Kotlin 1.4.0에서 kotlin2js와 kotlin-dce-plugin 플러그인이 deprecated 됐고, 이 릴리스에서 제거됐어요. kotlin2js 대신 새 org.jetbrains.kotlin.js 플러그인을 사용해요. 죽은 코드 제거(DCE)는 Kotlin/JS Gradle 플러그인이 제대로 구성되면 동작해요.
Kotlin 1.6.0에서 KotlinGradleSubplugin 클래스의 deprecation 수준을 ERROR로 바꿨어요. 개발자들은 이 클래스를 컴파일러 플러그인 작성에 사용했어요. 이 릴리스에서 이 클래스가 제거됐어요. 대신 KotlinCompilerPluginSupportPlugin 클래스를 사용해요.
deprecated coroutines DSL 옵션과 프로퍼티 제거
deprecated kotlin.experimental.coroutines Gradle DSL 옵션과 gradle.properties에서 사용되던 kotlin.coroutines 프로퍼티를 제거했어요. 이제 그냥 suspending 함수를 사용하거나 빌드 스크립트에 kotlinx.coroutines 의존성을 추가하면 돼요.
Coroutines 가이드에서 coroutines에 대해 더 알아보세요.
toolchain 확장 메서드의 타입 캐스트 제거
Kotlin 1.7.0 이전에는 Kotlin DSL로 Gradle toolchain을 구성할 때 JavaToolchainSpec 클래스로 타입 캐스트를 해야 했어요:
kotlin {
jvmToolchain {
(this as JavaToolchainSpec).languageVersion.set(JavaLanguageVersion.of(<MAJOR_JDK_VERSION>)
}
}
이제 (this as JavaToolchainSpec) 부분을 생략할 수 있어요:
kotlin {
jvmToolchain {
languageVersion.set(JavaLanguageVersion.of(<MAJOR_JDK_VERSION>)
}
}
Kotlin 1.7.0으로 마이그레이션
Kotlin 1.7.0 설치
IntelliJ IDEA 2022.1과 Android Studio Chipmunk (212)는 Kotlin 플러그인 1.7.0으로 업데이트를 자동으로 제안해요.
새 커맨드라인 컴파일러는 GitHub 릴리스 페이지에서 다운로드할 수 있어요.
Kotlin 1.7.0으로 기존 프로젝트 마이그레이션 또는 새 프로젝트 시작
- 기존 프로젝트를 Kotlin 1.7.0으로 마이그레이션하려면 Kotlin 버전을
1.7.0으로 바꾸고 Gradle 또는 Maven 프로젝트를 다시 임포트하면 돼요. Kotlin 1.7.0으로 업데이트하는 방법 알아보기. - Kotlin 1.7.0으로 새 프로젝트를 시작하려면 Kotlin 플러그인을 업데이트하고 File | New | Project에서 Project Wizard를 실행하면 돼요.
Kotlin 1.7.0 호환성 가이드
Kotlin 1.7.0은 기능 릴리스이므로 이전 버전의 언어로 작성된 코드와 호환되지 않는 변화를 가져올 수 있어요. 그런 변화의 자세한 목록은 Kotlin 1.7.0 호환성 가이드에서 확인할 수 있어요.