Kotlin 1.4.30의 새로운 기능

Kotlin 1.4.30의 새로운 기능

Kotlin 1.4.30은 새 언어 기능들의 프리뷰 버전을 제공하고, Kotlin/JVM 컴파일러의 새 IR 백엔드를 Beta 단계로 승격하며, 다양한 성능·기능 개선을 함께 담아 2021년 2월 3일에 공개됐어요.

새 기능에 대해 더 자세히 알고 싶다면 이 블로그 포스트를 읽어 볼 수 있어요. Kotlin의 릴리스 주기에 대한 정보는 Kotlin 릴리스 절차 문서를 참고해 주세요.

출처: What's new in Kotlin 1.4.30

본문

언어 기능 (Language features)

Kotlin 1.5.0에서는 JVM 레코드(record) 지원, sealed 인터페이스, 안정적인 inline 클래스 같은 새 언어 기능이 제공될 예정이에요. Kotlin 1.4.30에서는 이 기능들과 개선 사항들을 프리뷰 모드로 미리 써 볼 수 있어요. 1.5.0이 출시되기 전에 피드백을 반영하고 싶으니, 해당 YouTrack 티켓으로 여러분의 의견을 꼭 남겨 주시면 감사하겠어요.

이 언어 기능들을 프리뷰 모드로 켜려면 특정 컴파일러 옵션을 추가해 옵트인(opt-in)해야 해요. 자세한 내용은 아래 각 섹션을 확인해 주세요. 새 기능 프리뷰에 대해 더 알고 싶다면 이 블로그 포스트를 읽어 보세요.

JVM 레코드 지원

JVM 레코드 기능은 Experimental 상태예요. 언제든 삭제되거나 바뀔 수 있고, 옵트인이 필요하며(아래 상세 참고), 평가 목적으로만 사용해야 해요. 이에 대한 의견은 YouTrack으로 남겨 주세요.

JDK 16 릴리스에는 Java의 새 클래스 타입인 record를 안정화하려는 계획이 들어 있어요. Kotlin은 Java와의 상호 운용성을 유지하면서 Kotlin의 장점까지 모두 제공하기 위해, 실험적인 record 클래스 지원을 도입하고 있어요.

Java에서 선언된 record 클래스는 Kotlin에서 프로퍼티를 가진 일반 클래스처럼 그냥 사용할 수 있어요. 추가 작업이 전혀 필요 없답니다.

1.4.30부터는 data class@JvmRecord 애노테이션을 붙여서 Kotlin 안에서도 record 클래스를 선언할 수 있어요:

@JvmRecord
data class User(val name: String, val age: Int)

JVM 레코드의 프리뷰 버전을 시도해 보려면 컴파일러 옵션 -Xjvm-enable-preview-language-version 1.5를 추가하면 돼요.

JVM 레코드 지원은 계속 작업 중이에요. 구현, 제약, 문법에 대해 더 자세히 알고 싶다면 KEEP을 참고하고, 의견은 이 YouTrack 티켓으로 공유해 주세요.

Sealed 인터페이스

Sealed 인터페이스는 Experimental 상태예요. 언제든 삭제되거나 바뀔 수 있고, 옵트인이 필요하며, 평가 목적으로만 사용해야 해요. 이에 대한 의견은 YouTrack으로 남겨 주세요.

Kotlin 1.4.30에서는 sealed 인터페이스의 프로토타입을 함께 제공해요. Sealed 인터페이스는 sealed 클래스를 보완하면서 더 유연한 제한된 클래스 계층(hierarchy)을 만들 수 있게 해 줘요.

Sealed 인터페이스는 같은 모듈 밖에서는 구현할 수 없는 "내부용" 인터페이스로 쓰일 수 있어요. 이 점을 활용하면 예를 들어 완전한(exhaustive) when 표현식을 작성할 수 있어요.

또 하나의 용례로, sealed 인터페이스 덕분에 하나의 클래스가 두 개 이상의 sealed 슈퍼클래스에서 상속받을 수도 있어요.

sealed interface Polygon

class Rectangle(): Polygon
class Triangle(): Polygon

// when()이 완전하다: 모듈이 컴파일된 뒤에는
// 다른 Polygon 구현이 등장할 수 없다
fun draw(polygon: Polygon) = when (polygon) {
    is Rectangle -> // ...
    is Triangle -> // ...
}

