Kotlin 1.6.0의 새로운 기능

Kotlin 1.6.0의 새로운 기능

Kotlin 1.6.0은 새 언어 기능, 기존 기능에 대한 최적화와 개선, 그리고 Kotlin 표준 라이브러리에 대한 많은 개선을 가져와요. 2021년 11월 16일에 공개된 이 버전의 변경 사항 개요는 릴리스 블로그 포스트에서도 확인할 수 있어요.

출처: What's new in Kotlin 1.6.0

본문

언어 (Language)

Kotlin 1.6.0은 이전 1.5.30 릴리스에서 프리뷰로 소개된 여러 언어 기능을 안정화해요:

또한 다양한 타입 추론 개선과 클래스 타입 파라미터에 대한 애노테이션 지원도 포함해요:

enum, sealed, Boolean 주제에 대한 안정적인 완전한 when 문

완전한(exhaustive) when 문은 주제의 가능한 모든 타입이나 값에 대한 분기, 또는 일부 타입에 대한 분기와 else 분기를 포함해요. 가능한 모든 경우를 다루므로 코드가 더 안전해져요.

우리는 곧 비완전한 when 문을 금지해서 when 표현식과 동작을 일관되게 만들 거예요. 매끄러운 마이그레이션을 위해 Kotlin 1.6.0은 enum, sealed, Boolean 주제를 가진 비완전한 when 문에 대해 경고를 보고해요. 이 경고들은 향후 릴리스에서 오류가 될 거예요.

sealed class Contact {
    data class PhoneCall(val number: String) : Contact()
    data class TextMessage(val number: String) : Contact()
}

fun Contact.messageCost(): Int =
    when(this) { // Error: 'when' expression must be exhaustive
        is Contact.PhoneCall -> 42
    }

fun sendMessage(contact: Contact, message: String) {
    // Starting with 1.6.0

    // Warning: Non exhaustive 'when' statements on Boolean will be
    // prohibited in 1.7, add 'false' branch or 'else' branch instead 
    when(message.isEmpty()) {
        true -> return
    }
    // Warning: Non exhaustive 'when' statements on sealed class/interface will be
    // prohibited in 1.7, add 'is TextMessage' branch or 'else' branch instead
    when(contact) {
        is Contact.PhoneCall -> TODO()
    }
}

이 변화와 그 영향에 대한 더 자세한 설명은 이 YouTrack 티켓에서 확인할 수 있어요.

안정적인 수퍼타입으로서의 suspend 함수

suspending 함수형 타입의 구현이 Kotlin 1.6.0에서 Stable이 됐어요. 프리뷰는 1.5.30에서 사용할 수 있었어요.

이 기능은 Kotlin 코루틴을 사용하고 suspending 함수형 타입을 받는 API를 설계할 때 유용해요. 이제 원하는 동작을 suspending 함수형 타입을 구현하는 별도 클래스로 감싸서 코드를 간결하게 만들 수 있어요.

class MyClickAction : suspend () -> Unit {
    override suspend fun invoke() { TODO() }
}

fun launchOnClick(action: suspend () -> Unit) {}

이전에 람다와 suspend 함수 참조만 허용됐던 곳에서 이 클래스의 인스턴스를 사용할 수 있어요: launchOnClick(MyClickAction()).

현재 구현 세부사항에서 오는 두 가지 제한이 있어요:

  • 수퍼타입 목록에서 일반 함수형 타입과 suspension 타입을 섞을 수 없어요.
  • 여러 개의 suspending 함수형 수퍼타입을 사용할 수 없어요.

안정적인 suspend 변환

Kotlin 1.6.0은 일반 함수형 타입에서 suspending 함수형 타입으로의 Stable 변환을 도입해요. 1.4.0부터 이 기능은 함수 리터럴과 호출 가능 참조를 지원했어요. 1.6.0에서는 어떤 형태의 표현식에도 동작해요. 호출 인자로서 이제 suspending이 기대되는 곳에 적합한 일반 함수형 타입의 어떤 표현식이든 전달할 수 있어요. 컴파일러가 암시적 변환을 자동으로 수행해요.

fun getSuspending(suspending: suspend () -> Unit) {}

fun suspending() {}

