Skip to content

Kotlin 1.5.0의 새로운 기능

출시일: 2021년 5월 5일

Kotlin 1.5.0에서는 새로운 언어 기능, 안정화된 IR 기반 JVM 컴파일러 백엔드, 성능 향상, 그리고 실험적 기능의 안정화 및 오래된 기능의 지원 중단과 같은 점진적인 변화가 도입되었습니다.

출시 블로그 포스트에서도 변경 사항에 대한 개요를 확인할 수 있습니다.

Kotlin 출시 주기에 대한 정보는 Kotlin 출시 프로세스를 참조하세요.

언어 기능 (Language features)

Kotlin 1.5.0은 1.4.30에서 미리보기로 선보였던 새로운 언어 기능들의 안정 버전을 제공합니다.

이러한 기능들에 대한 자세한 설명은 이 블로그 포스트와 Kotlin 문서의 해당 페이지에서 확인할 수 있습니다.

JVM 레코드 지원

Java는 빠르게 진화하고 있으며, Kotlin이 Java와 상호 운용성을 유지할 수 있도록 최신 기능 중 하나인 레코드 클래스(record classes)에 대한 지원을 도입했습니다.

Kotlin의 JVM 레코드 지원에는 양방향 상호 운용성이 포함됩니다.

  • Kotlin 코드에서는 Java 레코드 클래스를 속성이 있는 일반 클래스처럼 사용할 수 있습니다.
  • Java 코드에서 Kotlin 클래스를 레코드로 사용하려면, 해당 클래스를 data 클래스로 만들고 @JvmRecord 어노테이션을 붙이세요.
kotlin
@JvmRecord
data class User(val name: String, val age: Int)

Kotlin에서 JVM 레코드 사용에 대해 더 알아보기.

봉인된 인터페이스 (Sealed interfaces)

이제 Kotlin 인터페이스에 sealed 수정자를 사용할 수 있습니다. 이는 클래스에서 작동하는 방식과 동일하게 인터페이스에서도 작동합니다. 즉, 봉인된 인터페이스의 모든 구현체는 컴파일 타임에 알 수 있습니다.

kotlin
sealed interface Polygon

이 사실을 활용하여, 예를 들어 철저한(exhaustive) when 표현식을 작성할 수 있습니다.

kotlin
fun draw(polygon: Polygon) = when (polygon) {
   is Rectangle -> // ...
   is Triangle -> // ...
   // else가 필요하지 않음 - 가능한 모든 구현이 처리됨
}

또한, 클래스가 둘 이상의 봉인된 인터페이스를 직접 상속할 수 있으므로, 봉인된 인터페이스를 통해 더 유연하게 제한된 클래스 계층 구조를 만들 수 있습니다.

kotlin
class FilledRectangle: Polygon, Fillable

봉인된 인터페이스에 대해 더 알아보기.

패키지 범위의 봉인된 클래스 계층 구조

이제 동일한 컴파일 단위 및 동일한 패키지의 모든 파일에서 봉인된 클래스의 서브클래스를 가질 수 있습니다. 이전에는 모든 서브클래스가 동일한 파일에 있어야 했습니다.

직접적인 서브클래스는 최상위 수준이거나 임의의 수의 다른 이름이 지정된 클래스, 인터페이스 또는 객체 내부에 중첩될 수 있습니다.

봉인된 클래스의 서브클래스는 적절한 이름(qualified name)을 가져야 하며, 로컬 또는 익명 객체일 수 없습니다.

봉인된 클래스 계층 구조에 대해 더 알아보기.

인라인 클래스 (Inline classes)

인라인 클래스는 값을 하나만 보유하는 값 기반(value-based) 클래스의 하위 집합입니다. 메모리 할당으로 인한 추가 오버헤드 없이 특정 타입의 값에 대한 래퍼(wrapper)로 사용할 수 있습니다.

인라인 클래스는 클래스 이름 앞에 value 수정자를 붙여 선언할 수 있습니다.

kotlin
value class Password(val s: String)

JVM 백엔드에서는 특수한 @JvmInline 어노테이션도 필요합니다.

kotlin
@JvmInline
value class Password(val s: String)

기존의 inline 수정자는 이제 경고와 함께 지원이 중단(deprecated)되었습니다.