sealed 인터페이스의 또 다른 용례를 살펴볼까요. sealed 인터페이스 덕분에 클래스가 두 개 이상의 sealed 슈퍼클래스에서 상속받는 것도 가능해요.

sealed interface Fillable {
   fun fill()
}
sealed interface Polygon {
   val vertices: List<Point>
}

class Rectangle(override val vertices: List<Point>): Fillable, Polygon {
   override fun fill() { /*...*/ }
}

sealed 인터페이스의 프리뷰 버전을 시도해 보려면 컴파일러 옵션 -language-version 1.5를 추가하면 돼요. 이 버전으로 전환하면 인터페이스에서 sealed 한정자를 사용할 수 있어요. 의견은 이 YouTrack 티켓으로 공유해 주세요.

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

패키지 전체에 걸친 sealed 클래스 계층

sealed 클래스의 패키지 전체 계층은 Experimental 상태예요. 언제든 삭제되거나 바뀔 수 있고, 옵트인이 필요하며, 평가 목적으로만 사용해야 해요. 이에 대한 의견은 YouTrack으로 남겨 주세요.

이제 sealed 클래스는 더 유연한 계층을 만들 수 있어요. 같은 컴파일 단위와 같은 패키지의 모든 파일에 서브클래스를 가질 수 있게 됐어요. 이전에는 모든 서브클래스가 같은 파일에 있어야 했죠.

직접 서브클래스는 최상위(top-level)에 있거나, 이름 있는 클래스·인터페이스·객체 안에 얼마든지 중첩될 수 있어요. sealed 클래스의 서브클래스는 이름이 제대로 정규화된(qualified) 것이어야 해요. 즉 로컬 객체나 익명 객체가 될 수는 없어요.

패키지 전체에 걸친 sealed 클래스 계층을 시도해 보려면 컴파일러 옵션 -language-version 1.5를 추가하면 돼요. 의견은 이 YouTrack 티켓으로 공유해 주세요.

패키지 전체에 걸친 sealed 클래스 계층에 대해 더 알아보기.

향상된 inline 클래스

Inline 값 클래스는 Beta 상태예요. 거의 안정적이지만, 앞으로 마이그레이션 단계가 필요할 수도 있어요. 변경을 최소화하기 위해 최선을 다할게요. inline 클래스 기능에 대한 의견은 YouTrack으로 남겨 주세요.

Kotlin 1.4.30은 inline 클래스Beta로 승격하면서 다음과 같은 기능과 개선 사항을 함께 가져왔어요:

  • inline 클래스는 값 기반(value-based)이기 때문에 value 한정자로 정의할 수 있어요. 이제 inlinevalue 한정자는 서로 동등해요. 앞으로의 Kotlin 버전에서는 inline 한정자를 deprecate할 계획이에요. 이제부터 JVM 백엔드에서는 클래스 선언 앞에 @JvmInline 애노테이션이 필요해요:
inline class Name(private val s: String)

value class Name(private val s: String)

// JVM 백엔드용
@JvmInline
value class Name(private val s: String)
  • inline 클래스는 init 블록을 가질 수 있어요. 클래스가 인스턴스화된 직후에 실행될 코드를 추가할 수 있어요:
@JvmInline
value class Negative(val x: Int) {
  init {
      require(x < 0) { }
  }
}
  • Java 코드에서 inline 클래스를 인자로 받는 함수 호출: Kotlin 1.4.30 이전에는 맹글링(mangling) 때문에 Java에서 inline 클래스를 받는 함수를 호출할 수 없었어요. 이제부터는 맹글링을 수동으로 끌 수 있어요. Java 코드에서 그런 함수를 호출하려면 함수 선언 앞에 @JvmName 애노테이션을 추가하면 돼요:
inline class UInt(val x: Int)

fun compute(x: Int) { }

@JvmName("computeUInt")
fun compute(x: UInt) { }
  • 이번 릴리스에서는 잘못된 동작을 고치기 위해 함수의 맹글링 방식을 바꿨어요. 이 변경으로 ABI가 달라졌어요. 1.4.30부터 Kotlin 컴파일러는 기본적으로 새 맹글링 방식을 사용해요. 옛 1.4.0 맹글링 방식을 강제로 쓰고 바이너리 호환성을 유지하려면 -Xuse-14-inline-classes-mangling-scheme 컴파일러 플래그를 사용하면 돼요.