fun test(regular: () -> Unit) {
    getSuspending { }           // OK
    getSuspending(::suspending) // OK
    getSuspending(regular)      // OK
}

안정적인 애노테이션 클래스 인스턴스화

Kotlin 1.5.30은 JVM 플랫폼에서 애노테이션 클래스 인스턴스화에 대한 실험적 지원을 도입했어요. 1.6.0에서는 이 기능이 Kotlin/JVM과 Kotlin/JS 모두에서 기본으로 사용 가능해요.

애노테이션 클래스 인스턴스화를 이 KEEP에서 더 알아보기.

재귀적 제네릭 타입의 개선된 타입 추론

Kotlin 1.5.30은 재귀적 제네릭 타입의 타입 추론 개선을 도입했어요. 이 개선 덕분에 그 타입 인자를 해당 타입 파라미터의 상한만으로 추론할 수 있었어요. 그 개선은 컴파일러 옵션으로 사용할 수 있었어요. 1.6.0 이상에서는 기본으로 켜져 있어요.

// 1.5.30 이전
val containerA = PostgreSQLContainer<Nothing>(DockerImageName.parse("postgres:13-alpine")).apply {
  withDatabaseName("db")
  withUsername("user")
  withPassword("password")
  withInitScript("sql/schema.sql")
}

// 1.5.30의 컴파일러 옵션 또는 1.6.0부터는 기본
val containerB = PostgreSQLContainer(DockerImageName.parse("postgres:13-alpine"))
  .withDatabaseName("db")
  .withUsername("user")
  .withPassword("password")
  .withInitScript("sql/schema.sql")

빌더 추론의 변화

빌더 추론은 제네릭 빌더 함수를 호출할 때 유용한 타입 추론의 한 형태예요. 람다 인자 안의 호출들로부터의 타입 정보의 도움으로 호출의 타입 인자를 추론할 수 있어요.

우리는 완전히 안정적인 빌더 추론에 더 가까워지게 하는 여러 변화를 만들고 있어요. 1.6.0부터:

  • 1.5.30에서 도입-Xunrestricted-builder-inference 컴파일러 옵션을 지정하지 않고도 빌더 람다 안에서 아직 추론되지 않은 타입의 인스턴스를 반환하는 호출을 할 수 있어요.
  • -Xenable-builder-inference@BuilderInference 애노테이션을 적용하지 않고 직접 빌더를 작성할 수 있어요.
  • -Xenable-builder-inference로 일반 타입 추론이 타입에 대한 충분한 정보를 얻지 못하면 빌더 추론이 자동으로 활성화돼요.

커스텀 제네릭 빌더 작성하는 법 알아보기.

클래스 타입 파라미터에 대한 애노테이션 지원

클래스 타입 파라미터에 대한 애노테이션 지원은 다음과 같은 모습이에요:

@Target(AnnotationTarget.TYPE_PARAMETER)
annotation class BoxContent

class Box<@BoxContent T> {}

모든 타입 파라미터에 대한 애노테이션은 JVM 바이트코드로 방출되어서 애노테이션 프로세서가 사용할 수 있어요.

동기 사용 사례는 이 YouTrack 티켓을 읽어보세요.

애노테이션에 대해 더 알아보세요.

이전 API 버전을 더 오래 지원

Kotlin 1.6.0부터 현재 안정 버전과 함께 세 개의 이전 API 버전에 대한 개발을 지원할 거예요(이전에는 두 개). 현재 1.3, 1.4, 1.5, 1.6 버전을 지원해요.

Kotlin/JVM

Kotlin/JVM에서 1.6.0부터 컴파일러는 JVM 17에 해당하는 바이트코드 버전의 클래스를 생성할 수 있어요. 새 언어 버전은 또한 로드맵에 있던 최적화된 위임 프로퍼티와 반복 가능 애노테이션을 포함해요:

1.8 JVM 타깃을 위한 런타임 보존 반복 가능 애노테이션

Java 8은 반복 가능 애노테이션을 도입했어요. 이는 단일 코드 요소에 여러 번 적용할 수 있어요. 이 기능은 Java 코드에 두 가지 선언이 있어야 해요: @java.lang.annotation.Repeatable로 표시된 반복 가능 애노테이션 자체와 그 값을 담는 containing 애노테이션.