인라인 클래스에 대해 더 알아보기.

Kotlin/JVM

Kotlin/JVM에는 내부적인 개선 사항과 사용자 대면 개선 사항이 모두 포함되었습니다. 가장 주목할 만한 사항은 다음과 같습니다.

안정적인 JVM IR 백엔드

Kotlin/JVM 컴파일러를 위한 IR 기반 백엔드가 이제 안정화(Stable)되었으며 기본적으로 활성화됩니다.

Kotlin 1.4.0부터 IR 기반 백엔드의 초기 버전을 미리 볼 수 있었으며, 이제 언어 버전 1.5부터 기본값이 되었습니다. 이전 백엔드는 이전 언어 버전에서 여전히 기본적으로 사용됩니다.

IR 백엔드의 장점과 향후 개발 계획에 대한 자세한 내용은 이 블로그 포스트에서 확인할 수 있습니다.

Kotlin 1.5.0에서 이전 백엔드를 사용해야 하는 경우, 프로젝트 설정 파일에 다음 줄을 추가할 수 있습니다.

  • Gradle의 경우:
kotlin
tasks.withType<org.jetbrains.kotlin.gradle.dsl.KotlinJvmCompile> {
  kotlinOptions.useOldBackend = true
}
groovy
tasks.withType(org.jetbrains.kotlin.gradle.dsl.KotlinJvmCompile) {
 kotlinOptions.useOldBackend = true
}
  • Maven의 경우:
xml
<configuration>
    <args>
        <arg>-Xuse-old-backend</arg>
    </args>
</configuration>

새로운 기본 JVM 타겟: 1.8

Kotlin/JVM 컴파일의 기본 타겟 버전이 이제 1.8이 되었습니다. 1.6 타겟은 지원 중단되었습니다.

JVM 1.6용 빌드가 필요한 경우 여전히 이 타겟으로 전환할 수 있습니다. 방법은 다음과 같습니다.

invokedynamic을 통한 SAM 어댑터

Kotlin 1.5.0은 이제 SAM(Single Abstract Method) 변환을 컴파일할 때 동적 호출(invokedynamic)을 사용합니다.

새로운 구현은 LambdaMetafactory.metafactory()를 사용하며, 컴파일 중에 더 이상 보조 래퍼 클래스가 생성되지 않습니다. 이를 통해 애플리케이션의 JAR 크기가 줄어들어 JVM 시작 성능이 향상됩니다.

익명 클래스 생성을 기반으로 하는 이전 구현 방식으로 되돌리려면 컴파일러 옵션 -Xsam-conversions=class를 추가하세요.

Gradle, Maven, 명령줄 컴파일러에서 컴파일러 옵션을 추가하는 방법을 알아보세요.

invokedynamic을 통한 람다

일반 Kotlin 람다를 invokedynamic으로 컴파일하는 기능은 실험적(Experimental)입니다. 이는 언제든지 삭제되거나 변경될 수 있습니다. 옵트인(Opt-in)이 필요하며(아래 상세 내용 참조), 평가 목적으로만 사용해야 합니다. YouTrack을 통해 이에 대한 의견을 보내주시면 감사하겠습니다.

Kotlin 1.5.0은 일반 Kotlin 람다(함수형 인터페이스의 인스턴스로 변환되지 않는 람다)를 동적 호출(invokedynamic)로 컴파일하는 실험적 지원을 도입합니다. 이 구현은 런타임에 필요한 클래스를 효과적으로 생성하는 LambdaMetafactory.metafactory()를 사용하여 더 가벼운 바이너리를 생성합니다. 현재 일반적인 람다 컴파일과 비교하여 세 가지 제한 사항이 있습니다.

  • invokedynamic으로 컴파일된 람다는 직렬화할 수 없습니다.
  • 이러한 람다에서 toString()을 호출하면 가독성이 떨어지는 문자열 표현이 생성됩니다.
  • 실험적인 reflect API는 LambdaMetafactory로 생성된 람다를 지원하지 않습니다.

이 기능을 시도하려면 -Xlambdas=indy 컴파일러 옵션을 추가하세요. 이 YouTrack 티켓을 통해 피드백을 공유해 주시면 감사하겠습니다.