Kotlin 1.4.30은 inline 클래스를 Beta로 승격했고, 앞으로의 릴리스에서 Stable로 만들 계획이에요. 의견은 이 YouTrack 티켓으로 공유해 주세요.

inline 클래스의 프리뷰 버전을 시도해 보려면 컴파일러 옵션 -Xinline-classes 또는 -language-version 1.5를 추가하면 돼요.

맹글링 알고리즘에 대해 더 알아보려면 KEEP을 참고해 주세요.

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

Kotlin/JVM

JVM IR 컴파일러 백엔드가 Beta 도달

1.4.0에서 Alpha로 소개된 Kotlin/JVM용 IR 기반 컴파일러 백엔드가 Beta에 도달했어요. IR 백엔드가 Kotlin/JVM 컴파일러의 기본이 되기 전 마지막 pre-stable 단계예요.

이제 IR 컴파일러가 만든 바이너리를 소비할 때의 제한을 없앴어요. 이전에는 새 JVM IR 백엔드를 켠 경우에만 그 백엔드로 컴파일된 코드를 사용할 수 있었죠. 1.4.30부터는 그런 제약이 없어져서, 새 백엔드로 라이브러리처럼 제3자가 쓸 부품을 만들 수도 있어요. 새 백엔드의 Beta 버전을 써 보고 이슈 트래커에 피드백을 남겨 주세요.

새 JVM IR 백엔드를 켜려면 프로젝트 설정 파일에 다음 내용을 추가하면 돼요:

  • Gradle에서:
tasks.withType(org.jetbrains.kotlin.gradle.dsl.KotlinJvmCompile::class) {
  kotlinOptions.useIR = true
}

tasks.withType(org.jetbrains.kotlin.gradle.dsl.KotlinJvmCompile) {
  kotlinOptions.useIR = true
}
  • Maven에서:
<configuration>
    <args>
        <arg>-Xuse-ir</arg>
    </args>
</configuration>

JVM IR 백엔드가 가져오는 변경 사항에 대해 더 알고 싶다면 이 블로그 포스트를 읽어 보세요.

Kotlin/Native

성능 개선

Kotlin/Native는 1.4.30에서 다양한 성능 개선을 받아 컴파일 시간이 빨라졌어요. 예를 들어 Kotlin Multiplatform Mobile로 하는 네트워킹과 데이터 저장 샘플에서 프레임워크를 다시 빌드하는 시간이 (1.4.10 기준) 9.5초에서 (1.4.30 기준) 4.5초로 줄었어요.

Apple watchOS 64비트 시뮬레이터 타깃

x86 시뮬레이터 타깃은 watchOS 버전 7.0부터 deprecated 됐어요. 최신 watchOS 버전을 따라잡기 위해 Kotlin/Native에는 64비트 아키텍처에서 시뮬레이터를 돌리기 위한 새 타깃 watchosX64가 추가됐어요.

Xcode 12.2 라이브러리 지원

Xcode 12.2와 함께 제공되는 새 라이브러리들에 대한 지원이 추가됐어요. 이제 Kotlin 코드에서 그 라이브러리들을 사용할 수 있어요.

Kotlin/JS

최상위 프로퍼티의 지연 초기화 (Lazy initialization)

최상위 프로퍼티의 지연 초기화는 Experimental 상태예요. 언제든 삭제되거나 바뀔 수 있고, 옵트인이 필요하며, 평가 목적으로만 사용해야 해요. 이에 대한 의견은 YouTrack으로 남겨 주세요.

Kotlin/JS의 IR 백엔드에 최상위 프로퍼티의 지연 초기화 프로토타입 구현이 추가됐어요. 이 기능은 애플리케이션 시작 시 모든 최상위 프로퍼티를 초기화할 필요를 줄여서, 애플리케이션 시작 시간을 크게 개선해 줘요.

지연 초기화 작업은 계속 진행될 거예요. 현재 프로토타입을 써 보고 의견이나 결과를 이 YouTrack 티켓 또는 공식 Kotlin Slack#javascript 채널(초대는 여기에서)에서 공유해 주세요.

지연 초기화를 사용하려면 JS IR 컴파일러로 코드를 컴파일할 때 -Xir-property-lazy-initialization 컴파일러 옵션을 추가하면 돼요.

Gradle 프로젝트 개선

Gradle configuration cache 지원

1.4.30부터 Kotlin Gradle 플러그인은 configuration cache 기능을 지원해요. 이 기능은 빌드 과정을 빨라지게 해요. 명령을 한 번 실행하면 Gradle이 configuration 단계를 수행하고 태스크 그래프를 계산해요. Gradle은 그 결과를 캐시했다가 이후 빌드에 재사용해요.