Kotlin에도 반복 가능 애노테이션이 있지만, 반복 가능하게 만들려면 애노테이션 선언에 @kotlin.annotation.Repeatable만 있으면 돼요. 1.6.0 이전에는 이 기능이 SOURCE 보존만 지원했고 Java의 반복 가능 애노테이션과 호환되지 않았어요. Kotlin 1.6.0은 이 제한들을 제거해요. @kotlin.annotation.Repeatable은 이제 어떤 보존이든 받아들이고 그 애노테이션을 Kotlin과 Java 양쪽에서 반복 가능하게 만들어요. Java의 반복 가능 애노테이션도 이제 Kotlin 쪽에서 지원돼요.

containing 애노테이션을 선언할 수는 있지만, 반드시 필요한 것은 아니에요. 예를 들어:

  • @Tag 애노테이션이 @kotlin.annotation.Repeatable로 표시되면 Kotlin 컴파일러가 @Tag.Container라는 이름으로 containing 애노테이션 클래스를 자동 생성해요:
    @Repeatable 
    annotation class Tag(val name: String)
    
    // 컴파일러가 @Tag.Container containing 애노테이션을 생성한다
    
  • containing 애노테이션에 커스텀 이름을 설정하려면 @kotlin.jvm.JvmRepeatable 메타-애노테이션을 적용하고 명시적으로 선언한 containing 애노테이션 클래스를 인자로 전달하면 돼요:
    @JvmRepeatable(Tags::class)
    annotation class Tag(val name: String)
    
    annotation class Tags(val value: Array<Tag>)
    

Kotlin 리플렉션은 이제 새 함수 KAnnotatedElement.findAnnotations()를 통해 Kotlin과 Java 양쪽의 반복 가능 애노테이션을 지원해요.

Kotlin 반복 가능 애노테이션을 이 KEEP에서 더 알아보기.

주어진 KProperty 인스턴스에 get/set을 호출하는 위임 프로퍼티 최적화

우리는 $delegate 필드를 생략하고 참조된 프로퍼티에 즉시 접근하도록 생성해서 생성된 JVM 바이트코드를 최적화했어요.

예를 들어 다음 코드에서

class Box<T> {
    private var impl: T = ...

    var content: T by ::impl
}

Kotlin은 더 이상 content$delegate 필드를 생성하지 않아요. content 변수의 프로퍼티 접근자가 impl 변수를 직접 호출해서, 위임 프로퍼티의 getValue/setValue 연산자를 건너뛰고 KProperty 타입의 프로퍼티 참조 객체를 만들 필요도 없어져요.

이 구현은 Google 팀의 도움으로 이뤄졌어요. 감사해요!

위임 프로퍼티에 대해 더 알아보세요.

Kotlin/Native

Kotlin/Native는 여러 개선과 컴포넌트 업데이트를 받고 있으며, 그중 일부는 프리뷰 상태예요:

새 메모리 매니저 프리뷰

Kotlin 1.6.0에서는 새 Kotlin/Native 메모리 매니저의 개발 프리뷰를 시도할 수 있어요. 이것은 JVM과 Native 플랫폼 사이의 차이를 없애 멀티플랫폼 프로젝트에서 일관된 개발자 경험을 제공하는 데 더 가까워지게 해줘요.

주목할 만한 변화 중 하나는 Kotlin/JVM처럼 최상위 프로퍼티의 지연 초기화예요. 최상위 프로퍼티는 같은 파일의 최상위 프로퍼티나 함수가 처음 접근될 때 초기화돼요. 이 모드는 또한 (릴리스 바이너리에만 켜지는) 전역 프로시저 간 최적화를 포함해서 불필요한 초기화 검사를 없애요.

우리는 얼마 전 새 메모리 매니저에 대한 블로그 포스트를 공개했어요. 새 메모리 매니저의 현재 상태와 데모 프로젝트를 알아보려면 그 포스트를 읽어보거나, 직접 시도해 보려면 마이그레이션 지침으로 바로 갈 수 있어요. 새 메모리 매니저가 프로젝트에서 어떻게 동작하는지 확인하고 이슈 트래커 YouTrack에 피드백을 공유해 주세요.

Xcode 13 지원