Gradle, Maven, 명령줄 컴파일러에서 컴파일러 옵션을 추가하는 방법을 알아보세요.

@JvmDefault 및 이전 Xjvm-default 모드 지원 중단

Kotlin 1.4.0 이전에는 -Xjvm-default=enable-Xjvm-default=compatibility 모드와 함께 @JvmDefault 어노테이션이 있었습니다. 이들은 Kotlin 인터페이스의 특정 비추상 멤버에 대해 JVM 기본 메서드(default method)를 만드는 데 사용되었습니다.

Kotlin 1.4.0에서는 전체 프로젝트에 대해 기본 메서드 생성을 활성화하는 새로운 Xjvm-default 모드를 도입했습니다.

Kotlin 1.5.0에서는 @JvmDefault와 이전 Xjvm-default 모드인 -Xjvm-default=enable-Xjvm-default=compatibility를 지원 중단합니다.

Java 상호 운용성에서의 기본 메서드에 대해 더 알아보기.

널 허용 여부(nullability) 어노테이션 처리 개선

Kotlin은 널 허용 여부 어노테이션을 통해 Java의 타입 널 허용 여부 정보 처리를 지원합니다. Kotlin 1.5.0에서는 이 기능에 대한 여러 개선 사항이 도입되었습니다.

  • 종속성으로 사용되는 컴파일된 Java 라이브러리의 타입 인자에 있는 널 허용 여부 어노테이션을 읽습니다.
  • 다음 항목에 대해 TYPE_USE 타겟이 있는 널 허용 여부 어노테이션을 지원합니다.
    • 배열(Arrays)
    • 가변 인자(Varargs)
    • 필드(Fields)
    • 타입 파라미터 및 그 경계(bounds)
    • 베이스 클래스 및 인터페이스의 타입 인자
  • 널 허용 여부 어노테이션에 타입에 적용 가능한 여러 타겟이 있고 그 중 하나가 TYPE_USE인 경우, TYPE_USE가 우선적으로 사용됩니다. 예를 들어, @NullableTYPE_USEMETHOD를 모두 타겟으로 지원한다면 메서드 시그니처 @Nullable String[] f()fun f(): Array<String?>!가 됩니다.

새롭게 지원되는 이러한 경우에 대해 Kotlin에서 Java를 호출할 때 잘못된 타입 널 허용 여부를 사용하면 경고가 발생합니다. 이러한 경우에 대해 엄격 모드(strict mode, 오류 보고 포함)를 활성화하려면 -Xtype-enhancement-improvements-strict-mode 컴파일러 옵션을 사용하세요.

널 안전성 및 플랫폼 타입에 대해 더 알아보기.

Kotlin/Native

Kotlin/Native는 이제 성능과 안정성이 더욱 향상되었습니다. 주목할 만한 변경 사항은 다음과 같습니다.

성능 향상

1.5.0에서 Kotlin/Native는 컴파일과 실행 속도를 모두 높이는 일련의 성능 개선 사항을 적용받았습니다.

컴파일러 캐시가 이제 linuxX64(Linux 호스트에서만) 및 iosArm64 타겟의 디버그 모드에서 지원됩니다. 컴파일러 캐시를 활성화하면 첫 번째 컴파일을 제외한 대부분의 디버그 컴파일이 훨씬 빠르게 완료됩니다. 테스트 프로젝트 측정 결과 약 200%의 속도 향상을 보였습니다.

새로운 타겟에 대해 컴파일러 캐시를 사용하려면 프로젝트의 gradle.properties에 다음 줄을 추가하여 옵트인하세요.

  • linuxX64 타겟: kotlin.native.cacheKind.linuxX64=static
  • iosArm64 타겟: kotlin.native.cacheKind.iosArm64=static

컴파일러 캐시를 활성화한 후 문제가 발생하면 이슈 트래커인 YouTrack에 보고해 주세요.

다른 개선 사항들은 Kotlin/Native 코드의 실행 속도를 높여줍니다.

  • 사소한 속성 접근자(trivial property accessors)가 인라인화됩니다.
  • 문자열 리터럴의 trimIndent()가 컴파일 중에 평가됩니다.

메모리 누수 검사기 비활성화

내장된 Kotlin/Native 메모리 누수 검사기가 기본적으로 비활성화되었습니다.