이 기능을 쓰려면 Gradle 명령을 사용하거나 IntelliJ 기반 IDE를 설정하면 돼요.

표준 라이브러리

텍스트 대문자/소문자 변환을 위한 로케일 무관(locale-agnostic) API

로케일 무관 API 기능은 Experimental 상태예요. 언제든 삭제되거나 바뀔 수 있어요. 평가 목적으로만 사용해야 해요. 이에 대한 의견은 YouTrack으로 남겨 주세요.

이 릴리스에서는 문자열과 문자를 대소문자로 변환하는 실험적인 로케일 무관 API가 도입됐어요. 현재의 toLowerCase(), toUpperCase(), capitalize(), decapitalize() API 함수들은 로케일에 민감해요. 즉 플랫폼의 로케일 설정에 따라 코드 동작이 달라질 수 있어요. 예를 들어 터키어 로케일에서 문자열 "kotlin"toUpperCase로 변환하면 결과가 "KOTLIN"이 아니라 "KOTLİN"이 돼요.

// 현재 API
println("Needs to be capitalized".toUpperCase()) // NEEDS TO BE CAPITALIZED

// 새 API
println("Needs to be capitalized".uppercase()) // NEEDS TO BE CAPITALIZED

Kotlin 1.4.30은 다음과 같은 대안을 제공해요:

  • String 함수용:

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

    이전 버전 1.4.30 대안
    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() 함수도 있어요.

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

명확한 Char-to-code 및 Char-to-digit 변환

Char 변환의 모호함 없는(명확한) API 기능은 Experimental 상태예요. 언제든 삭제되거나 바뀔 수 있어요. 평가 목적으로만 사용해야 해요. 이에 대한 의견은 YouTrack으로 남겨 주세요.

현재 여러 숫자 타입으로 표현된 UTF-16 코드를 반환하는 Char→숫자 변환 함수는, 문자열의 숫자 값을 반환하는 String-to-Int 변환과 자주 혼동되곤 해요:

"4".toInt() // 4를 반환
'4'.toInt() // 52를 반환
// 그리고 Char '4'에 대해 숫자 값 4를 반환하는 공통 함수는 없었다

이 혼동을 피하기 위해 Char 변환을 이름이 명확한 두 가지 함수 집합으로 분리하기로 했어요:

  • Char의 정수 코드를 얻고, 주어진 코드로 Char를 만드는 함수:
fun Char(code: Int): Char
fun Char(code: UShort): Char
val Char.code: Int
  • Char를 그것이 나타내는 숫자의 숫자 값으로 변환하는 함수:
fun Char.digitToInt(radix: Int): Int
fun Char.digitToIntOrNull(radix: Int): Int?
  • 음수 아닌 한 자리 숫자를 그에 해당하는 Char 표현으로 변환하는 Int 확장 함수:
fun Int.digitToChar(radix: Int): Char

자세한 내용은 KEEP에서 확인할 수 있어요.

Serialization 업데이트

Kotlin 1.4.30과 함께 kotlinx.serialization 1.1.0-RC가 공개됐어요. 여기에는 다음과 같은 새 기능이 포함돼요:

  • Inline 클래스 직렬화 지원
  • 부호 없는(unsigned) 원시 타입 직렬화 지원

Inline 클래스 직렬화 지원

Kotlin 1.4.30부터 inline 클래스를 직렬화 가능하게 만들 수 있어요:

@Serializable
inline class Color(val rgb: Int)

이 기능은 새 1.4.30 IR 컴파일러가 필요해요. 직렬화 프레임워크는 다른 직렬화 가능 클래스에 사용될 때 직렬화 가능 inline 클래스를 박싱하지 않아요.

kotlinx.serialization 문서에서 더 자세히 알아보세요.

부호 없는 원시 타입 직렬화 지원

1.4.30부터 kotlinx.serialization의 표준 JSON 직렬화기를 부호 없는 원시 타입(UInt, ULong, UByte, UShort)에 사용할 수 있어요:

@Serializable
class Counter(val counted: UByte, val description: String)
fun main() {
   val counted = 239.toUByte()
   println(Json.encodeToString(Counter(counted, "tries")))
}

kotlinx.serialization 문서에서 더 자세히 알아보세요.

더 알아보기