Kotlin/Native 1.6.0은 Xcode의 최신 버전인 Xcode 13을 지원해요. Xcode를 업데이트하고 Apple 운영체제용 Kotlin 프로젝트 작업을 계속해도 돼요.

어떤 호스트에서든 Windows 타깃 컴파일

1.6.0부터 Windows 타깃 mingwX64mingwX86을 컴파일하는 데 Windows 호스트가 필요하지 않아요. 그 타깃들은 Kotlin/Native를 지원하는 어떤 호스트에서든 컴파일할 수 있어요.

LLVM과 링커 업데이트

우리는 Kotlin/Native가 내부에서 사용하는 LLVM 의존성을 재작업했어요. 이는 다양한 이점을 가져와요:

  • LLVM 버전을 11.1.0으로 업데이트.
  • 의존성 크기 감소. 예를 들어 macOS에서 이전 버전의 약 1200 MB 대신 이제 약 300 MB예요.
  • 최신 Linux 배포판에서 사용할 수 없는 ncurses5 라이브러리에 대한 의존성 제외.

LLVM 업데이트 외에도 Kotlin/Native는 이제 MingGW 타깃에 LLD 링커(LLVM 프로젝트의 링커)를 사용해요. 그것은 이전에 사용하던 ld.bfd 링커보다 다양한 이점을 제공하고, 생성된 바이너리의 런타임 성능을 개선하며 MinGW 타깃용 컴파일러 캐시를 지원하게 해줄 거예요. LLD는 DLL 링크에 import 라이브러리가 필요하다는 점을 유의하세요. 이 Stack Overflow 스레드에서 더 알아보세요.

성능 개선

Kotlin/Native 1.6.0은 다음 성능 개선을 제공해요:

  • 컴파일 시간: 컴파일러 캐시가 linuxX64iosArm64 타깃에서 기본으로 켜져요. 이것은 (첫 컴파일을 제외한) 디버그 모드의 대부분의 컴파일을 빠르게 해줘요. 측정 결과 테스트 프로젝트에서 약 200%의 속도 향상이 있었어요. 컴파일러 캐시는 Kotlin 1.5.0부터 추가 Gradle 프로퍼티로 이 타깃에서 사용할 수 있었는데, 이제 그것들을 제거할 수 있어요.
  • 런타임: for 루프로 배열을 반복하는 것이 생성된 LLVM 코드의 최적화 덕분에 최대 12% 빨라졌어요.

JVM 및 JS IR 백엔드와 통일된 컴파일러 플러그인 ABI

이전 버전에서 컴파일러 플러그인 작성자는 ABI 차이 때문에 Kotlin/Native용 별도 아티팩트를 제공해야 했어요.

1.6.0부터 Kotlin Multiplatform Gradle 플러그인은 Kotlin/Native에 내장 가능한(embeddable) 컴파일러 jar — JVM 및 JS IR 백엔드에 사용되는 것 — 을 사용할 수 있어요. 이제 Native와 다른 지원 플랫폼에서 같은 컴파일러 플러그인 아티팩트를 사용할 수 있으므로, 이는 컴파일러 플러그인 개발 경험을 통일하는 한 걸음이에요.

이것은 그러한 지원의 프리뷰 버전이고 옵트인이 필요해요. Kotlin/Native에 일반 컴파일러 플러그인 아티팩트를 사용하려면 gradle.properties에 다음 줄을 추가하세요: kotlin.native.useEmbeddableCompilerJar=true.

우리는 향후 Kotlin/Native에 내장 가능한 컴파일러 jar를 기본으로 사용할 계획이므로, 프리뷰가 여러분에게 어떻게 동작하는지 듣는 것이 중요해요.

컴파일러 플러그인 작성자라면 이 모드를 시도해서 플러그인에서 동작하는지 확인해 주세요. 플러그인 구조에 따라 마이그레이션 단계가 필요할 수 있다는 점을 유의하세요. 마이그레이션 지침은 이 YouTrack 이슈를 참고하고 댓글로 피드백을 남겨 주세요.

klib 링크 실패에 대한 상세 오류 메시지

Kotlin/Native 컴파일러는 이제 klib 링크 오류에 대한 상세 오류 메시지를 제공해요. 메시지에는 명확한 오류 설명이 있고, 가능한 원인과 해결 방법에 대한 정보도 포함돼요.