이 검사기는 원래 내부용으로 설계되었으며, 모든 경우가 아닌 제한된 수의 사례에서만 누수를 찾을 수 있습니다. 또한, 나중에 애플리케이션 충돌을 일으킬 수 있는 이슈가 있는 것으로 밝혀졌습니다. 따라서 메모리 누수 검사기를 끄기로 결정했습니다.

메모리 누수 검사기는 유닛 테스트와 같은 특정 사례에서 여전히 유용할 수 있습니다. 이러한 경우 다음 코드 줄을 추가하여 활성화할 수 있습니다.

kotlin
Platform.isMemoryLeakCheckerActive = true

애플리케이션 런타임에 대해 검사기를 활성화하는 것은 권장되지 않습니다.

Kotlin/JS

Kotlin/JS는 1.5.0에서 점진적인 변화를 맞이하고 있습니다. JS IR 컴파일러 백엔드를 안정화 단계로 옮기기 위한 작업을 계속하고 있으며 다른 업데이트들도 배포하고 있습니다.

webpack 5로 업그레이드

이제 Kotlin/JS Gradle 플러그인은 브라우저 타겟에 대해 webpack 4 대신 webpack 5를 사용합니다. 이는 호환되지 않는 변경 사항을 포함하는 주요 webpack 업그레이드입니다. 사용자 정의 webpack 설정을 사용 중이라면 webpack 5 출시 노트를 반드시 확인하세요.

webpack을 사용한 Kotlin/JS 프로젝트 번들링에 대해 더 알아보기.

IR 컴파일러용 프레임워크 및 라이브러리

Kotlin/JS IR 컴파일러는 알파(Alpha) 단계에 있습니다. 향후 호환되지 않는 변경이 발생할 수 있으며 수동 마이그레이션이 필요할 수 있습니다. YouTrack을 통해 의견을 보내주시면 감사하겠습니다.

Kotlin/JS 컴파일러용 IR 기반 백엔드 작업과 함께, 라이브러리 저자들이 프로젝트를 both 모드로 빌드하도록 장려하고 돕고 있습니다. 이는 두 Kotlin/JS 컴파일러 모두를 위한 아티팩트를 생성할 수 있음을 의미하며, 결과적으로 새 컴파일러를 위한 생태계를 성장시킵니다.

KVision, fritz2, doodle 등 이미 많은 유명 프레임워크와 라이브러리가 IR 백엔드에서 사용 가능합니다. 프로젝트에서 이러한 라이브러리를 사용하고 있다면 이미 IR 백엔드로 빌드하여 그 이점을 확인할 수 있습니다.

직접 라이브러리를 작성하고 있다면 'both' 모드로 컴파일하여 사용자가 새 컴파일러에서도 해당 라이브러리를 사용할 수 있도록 하세요.

Kotlin 멀티플랫폼

Kotlin 1.5.0에서는 각 플랫폼에 대한 테스트 종속성 선택이 단순화되었으며, 이제 Gradle 플러그인에 의해 자동으로 수행됩니다.

새로운 문자 카테고리를 가져오기 위한 API가 이제 멀티플랫폼 프로젝트에서 사용 가능합니다.

표준 라이브러리 (Standard library)

표준 라이브러리는 실험적 기능의 안정화부터 새로운 기능 추가에 이르기까지 다양한 변경과 개선이 이루어졌습니다.

표준 라이브러리 변경 사항에 대한 자세한 내용은 이 블로그 포스트에서 확인할 수 있습니다.

안정적인 부호 없는 정수 타입

UInt, ULong, UByte, UShort 부호 없는 정수 타입이 이제 안정화(Stable)되었습니다. 이러한 타입에 대한 연산, 범위(ranges), 진행(progressions)도 마찬가지입니다. 부호 없는 배열 및 그 연산은 베타 단계로 유지됩니다.

부호 없는 정수 타입에 대해 더 알아보기.

대소문자 변환을 위한 안정적인 로케일 중립적 API

이번 릴리스에서는 대소문자 텍스트 변환을 위한 로케일에 무관한(locale-agnostic) 새로운 API를 도입했습니다. 이는 로케일에 민감한 toLowerCase(), toUpperCase(), capitalize(), decapitalize() API 함수에 대한 대안을 제공합니다. 새로운 API를 사용하면 서로 다른 로케일 설정으로 인한 오류를 피할 수 있습니다.

