목적
kotlin 1.6.20에서 1.9.25로 버전 업그레이드 시 사용할 수 있는 새로운 기능에 대해 알아본다. 포스팅 시점 기준 가장 최신 버전은 kotlin 2.2.20이기 때문에, 2.0.0 버전도 간단하게 알아본다.
Versions
Kotlin 1.7.0
상당한 성능 향상을 제공하는 kotlin/JVM용 K2 컴파일러 등장 (alpha 버전)

K2 컴파일러는 성능 향상에 중점을 두고 있으며, JVM 프로젝트에서만 작동합니다. kotlin/JS, kotlin/Native 등 다중 플랫폼 프로젝트에서는 지원하지 않습니다.
컴파일 시, 아래 옵션을 추가하면 Kotlin K2 컴파일러를 활성화할 수 있습니다.
-Xuse-k2
Language
인라인 클래스의 인라인 값에 위임을 통한 구현 허용
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())
}
값이나 클래스 인스턴스에 대해 경량 래퍼(lightweight wrapper)를 만들기 위해서는 모든 인터페이스 메소드를 직접 구현해야 합니다. 1.7.0 버전에서는 이러한 제한이 제거되어 위임을 통한 구현으로 해결됩니다.
- 이전 버전에서는 value class는 위임을 통해 인터페이스 구현이 허용되지 않았음
type arguments에 대한 밑줄 연산자
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() {
// T is inferred as String because SomeImplementation derives from SomeClass<String>
val s = Runner.run<SomeImplementation, _>()
assert(s == "Test")
// T is inferred as Int because OtherImplementation derives from SomeClass<Int>
val n = Runner.run<OtherImplementation, _>()
assert(n == 42)
}
type argument에 대한 밑줄 연산자(_)가 도입됨에 따라 type argument를 유추할 수 있습니다.
안정적이고 확실한 non-nullable type
T & Any라는 새로운 구문을 사용하여 확실히 non-nullable한 제네릭 타입을 만들 수 있습니다.
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
}
kotlin/JVM
- 컴파일러 성능 최적화: kotlin 1.6.0에 비해 컴파일 시간이 평균 10% 단축 (인라인 함수를 많이 사용할 수록 컴파일 속도가 빨라짐)
- JVM 1.8 이상부터 사용할 수 있음 (1.6 버전 제거됨)
표준 라이브러리
- min(), max() 컬렉션 함수는 non-nullable한 값 반환
- 시간 표시를 인라인 값 클래스로 변경해 시간 측정 기능 성능 향상
- markNow(), elapseNow()와 같은 함수 호출 시 인스턴스 measureTimedValue()에 래퍼 클래스 할당하지 않음
- Java Optionals에 대한 새로운 실험적 기능 추가 (getOrNull(), getOrDefault(), getOrElse())
Kotlin 1.7.20
Kotlin K2 컴파일러 플러그인 지원
여전히 알파 버전이지만, 여러 컴파일러 플러그인을 지원합니다. K2 컴파일러를 활성화하려면 build.gradle.kts에 아래와 같이 작성하면 됩니다.
tasks.withType<KotlinCompile> {
kotlinOptions.useK2 = true
}
Language
..< 연산자를 실험적으로 제공
when (value) {
in 0.0..<0.25 -> // First quarter
in 0.25..<0.5 -> // Second quarter
in 0.5..<0.75 -> // Third quarter
in 0.75..1.0 -> // Last quarter <- Note closed range here
}
코틀린에는 값의 범위(range)를 지정하는 .. 연산자가 존재합니다. 새로운 ..< 연산자는 util 처럼 동작합니다.
data object를 통한 singleton 및 sealed class 계층의 문자열 표현 향상
package org.example
object MyObject
data object MyDataObject
fun main() {
println(MyObject) // org.example.MyObject@1f32e575
println(MyDataObject) // MyDataObject
}
kotlin 1.7.20에서는 data object를 사용할 수 있습니다. data object는 일반적인 object와 컨셉은 동일하지만, toString의 결과가 object보다 깔끔합니다.
sealed class ReadResult {
data class Number(val value: Int) : ReadResult()
data class Text(val value: String) : ReadResult()
data object EndOfFile : ReadResult()
}
fun main() {
println(ReadResult.Number(1)) // Number(value=1)
println(ReadResult.Text("Foo")) // Text(value=Foo)
println(ReadResult.EndOfFile) // EndOfFile
}
data object 선언은 sealed 클래스 계층에 data class와 함께 완벽하게 적용이 가능합니다.
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
// ...
kotlinOptions.languageVersion = "1.9"
}
data object를 사용하기 위해서는 위처럼 컴파일러 옵션에 -language-version 1.9을 활성화해야 합니다.
빌더 타입 추론 제한
kotlin 1.7.20에서는 빌더 타입 추론을 일부 제한하여 버전 업그레이드 시 영향을 받을 수 있습니다. builder lambda function을 포함된 코드에 적용되는 제약이며, 람다 자체를 분석하지 않고는 매개변수를 도출할 수 없어집니다.
class Data {
fun doSmth() {} // 1
}
fun <T> T.doSmth() {} // 2
fun test() {
buildList {
this.add(Data())
this.get(0).doSmth() // Resolves to 2 and leads to error
}
buildList<Data> { // Type argument!
this.add(Data())
this.get(0).doSmth() // Resolves to 1
}
}
kotlin/JVM
generic 인라인 클래스 제공
@JvmInline
value class UserId<T>(val value: T)
fun compute(s: UserId<String>) {} // Compiler generates fun compute-<hashcode>(s: Any?)
실험 기능이기 때문에 어느 버전에서 갑자기 지원 중단될 지 알 수 없음
Gradle
JVM toolchain 설정을 위한 새로운 메소드 제공
kotlin {
// 기존에 제공하던 toolchain
jvmToolchain {
languageVersion.set(JavaLanguageVersion.of(<MAJOR_JDK_VERSION>))
}
// 새로 제공하는 toolchain
jvmToolchain(<MAJOR_JDK_VERSION>)
// For example:
jvmToolchain(17)
}
JVM project의 toolchain이 무엇인가요?
여러 버전의 Java를 여러 프로젝트나 단일 프로젝트 내에서 사용해야 할 경우, 해당 버전에 맞게 빌드가 필요합니다.
Java toolchain은 Java project를 빌드하는 툴을 정의한 것으로 지정한 버전에 맞게 빌드할 수 있게 해줍니다.
참고 문서
Kotlin 1.8.0
kotlin/JVM
- JVM 19 버전에 해당하는 바이트코드 버전으로 클래스 생성 가능
표준 라이브러리
- 업데이트된 JVM 컴파일 타겟
- kotlin 1.8.0에서는 표준 라이브러리(kotlin-stdlib, kotlin-reflect, kotlin-script-*)가 JVM 1.8 기준으로 컴파일됩니다. (이전 버전까지는 1.6 JVM 기준으로 컴파일되었음)
- cbrt()
- 실수 세제곱근을 계산할 수 있는 함수가 안정화되었습니다.
- 향상된 kotlin-reflect 성능
Kotlin 1.8.20
K2 컴파일러 업데이트 (베타 버전으로 전환)
K2 컴파일러를 활성화하고 테스트하려면 아래와 같이 컴파일러 옵션을 설정해야 합니다. 이전 버전에서 사용하던 -Xuse-k2 옵션은 사용되지 않습니다.
kotlin {
sourceSets.all {
languageSettings {
languageVersion = "2.0"
}
}
}
Language
Enum class의 values() 함수에 대한 성능 좋은 대체
enum class Color(val colorName: String, val rgb: String) {
RED("Red", "#FF0000"),
ORANGE("Orange", "#FF7F00"),
YELLOW("Yellow", "#FFFF00")
}
@OptIn(ExperimentalStdlibApi::class)
fun findByRgb(rgb: String): Color? = Color.entries.find { it.rgb == rgb }
enum class에는 values()라는 enum의 값들의 배열로 반환하는 함수가 있습니다. 하지만, 이 배열을 활용하는 것에는 성능 문제가 있을 수 있습니다. 이를 해결하고자 entries 속성을 제공하며, 이는 values() 함수를 대체합니다. entries를 호출하면 미리 할당된 불변 리스트를 반환합니다.
values() 함수는 여전히 지원되지만, entries 속성을 사용하는 것을 권합니다.
단, 이 기능은 실험 단계로, 언제든 삭제되거나 변경될 수 있습니다. 위 기능을 사용하고자 한다면 컴파일러 옵션에서 @OptIn(ExperimentalStdlibApi)를 이용하거나, build.gradle.kts에서 -language-version 1.9 컴파일러 옵션을 추가해야 합니다.
tasks
.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask<*>>()
.configureEach {
compilerOptions
.languageVersion
.set(
org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_1_9
)
}
data class와의 대칭을 위한 data object 미리보기
data object는 object를 싱글톤으로 toString()을 깔끔하게 표현할 수 있습니다.
import java.lang.reflect.Constructor
data object MySingleton
fun main() {
val evilTwin = createInstanceViaReflection()
println(MySingleton) // MySingleton
println(evilTwin) // MySingleton
// Even when a library forcefully creates a second instance of MySingleton, its `equals` method returns true:
println(MySingleton == evilTwin) // true
// Do not compare data objects via ===.
println(MySingleton === evilTwin) // false
}
fun createInstanceViaReflection(): MySingleton {
// Kotlin reflection does not permit the instantiation of data objects.
// This creates a new MySingleton instance "by force" (i.e., Java platform reflection)
// Don't do this yourself!
return (MySingleton.javaClass.declaredConstructors[0].apply { isAccessible = true } as Constructor<MySingleton>).newInstance()
}
data object는 kotlin 1.7.20에서 처음 나온 이후로 의미론이 개선되었습니다.
Kotlin 1.9.0
Language
enum 클래스의 values() 함수 교체(entries)
kotlin 1.8.20에서 values() 함수를 대신할 entries 속성이 실험적으로 등장했습니다. kotlin 1.9.0에서는 entries가 stable 기능으로 변경되었습니다.
data class와의 대칭을 위한 data object
kotlin 1.8.20에 도입된 data object가 stable 기능으로 변경되었습니다.
kotlin/JVM
- kotlin 1.9.0부터 컴파일러는 JVM 20에 해당하는 바이트코드 버전을 사용하여 클래스를 생성할 수 있습니다.
Gradle
- classpath 속성 제거
- kotlin 1.7.0에서 KotlinCompile task의 속성인 classpath가 중단될 예정이라고 발표했었습니다. kotlin 1.9.0에서는 마침내 classpath 속성이 제거되었습니다.
- kotlin/JVM을 위한 프로젝트 수준 컴파일러 옵션
- kotlin 구성 블록 내 compilerOptions를 사용할 수 있게 되었습니다.
표준 라이브러리
stable 기능으로 변경된 ..< 연산자
fun main() {
for (number in 2..<10) {
if (number % 2 == 0) {
print("$number ")
}
}
// 2 4 6 8
// until은 닫혔는지 열렸는지 헷갈림
for (number in 2 until 10) {
if (number % 2 == 0) {
print("$number ")
}
}
// 2 4 6 8
}
kotlin 1.7.20에서 도입된 ..< 연산자는 1.8.0, 1.9.0 버전에 걸쳐 stable 기능으로 변경되었습니다. 이로 인해 util을 사용할 때보다 실수할 확률이 줄어들었습니다.
코드 실행 시간 측정
코드 블록을 실행하는데 걸린 시간을 측정하기 위해 measureTime inline 함수를 사용할 수 있습니다. 코드 블록 실행 결과 및 수행 시간을 반환하려면 measureTimedValue inline 함수를 활용할 수 있습니다.
fun slowFunction(): Unit = Thread.sleep(1000L)
val elapsed = measureTime {
slowFunction()
}
// Time elapsed: 1000 milliseconds (1.000716328s)
println("Time elapsed: ${elapsed.inWholeMilliseconds} milliseconds ($elapsed)")
val result = measureTimedValue {
slowFunction()
42
}
// Computed result: 42, time elapsed: 1.001441726s
println("Computed result: ${result.value}, time elapsed: ${result.duration}")
Kotlin 1.9.20
kotlin/JVM
- kotlin 1.9.0부터 컴파일러는 JVM 21에 해당하는 바이트코드 버전을 사용하여 클래스를 생성할 수 있습니다.
표준 라이브러리
enum class의 제네릭 values 함수 교체(enumValues<T> → enumEntries<T>)
enum class RGB { RED, GREEN, BLUE }
@OptIn(ExperimentalStdlibApi::class)
inline fun <reified T : Enum<T>> printAllValues() {
print(enumEntries<T>().joinToString { it.name })
}
printAllValues<RGB>()
// RED, GREEN, BLUE
실험 기능이므로, 언제든 삭제되거나 변경될 수 있으며, enumEntries 함수를 활성화하려면, @OptIn(ExperimentalStdlibApi)를 사용해야 합니다. 또는 build.gradle.kts에 language version을 1.9 이상으로 변경해야 합니다.
Kotlin 2.0.0
Kotlin K2 컴파일러
Kotlin 2.0.0에서는 새로운 Kotlin K2 컴파일러가 기본적으로 사용됩니다. K2 컴파일러는 모든 플랫폼(JVM, Native 등)에서 stable하게 사용됩니다. 새로운 컴파일러는 주요 성능 개선을 가져왔으며, 새로운 언어 기능 개발 속도 향상 등을 제공합니다.
현재의 K2 compiler 제한 사항
gradle 프로젝트에서 K2를 활성화하면, gradle 8.3 이하이면서 아래와 같은 케이스일 때 영향을 받을 수 있습니다.
- buildSrc 소스 코드 컴파일
- 빌드를 포함하여 gradle 플러그인 컴파일
- 8.3 이하 버전의 gradle 버전을 사용하는 프로젝트를 활용한 gradle plugin을 컴파일
- gradle 플러그인 종속성 빌드
문제가 발생하면 아래와 같이 설정하고, grdale 버전을 8.3 이상으로 업데이트하여 해결할 수 있습니다.
kotlin {
compilerOptions {
languageVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_1_9)
apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_1_9)
}
}
스마트 캐스트 개선
kotlin 2.0.0에서는 아래와 같은 영역에서 스마트 캐스트를 지원합니다.
- 지역 변수 및 추가 범위
- or 논리 연산자를 이용한 타입 체크
- 인라인 함수
- 함수 타입 속성
- 예외 처리
- 증가 및 감소 연산자
컴파일러 플러그인 지원
- all-open
- Lombok
- no-arg
- ...
참고 - all-open
- kotlin은 기본적으로 final로 선언되므로 Spring AOP처럼 클래스가 open으로 존재해야 하는 라이브러리나 프레임워크를 사용할 때 불푠합니다.
- all-open 컴파일러 플러그인을 활용하면 kotlin을 해당 프레임워크 요구 사항에 맞게 조정하고 명시적인 open 키워드 없이도 열 수 있도록 합니다.
plugins {
kotlin("plugin.allopen") version "2.2.21"
}
allOpen {
annotation("com.my.Annotation")
// annotations("com.another.Annotation", "com.third.Annotation")
}
@com.my.Annotation
annotation class MyFrameworkAnnotation
@MyFrameworkAnnotation
class MyClass // will be all-open
spring을 사용한다면, allopen 플러그인 대신 kotlin-spring 컴파일러 플러그인을 대신 사용할 수 있습니다.
plugins {
id("org.jetbrains.kotlin.plugin.spring") version "2.2.21"
// 또는
kotlin("plugin.spring") version "2.2.21"
}
kotlin/JVM
- kotlin 2.0.0부터 컴파일러는 JVM 22에 해당하는 바이트코드 버전을 사용하여 클래스를 생성할 수 있습니다.
표준 라이브러리
- enum class의 제네릭 values() 대체제 stable로 변경(enumValues<T> → enumEntries<T>)
- AutoCloseable interface stable로 변경
- use() 확장 함수는 주어진 블록 함수를 실행한 후 예외 발생 여부와 상관없이 올바르게 종료합니다.
- JVM 외 플랫폼에서도 String.toCharArray(destination) 함수 도입
참고 자료
'PROGRAMMING LANGUAGE > KOTLIN' 카테고리의 다른 글
| [Kotlin] sealed class 활용하기 (0) | 2026.01.03 |
|---|---|
| [Kotlin Coroutine] channel & select (0) | 2024.08.16 |
| [Effective Kotlin] 8장 효율적인 컬렉션 처리 (0) | 2024.08.10 |
| [Effective Kotlin] 7장 비용 줄이기 (0) | 2024.08.03 |
| [Effective Kotlin] 6장 클래스 설계 (0) | 2024.07.28 |