예를 들어:

  • 1.5.30:

    e: java.lang.IllegalStateException: IrTypeAliasSymbol expected: Unbound public symbol for public kotlinx.coroutines/CancellationException|null[0]
    <stack trace>
    
  • 1.6.0:

    e: The symbol of unexpected type encountered during IR deserialization: IrClassPublicSymbolImpl, kotlinx.coroutines/CancellationException|null[0].
    IrTypeAliasSymbol is expected.
    
    This could happen if there are two libraries, where one library was compiled against the different version of the other library than the one currently used in the project.
    Please check that the project configuration is correct and has consistent versions of dependencies.
    
    The list of libraries that depend on "org.jetbrains.kotlinx:kotlinx-coroutines-core (org.jetbrains.kotlinx:kotlinx-coroutines-core-macosx64)" and may lead to conflicts:
    <list of libraries and potential version mismatches>
    
    Project dependencies:
    <dependencies tree>
    

재설계된 처리되지 않은 예외 처리 API

우리는 Kotlin/Native 런타임 전체에서 처리되지 않은 예외의 처리를 통일했고, 기본 처리를 processUnhandledException(throwable: Throwable) 함수로 노출해서 kotlinx.coroutines 같은 커스텀 실행 환경에서 사용할 수 있게 했어요. 이 처리는 Worker.executeAfter()에서 작업을 벗어나는 예외에도 적용되지만, 새 메모리 매니저에서만 적용돼요.

API 개선은 setUnhandledExceptionHook()으로 설정된 훅에도 영향을 줬어요. 이전에는 Kotlin/Native 런타임이 처리되지 않은 예외로 훅을 호출한 뒤 그러한 훅이 재설정되고, 프로그램이 항상 바로 종료됐어요. 이제 그러한 훅은 여러 번 사용될 수 있고, 처리되지 않은 예외에서 프로그램이 항상 종료되길 원하면 처리되지 않은 예외 훅(setUnhandledExceptionHook())을 설정하지 않거나, 훅 끝에서 terminateWithUnhandledException()을 호출해야 해요. 이것은 예외를 제3자 크래시 리포팅 서비스(예: Firebase Crashlytics)로 보낸 다음 프로그램을 종료하는 데 도움이 돼요. main()을 벗어나는 예외와 interop 경계를 넘는 예외는 훅이 terminateWithUnhandledException()을 호출하지 않았더라도 항상 프로그램을 종료해요.

Kotlin/JS

우리는 Kotlin/JS 컴파일러의 IR 백엔드를 안정화하는 작업을 계속하고 있어요. 이제 Kotlin/JS는 Node.js 및 Yarn 다운로드를 끄는 옵션이 있어요.

사전 설치된 Node.js 및 Yarn 사용 옵션

이제 Kotlin/JS 프로젝트를 빌드할 때 Node.js와 Yarn의 다운로드를 끄고 호스트에 이미 설치된 인스턴스를 사용할 수 있어요. CI 서버처럼 인터넷 연결이 없는 서버에서 빌드할 때 유용해요.

외부 컴포넌트 다운로드를 끄려면 build.gradle(.kts)에 다음 줄을 추가하면 돼요:

  • Yarn:
    rootProject.plugins.withType<org.jetbrains.kotlin.gradle.targets.js.yarn.YarnPlugin> {
        rootProject.the<org.jetbrains.kotlin.gradle.targets.js.yarn.YarnRootExtension>().download = false // 또는 기본 동작을 위해 true
    }
    
    rootProject.plugins.withType(org.jetbrains.kotlin.gradle.targets.js.yarn.YarnPlugin) {
        rootProject.extensions.getByType(org.jetbrains.kotlin.gradle.targets.js.yarn.YarnRootExtension).download = false
    }
    
  • Node.js:
    rootProject.plugins.withType<org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin> {
        rootProject.the<org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension>().download = false // 또는 기본 동작을 위해 true
    }
    
    rootProject.plugins.withType(org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin) {
        rootProject.extensions.getByType(org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension).download = false
    }
    

Kotlin Gradle 플러그인