Kotlin 1.5.0은 다음과 같이 완전히 안정적인(Stable) 대안을 제공합니다.

  • String 함수의 경우:

    이전 버전1.5.0 대안
    String.toUpperCase()String.uppercase()
    String.toLowerCase()String.lowercase()
    String.capitalize()String.replaceFirstChar { it.uppercase() }
    String.decapitalize()String.replaceFirstChar { it.lowercase() }
  • Char 함수의 경우:

    이전 버전1.5.0 대안
    Char.toUpperCase()Char.uppercaseChar(): Char
    Char.uppercase(): String
    Char.toLowerCase()Char.lowercaseChar(): Char
    Char.lowercase(): String
    Char.toTitleCase()Char.titlecaseChar(): Char
    Char.titlecase(): String

Kotlin/JVM의 경우 명시적인 Locale 파라미터를 갖는 오버로드된 uppercase(), lowercase(), titlecase() 함수도 있습니다.

기존의 API 함수들은 지원 중단으로 표시되었으며 향후 릴리스에서 제거될 예정입니다.

텍스트 처리 함수의 전체 변경 목록은 KEEP에서 확인하세요.

안정적인 Char-to-integer 변환 API

Kotlin 1.5.0부터 새로운 char-to-code 및 char-to-digit 변환 함수가 안정화되었습니다. 이 함수들은 종종 유사한 string-to-Int 변환과 혼동되었던 기존 API 함수들을 대체합니다.

새로운 API는 이러한 명명 혼란을 제거하여 코드 동작을 더 투명하고 명확하게 만듭니다.

이번 릴리스에서는 다음과 같이 명확하게 명명된 함수 세트로 나누어진 Char 변환을 도입합니다.

  • Char의 정수 코드를 가져오고 주어진 코드에서 Char를 구성하는 함수:
kotlin
fun Char(code: Int): Char
fun Char(code: UShort): Char
val Char.code: Int
  • Char를 그것이 나타내는 숫자의 수치 값으로 변환하는 함수:
kotlin
fun Char.digitToInt(radix: Int): Int
fun Char.digitToIntOrNull(radix: Int): Int?
  • Int에 대한 확장 함수로, 그것이 나타내는 음이 아닌 단일 숫자를 해당하는 Char 표현으로 변환하는 함수:
kotlin
fun Int.digitToChar(radix: Int): Char

Int.toChar()를 제외한 모든 구현을 포함하는 Number.toChar()Char.toInt()와 같이 수치 타입으로의 변환을 위한 Char 확장 함수를 포함한 기존 변환 API들은 이제 지원 중단되었습니다.

KEEP에서 char-to-integer 변환 API에 대해 더 알아보기.

안정적인 Path API

java.nio.file.Path에 대한 확장 기능을 포함한 실험적인 Path API가 이제 안정화되었습니다.

kotlin
// div (/) 연산자로 경로 구성
val baseDir = Path("/base")
val subDir = baseDir / "subdirectory"

// 디렉토리 내 파일 목록 나열
val kotlinFiles: List<Path> = Path("/home/user").listDirectoryEntries("*.kt")

Path API에 대해 더 알아보기.

내림 나눗셈(Floored division) 및 mod 연산자

모듈러 산술(modular arithmetics)을 위한 새로운 연산이 표준 라이브러리에 추가되었습니다.

  • floorDiv()내림 나눗셈(floored division)의 결과를 반환합니다. 정수 타입에 대해 사용할 수 있습니다.
  • mod()는 내림 나눗셈의 나머지(modulus)를 반환합니다. 모든 수치 타입에 대해 사용할 수 있습니다.

이러한 연산은 기존의 정수 나눗셈rem() 함수(또는 % 연산자)와 매우 유사해 보이지만, 음수에 대해 다르게 작동합니다.

  • a.floorDiv(b)floorDiv가 결과를 내림(더 작은 정수 쪽으로)하는 반면, /는 결과를 0에 더 가까운 정수로 자른다(truncate)는 점에서 일반적인 /와 다릅니다.
  • a.mod(b)aa.floorDiv(b) * b 사이의 차이입니다. 결과는 0이거나 b와 같은 부호를 갖는 반면, a % b는 다른 부호를 가질 수 있습니다.