Kotlin 1.6.0에서 KotlinGradleSubplugin 클래스의 deprecation 수준을 'ERROR'로 바꿨어요. 이 클래스는 컴파일러 플러그인을 작성하는 데 사용됐어요. 다음 릴리스들에서 이 클래스를 제거할 거예요. 대신 KotlinCompilerPluginSupportPlugin 클래스를 사용하세요.

kotlin.useFallbackCompilerSearch 빌드 옵션과 noReflect, includeRuntime 컴파일러 옵션을 제거했어요. useIR 컴파일러 옵션은 숨겨졌고 다음 릴리스에서 제거될 예정이에요.

Kotlin Gradle 플러그인의 현재 지원되는 컴파일러 옵션에 대해 더 알아보세요.

표준 라이브러리

새 1.6.0 표준 라이브러리는 실험 기능을 안정화하고, 새 기능을 도입하며, 플랫폼 간 동작을 통일해요:

새 readline 함수

Kotlin 1.6.0은 표준 입력을 처리하는 새 함수들을 제공해요: readln()readlnOrNull().

readLine() readln()
String? 반환 String 반환
EOF에서 null 반환 EOF에서 예외 발생

한 줄을 읽을 때 !!를 쓸 필요를 없애는 것이 초보자 경험을 개선하고 Kotlin을 가르치는 것을 단순화할 것이라고 믿어요. 읽기 연산의 이름을 println() 상대편과 일관되게 하기 위해 새 함수의 이름을 'ln'으로 줄이기로 했어요.

println("What is your nickname?")
val nickname = readln()
println("Hello, $nickname!")
fun main() {
//sampleStart
    var sum = 0
    while (true) {
        val nextLine = readlnOrNull().takeUnless { 
            it.isNullOrEmpty() 
        } ?: break
        sum += nextLine.toInt()
    }
    println(sum)
//sampleEnd
}

기존 readLine() 함수는 IDE 코드 완성에서 readln()readlnOrNull()보다 낮은 우선순위를 받을 거예요. IDE 검사도 기존 readLine() 대신 새 함수를 추천할 거예요.

우리는 향후 릴리스에서 readLine() 함수를 점진적으로 deprecate할 계획이에요.

안정적인 typeOf()

버전 1.6.0은 Stable typeOf() 함수를 가져와 주요 로드맵 항목 중 하나를 마무리해요.

1.3.40부터 typeOf()는 JVM 플랫폼에서 실험적 API로 사용할 수 있었어요. 이제 어떤 Kotlin 플랫폼에서든 사용해서 컴파일러가 추론할 수 있는 어떤 Kotlin 타입의 KType 표현을 얻을 수 있어요:

inline fun <reified T> renderType(): String {
    val type = typeOf<T>()
    return type.toString()
}

fun main() {
    val fromExplicitType = typeOf<Int>()
    val fromReifiedType = renderType<List<Int>>()
}

안정적인 컬렉션 빌더

Kotlin 1.6.0에서 컬렉션 빌더 함수가 Stable로 승격됐어요. 컬렉션 빌더가 반환하는 컬렉션은 이제 읽기 전용 상태에서 직렬화 가능해요.

이제 buildMap(), buildList(), buildSet()을 opt-in 애노테이션 없이 사용할 수 있어요:

fun main() {
//sampleStart
    val x = listOf('b', 'c')
    val y = buildList {
        add('a')
        addAll(x)
        add('d')
    }
    println(y)  // [a, b, c, d]
//sampleEnd
}

안정적인 Duration API

다른 시간 단위로 지속 시간을 나타내는 Duration 클래스가 Stable로 승격됐어요. 1.6.0에서 Duration API는 다음 변화를 받았어요:

  • 지속 시간을 일, 시간, 분, 초, 나노초로 분해하는 toComponents() 함수의 첫 컴포넌트가 Int 대신 Long 타입이 됐어요. 이전에는 값이 Int 범위에 맞지 않으면 그 범위로 강제 변환됐어요. Long 타입 덕분에 Int에 맞지 않는 값을 잘라내지 않고 지속 시간 범위의 어떤 값이든 분해할 수 있어요.
  • DurationUnit enum은 이제 독립적이며 JVM에서 java.util.concurrent.TimeUnit의 타입 별칭이 아니에요. typealias DurationUnit = TimeUnit이 유용할 만한 설득력 있는 사례를 찾지 못했어요. 또한 타입 별칭을 통해 TimeUnit API를 노출하면 DurationUnit 사용자를 혼란스럽게 할 수 있어요.
  • 커뮤니티 피드백에 응답해서 Int.seconds 같은 확장 프로퍼티를 되돌려 가져와요. 하지만 그 적용 범위를 제한하고 싶어서 그것들을 Duration 클래스의 컴패니언에 넣었어요. IDE는 여전히 완성에서 확장을 제안하고 컴패니언에서 import를 자동으로 삽입할 수 있지만, 향후에는 이 동작을 Duration 타입이 기대되는 경우로 제한할 계획이에요.
    import kotlin.time.Duration.Companion.seconds
    
    fun main() {
    //sampleStart
        val duration = 10000
        println("There are ${duration.seconds.inWholeMinutes} minutes in $duration seconds")
        // There are 166 minutes in 10000 seconds
    //sampleEnd
    }
    
    이전에 도입한 Duration.seconds(Int) 같은 컴패니언 함수와 Int.seconds 같은 deprecated 최상위 확장을 Duration.Companion의 새 확장으로 교체하는 것을 권장해요.

Regex를 시퀀스로 분할

Regex.splitToSequence(CharSequence)CharSequence.splitToSequence(Regex) 함수가 Stable로 승격됐어요. 이 함수들은 주어진 정규식의 매치 주변에서 문자열을 나누지만, 결과를 Sequence로 반환해서 그 결과에 대한 모든 연산이 지연 실행돼요:

fun main() {
//sampleStart
    val colorsText = "green, red, brown&blue, orange, pink&green"
    val regex = "[,\\s]+".toRegex()
    val mixedColor = regex.splitToSequence(colorsText)
    // or
    // val mixedColor = colorsText.splitToSequence(regex)
        .onEach { println(it) }
        .firstOrNull { it.contains('&') }
    println(mixedColor) // "brown&blue"
//sampleEnd
}

정수의 비트 회전 연산

Kotlin 1.6.0에서 비트 조작을 위한 rotateLeft()rotateRight() 함수가 Stable이 됐어요. 이 함수들은 숫자의 이진 표현을 지정된 비트 수만큼 왼쪽이나 오른쪽으로 회전해요:

fun main() {
//sampleStart
    val number: Short = 0b10001
    println(number
        .rotateRight(2)
        .toString(radix = 2)) // 100000000000100
    println(number
        .rotateLeft(2)
        .toString(radix = 2))  // 1000100
//sampleEnd
}

JS에서 replace()와 replaceFirst()의 변화

Kotlin 1.6.0 이전에는 대체 문자열에 그룹 참조가 포함되었을 때 replace()replaceFirst() Regex 함수가 Java와 JS에서 다르게 동작했어요. 모든 타깃 플랫폼에서 동작을 일관되게 만들기 위해 JS에서 그 구현을 바꿨어요.

대체 문자열에서 ${name}이나 $index의 발생은 지정된 인덱스나 이름을 가진 캡처된 그룹에 해당하는 부분 문자열로 대체돼요:

  • $index$ 뒤의 첫 번째 숫자는 항상 그룹 참조의 일부로 취급돼요. 뒤따르는 숫자들은 유효한 그룹 참조를 형성할 때만 index에 포함돼요. 숫자 '0'–'9'만 그룹 참조의 잠재적 컴포넌트로 간주돼요. 캡처된 그룹의 인덱스는 '1'부터 시작한다는 점을 유의하세요. 인덱스 '0'의 그룹은 전체 매치를 나타내요.
  • ${name}name은 라틴 문자 'a'–'z', 'A'–'Z' 또는 숫자 '0'–'9'로 구성될 수 있어요. 첫 문자는 반드시 문자(letter)여야 해요.
  • 뒤따르는 문자를 대체 문자열에 리터럴로 포함하려면 백슬래시 문자 \를 사용하면 돼요:
    fun main() {
    //sampleStart
        println(Regex("(.+)").replace("Kotlin", """\$ $1""")) // $ Kotlin
        println(Regex("(.+)").replaceFirst("1.6.0", """\\ $1""")) // \ 1.6.0
    //sampleEnd
    }
    
    대체 문자열을 리터럴 문자열로 취급해야 한다면 Regex.escapeReplacement()를 사용할 수 있어요.