kotlin
fun main() {
    println("내림 나눗셈 -5/3: ${(-5).floorDiv(3)}")
    println( "나머지(Modulus): ${(-5).mod(3)}")
    
    println("절삭 나눗셈 -5/3: ${-5 / 3}")
    println( "나머지(Remainder): ${-5 % 3}")
}

Duration API 변경 사항

Duration API는 실험적(Experimental)입니다. 이는 언제든지 삭제되거나 변경될 수 있습니다. 평가 목적으로만 사용하세요. YouTrack을 통해 이에 대한 의견을 보내주시면 감사하겠습니다.

다양한 시간 단위로 기간(duration) 양을 표현하기 위한 실험적인 Duration 클래스가 있습니다. 1.5.0에서 Duration API는 다음과 같은 변경 사항이 적용되었습니다.

  • 내부 값 표현에 Double 대신 Long을 사용하여 더 나은 정밀도를 제공합니다.
  • Long 타입의 특정 시간 단위로 변환하기 위한 새로운 API가 도입되었습니다. 이는 Double 값으로 작동하며 현재 지원 중단된 기존 API를 대체합니다. 예를 들어, Duration.inWholeMinutesLong으로 표현된 기간 값을 반환하며 Duration.inMinutes를 대체합니다.
  • 숫자로부터 Duration을 구성하기 위한 새로운 동반(companion) 함수들이 추가되었습니다. 예를 들어, Duration.seconds(Int)는 정수 초를 나타내는 Duration 객체를 생성합니다. Int.seconds와 같은 기존 확장 속성들은 이제 지원 중단되었습니다.
kotlin
import kotlin.time.Duration
import kotlin.time.ExperimentalTime

@ExperimentalTime
fun main() {
    val duration = Duration.milliseconds(120000)
    println("${duration.inWholeMinutes}분은 ${duration.inWholeSeconds}초입니다.")
}

멀티플랫폼 코드에서 문자 카테고리를 가져오기 위한 새로운 API 사용 가능

Kotlin 1.5.0은 멀티플랫폼 프로젝트에서 Unicode에 따른 문자의 카테고리를 가져오기 위한 새로운 API를 도입했습니다. 이제 여러 함수를 모든 플랫폼과 공통 코드에서 사용할 수 있습니다.

문자가 문자인지 숫자인지 확인하는 함수:

kotlin
fun main() {
    val chars = listOf('a', '1', '+')
    val (letterOrDigitList, notLetterOrDigitList) = chars.partition { it.isLetterOrDigit() }
    println(letterOrDigitList) // [a, 1]
    println(notLetterOrDigitList) // [+]
}

문자의 대소문자를 확인하는 함수:

kotlin
fun main() {
    val chars = listOf('Dž', 'Lj', 'Nj', 'Dz', '1', 'A', 'a', '+')
    val (titleCases, notTitleCases) = chars.partition { it.isTitleCase() }
    println(titleCases) // [Dž, Lj, Nj, Dz]
    println(notTitleCases) // [1, A, a, +]
}

기타 함수:

Unicode에 따른 문자의 일반 카테고리를 나타내는 Char.category 속성과 그 반환 타입인 CharCategory 열거형 클래스도 이제 멀티플랫폼 프로젝트에서 사용할 수 있습니다.

문자에 대해 더 알아보기.

새로운 컬렉션 함수 firstNotNullOf()

새로운 firstNotNullOf()firstNotNullOfOrNull() 함수는 mapNotNull()first() 또는 firstOrNull()과 결합한 것입니다. 이 함수들은 사용자 정의 선택자 함수를 사용하여 원본 컬렉션을 매핑하고 null이 아닌 첫 번째 값을 반환합니다. 만약 그러한 값이 없으면 firstNotNullOf()는 예외를 던지고, firstNotNullOfOrNull()은 null을 반환합니다.

kotlin
fun main() {
    val data = listOf("Kotlin", "1.5")
    println(data.firstNotNullOf(String::toDoubleOrNull))
    println(data.firstNotNullOfOrNull(String::toIntOrNull))
}

String?.toBoolean()의 엄격한 버전

기존의 String?.toBoolean()에 대한 대소문자를 구분하는 엄격한 버전인 두 가지 새로운 함수가 도입되었습니다.

kotlin
fun main() {
    println("true".toBooleanStrict())
    println("1".toBooleanStrictOrNull())
    // println("1".toBooleanStrict()) // 예외 발생
}

kotlin-test 라이브러리

kotlin-test 라이브러리에 몇 가지 새로운 기능이 도입되었습니다.

멀티플랫폼 프로젝트에서 테스트 종속성 사용 간소화

이제 commonTest 소스 세트에서 테스트를 위한 종속성을 추가할 때 kotlin-test 종속성을 사용할 수 있으며, Gradle 플러그인이 각 테스트 소스 세트에 해당하는 플랫폼 종속성을 추론합니다.

또한, 공유 또는 플랫폼별 소스 세트에서도 kotlin-test 종속성을 사용할 수 있습니다.

명시적 종속성이 있는 기존의 kotlin-test 설정은 Gradle 및 Maven 모두에서 계속 작동합니다.

테스트 라이브러리에 대한 종속성 설정에 대해 더 알아보세요.

Kotlin/JVM 소스 세트에 대한 테스트 프레임워크 자동 선택

이제 Gradle 플러그인이 테스트 프레임워크에 대한 종속성을 자동으로 선택하고 추가합니다. 공통 소스 세트에 kotlin-test 종속성만 추가하면 됩니다.

Gradle은 기본적으로 JUnit 4를 사용합니다. 따라서 kotlin("test") 종속성은 JUnit 4용 변체인 kotlin-test-junit으로 해석됩니다.

kotlin
kotlin {
    sourceSets {
        val commonTest by getting {
            dependencies {
                implementation(kotlin("test")) // 이 코드는 전이적으로 
                                               // JUnit 4에 대한 종속성을 가져옵니다.
            }
        }
    }
}
groovy
kotlin {
    sourceSets {
        commonTest {
            dependencies {
                implementation kotlin("test") // 이 코드는 전이적으로 
                                              // JUnit 4에 대한 종속성을 가져옵니다.
            }
        }
    }
}

테스트 태스크에서 useJUnitPlatform() 또는 useTestNG()를 호출하여 JUnit 5 또는 TestNG를 선택할 수 있습니다.

groovy
tasks {
    test {
        // TestNG 지원 활성화
        useTestNG()
        // 또는
        // JUnit Platform (JUnit 5) 지원 활성화
        useJUnitPlatform()
    }
}

프로젝트의 gradle.propertieskotlin.test.infer.jvm.variant=false 라인을 추가하여 테스트 프레임워크 자동 선택을 비활성화할 수 있습니다.

테스트 라이브러리에 대한 종속성 설정에 대해 더 알아보세요.

단언문(Assertion) 함수 업데이트

이번 릴리스에서는 새로운 단언문 함수들이 추가되고 기존 함수들이 개선되었습니다.