기존 API 개선

  • 버전 1.6.0은 Comparable.compareTo()에 infix 확장 함수를 추가했어요. 이제 두 객체의 순서를 비교할 때 infix 형태를 사용할 수 있어요:
     class WrappedText(val text: String) : Comparable<WrappedText> {
         override fun compareTo(other: WrappedText): Int =
             this.text compareTo other.text
    }
    
  • JS의 Regex.replace()도 이제 모든 플랫폼에서 구현을 통일하기 위해 더 이상 inline이 아니에요.
  • compareTo()equals() String 함수 및 isBlank() CharSequence 함수가 이제 JS에서 JVM에서와 정확히 같이 동작해요. 이전에는 비-ASCII 문자와 관련해 편차가 있었어요.

Deprecations

Kotlin 1.6.0에서 일부 JS 전용 stdlib API에 대해 경고와 함께 deprecation 주기를 시작해요.

concat(), match(), matches() 문자열 함수

  • 문자열을 주어진 다른 객체의 문자열 표현과 연결하려면 concat() 대신 plus()를 사용해요.
  • 입력 안의 정규식의 모든 발생을 찾으려면 String.match(regex: String) 대신 Regex 클래스의 findAll()을 사용해요.
  • 정규식이 전체 입력과 일치하는지 확인하려면 String.matches(regex: String) 대신 Regex 클래스의 matches()를 사용해요.

비교 함수를 받는 배열의 sort()

비교 함수가 전달한 순서를 따라 배열을 정렬하던 Array<out T>.sort() 함수와 inline 함수들(ByteArray.sort(), ShortArray.sort(), IntArray.sort(), LongArray.sort(), FloatArray.sort(), DoubleArray.sort(), CharArray.sort())을 deprecated 했어요. 배열 정렬에는 다른 표준 라이브러리 함수를 사용하세요.

컬렉션 순서 섹션을 참고하세요.

도구 (Tools)

Kover – Kotlin용 코드 커버리지 도구

Kotlin 1.6.0과 함께 Kover를 소개해요. IntelliJJaCoCo Kotlin 코드 커버리지 에이전트를 위한 Gradle 플러그인이에요. inline 함수를 포함한 모든 언어 구조에서 동작해요.

Kover에 대해 GitHub 저장소나 아래 영상에서 더 알아보세요.

Coroutines 1.6.0-RC

kotlinx.coroutines 1.6.0-RC가 여러 기능과 개선과 함께 나왔어요:

  • 새 Kotlin/Native 메모리 매니저 지원
  • 추가 스레드를 만들지 않고 병렬성을 제한할 수 있는 dispatcher views API 도입
  • Java 6에서 Java 8 타깃으로 마이그레이션
  • 새로 재작업된 API와 멀티플랫폼 지원을 가진 kotlinx-coroutines-test
  • 코루틴에 ThreadLocal 변수에 대한 스레드 안전한 쓰기 접근을 주는 CopyableThreadContextElement 도입

changelog에서 더 알아보세요.

Kotlin 1.6.0으로 마이그레이션

IntelliJ IDEA와 Android Studio는 Kotlin 플러그인 1.6.0이 사용 가능해지면 업데이트를 제안할 거예요.

기존 프로젝트를 Kotlin 1.6.0으로 마이그레이션하려면 Kotlin 버전을 1.6.0으로 바꾸고 Gradle 또는 Maven 프로젝트를 다시 임포트하면 돼요. Kotlin 1.6.0으로 업데이트하는 방법 알아보기.

Kotlin 1.6.0으로 새 프로젝트를 시작하려면 Kotlin 플러그인을 업데이트하고 File | New | Project에서 Project Wizard를 실행하면 돼요.

새 커맨드라인 컴파일러는 GitHub 릴리스 페이지에서 다운로드할 수 있어요.

Kotlin 1.6.0은 기능 릴리스이므로 이전 버전의 언어로 작성된 코드와 호환되지 않는 변화를 가져올 수 있어요. 그런 변화의 자세한 목록은 Kotlin 1.6 호환성 가이드에서 확인할 수 있어요.

더 알아보기