kotlin-test 라이브러리에 추가된 기능은 다음과 같습니다.

  • 값의 타입 확인

    값의 타입을 확인하기 위해 새로운 assertIs<T>assertIsNot<T>를 사용할 수 있습니다.

    kotlin
    @Test
    fun testFunction() {
        val s: Any = "test"
        assertIs<String>(s)  // 단언이 실패하면 s의 실제 타입을 언급하는 AssertionError를 던집니다.
        // assertIs의 계약(contract) 덕분에 이제 s.length를 출력할 수 있습니다.
        println("${s.length}")
    }

    타입 소거(type erasure) 때문에, 다음 예제에서 이 단언 함수는 valueList 타입인지만 확인하며 특정 String 요소 타입의 리스트인지는 확인하지 않습니다: assertIs<List<String>>(value).

  • 배열, 시퀀스 및 임의의 이터러블에 대한 컨테이너 내용 비교

    구조적 동등성(structural equality)을 구현하지 않는 다양한 컬렉션의 내용을 비교하기 위한 새로운 오버로드된 assertContentEquals() 함수 세트가 추가되었습니다.

    kotlin
    @Test
    fun test() {
        val expectedArray = arrayOf(1, 2, 3)
        val actualArray = Array(3) { it + 1 }
        assertContentEquals(expectedArray, actualArray)
    }
  • DoubleFloat 숫자에 대한 assertEquals()assertNotEquals()의 새로운 오버로드

    두 개의 Double 또는 Float 숫자를 절대 정밀도로 비교할 수 있게 해주는 assertEquals() 함수의 새로운 오버로드가 추가되었습니다. 정밀도 값은 함수의 세 번째 파라미터로 지정됩니다.

    kotlin
     @Test
    fun test() {
        val x = sin(PI)
    
        // 정밀도 파라미터
        val tolerance = 0.000001
    
        assertEquals(0.0, x, tolerance)
    }
  • 컬렉션 및 요소의 내용을 확인하기 위한 새로운 함수

    이제 assertContains() 함수를 사용하여 컬렉션이나 요소에 무언가가 포함되어 있는지 확인할 수 있습니다. IntRange, Stringcontains() 연산자가 있는 Kotlin 컬렉션 및 요소와 함께 사용할 수 있습니다.

    kotlin
    @Test
    fun test() {
        val sampleList = listOf<String>("sample", "sample2")
        val sampleString = "sample"
        assertContains(sampleList, sampleString)  // 컬렉션 내 요소 확인
        assertContains(sampleString, "amp")       // 문자열 내 부분 문자열 확인
    }
  • 이제 assertTrue(), assertFalse(), expect() 함수가 인라인화됨

    이제부터 이 함수들을 인라인 함수로 사용할 수 있으므로, 람다 표현식 내부에서 일시 중단 함수(suspend functions)를 호출할 수 있습니다.

    kotlin
    @Test
    fun test() = runBlocking<Unit> {
        val deferred = async { "Kotlin is nice" }
        assertTrue("Kotlin 부분 문자열이 존재해야 함") {
            deferred.await() .contains("Kotlin")
        }
    }

kotlinx 라이브러리

Kotlin 1.5.0과 함께 kotlinx 라이브러리의 새로운 버전들도 출시되었습니다.

Coroutines 1.5.0-RC

kotlinx.coroutines 1.5.0-RC의 주요 변경 사항은 다음과 같습니다.

Kotlin 1.5.0부터 실험적 코루틴(experimental coroutines)은 비활성화되며 -Xcoroutines=experimental 플래그는 더 이상 지원되지 않습니다.

자세한 내용은 변경 로그kotlinx.coroutines 1.5.0 출시 블로그 포스트에서 확인하세요.

Serialization 1.2.1

kotlinx.serialization 1.2.1의 주요 변경 사항은 다음과 같습니다.

  • JSON 직렬화 성능 개선
  • JSON 직렬화 시 여러 이름(multiple names) 지원
  • @Serializable 클래스로부터 실험적인 .proto 스키마 생성 지원
  • 기타 등등

자세한 내용은 변경 로그kotlinx.serialization 1.2.1 출시 블로그 포스트에서 확인하세요.

dateTime 0.2.0

kotlinx-datetime 0.2.0의 주요 변경 사항은 다음과 같습니다.

  • @Serializable 적용이 가능한 Datetime 객체
  • 정규화된 DateTimePeriodDatePeriod API
  • 기타 등등

자세한 내용은 변경 로그kotlinx-datetime 0.2.0 출시 블로그 포스트에서 확인하세요.

Kotlin 1.5.0으로 마이그레이션

IntelliJ IDEA와 Android Studio는 Kotlin 1.5.0 플러그인이 사용 가능해지면 업데이트를 제안할 것입니다.

기존 프로젝트를 Kotlin 1.5.0으로 마이그레이션하려면 Kotlin 버전을 1.5.0으로 변경하고 Gradle 또는 Maven 프로젝트를 다시 가져오기만 하면 됩니다. Kotlin 1.5.0으로 업데이트하는 방법을 알아보세요.

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

새로운 명령줄 컴파일러는 GitHub 출시 페이지에서 다운로드할 수 있습니다.

Kotlin 1.5.0은 기능 릴리스(feature release)이므로 언어에 호환되지 않는 변경 사항이 포함될 수 있습니다. 이러한 변경 사항의 상세 목록은 Kotlin 1.5 호환성 가이드에서 확인할 수 있습니다.