Kotlin 1.5.20의 새로운 기능
Kotlin 1.5.20은 1.5.0의 새로운 기능에서 발견된 문제들에 대한 수정 사항을 담고 있으며, 다양한 도구 개선 사항도 포함하고 있습니다.
변경 사항에 대한 개요는 릴리스 블로그 포스트와 다음 영상에서 확인할 수 있습니다:
Kotlin 릴리스 주기에 대한 정보는 Kotlin 릴리스 프로세스를 참조하세요.
Kotlin/JVM
Kotlin 1.5.20에서는 JVM 플랫폼에 다음과 같은 업데이트가 적용되었습니다:
- invokedynamic을 통한 문자열 연결
- JSpecify 널 허용성(nullness) 어노테이션 지원
- Kotlin과 Java 코드가 혼용된 모듈 내에서 Java의 Lombok 생성 메서드 호출 지원
invokedynamic을 통한 문자열 연결
Kotlin 1.5.20은 JVM 9+ 타겟에서 문자열 연결(string concatenation)을 동적 호출(invokedynamic)로 컴파일하여 최신 Java 버전의 보조를 맞춥니다. 더 정확하게는, 문자열 연결에 StringConcatFactory.makeConcatWithConstants()를 사용합니다.
이전 버전에서 사용하던 StringBuilder.append()를 통한 연결 방식으로 되돌리려면, 컴파일러 옵션 -Xstring-concat=inline을 추가하세요.
Gradle, Maven, 커맨드 라인 컴파일러에서 컴파일러 옵션을 추가하는 방법을 알아볼 수 있습니다.
JSpecify 널 허용성 어노테이션 지원
Kotlin 컴파일러는 Java에서 Kotlin으로 널 허용성 정보를 전달하기 위해 다양한 유형의 널 허용성 어노테이션을 읽을 수 있습니다. 버전 1.5.20에는 표준화된 Java 널 허용성 어노테이션 세트를 포함하는 JSpecify 프로젝트에 대한 지원이 도입되었습니다.
JSpecify를 사용하면 Kotlin이 Java와 상호 운용할 때 널 안전성(null-safety)을 유지하는 데 도움이 되는 더 상세한 널 허용성 정보를 제공할 수 있습니다. 선언, 패키지 또는 모듈 범위에 대해 기본 널 허용성을 설정하거나, 파라미터형 널 허용성(parametric nullability) 등을 지정할 수 있습니다. 이에 대한 자세한 내용은 JSpecify 사용자 가이드에서 확인할 수 있습니다.
다음은 Kotlin이 JSpecify 어노테이션을 처리하는 예시입니다:
// JavaClass.java
import org.jspecify.nullness.*;
@NullMarked
public class JavaClass {
public String notNullableString() { return ""; }
public @Nullable String nullableString() { return ""; }
}// Test.kt
fun kotlinFun() = with(JavaClass()) {
notNullableString().length // OK
nullableString().length // Warning: receiver nullability mismatch
}1.5.20에서 JSpecify가 제공하는 널 허용성 정보에 따른 모든 널 허용성 불일치는 경고(warning)로 보고됩니다. JSpecify로 작업할 때 엄격 모드(에러 보고 포함)를 활성화하려면 -Xjspecify-annotations=strict 및 -Xtype-enhancement-improvements-strict-mode 컴파일러 옵션을 사용하세요. JSpecify 프로젝트는 현재 활발히 개발 중임을 유의하시기 바랍니다. API와 구현은 언제든지 크게 변경될 수 있습니다.
Kotlin과 Java 코드가 혼용된 모듈 내에서 Java의 Lombok 생성 메서드 호출 지원
Lombok 컴파일러 플러그인은 실험적(Experimental) 단계입니다. 언제든지 삭제되거나 변경될 수 있습니다. 평가 목적으로만 사용하세요. YouTrack을 통해 여러분의 피드백을 기다리고 있습니다.
Kotlin 1.5.20은 실험적인 Lombok 컴파일러 플러그인을 도입했습니다. 이 플러그인을 사용하면 Kotlin과 Java 코드가 함께 있는 모듈 내에서 Java의 Lombok 선언을 생성하고 사용할 수 있습니다. Lombok 어노테이션은 Java 소스에서만 작동하며 Kotlin 코드에서 사용하면 무시됩니다.
플러그인은 다음 어노테이션을 지원합니다:
@Getter,@Setter@NoArgsConstructor,@RequiredArgsConstructor,@AllArgsConstructor@Data@With@Value
저희는 이 플러그인 작업을 계속 진행 중입니다. 현재의 상세한 상태를 확인하려면 Lombok 컴파일러 플러그인 README를 방문하세요.
현재로서는 @Builder 어노테이션을 지원할 계획이 없으나, YouTrack의 @Builder 이슈에 투표해 주신다면 검토할 수 있습니다.
Kotlin/Native
Kotlin/Native 1.5.20은 새로운 기능의 프리뷰와 도구 개선 사항을 제공합니다:
생성된 Objective-C 헤더로 KDoc 주석 내보내기 선택 가능
생성된 Objective-C 헤더로 KDoc 주석을 내보내는 기능은 실험적(Experimental) 단계입니다. 언제든지 삭제되거나 변경될 수 있습니다. 옵트인(Opt-in)이 필요하며(아래 상세 내용 참조), 평가 목적으로만 사용해야 합니다. YouTrack을 통해 피드백을 공유해 주세요.
이제 Kotlin/Native 컴파일러가 Kotlin 코드의 문서 주석(KDoc)을 생성된 Objective-C 프레임워크로 내보내도록 설정하여, 프레임워크 사용자가 주석을 볼 수 있게 할 수 있습니다.
예를 들어, KDoc이 포함된 다음 Kotlin 코드는:
/**
* Prints the sum of the arguments.
* Properly handles the case when the sum doesn't fit in 32-bit integer.
*/
fun printSum(a: Int, b: Int) = println(a.toLong() + b)다음과 같은 Objective-C 헤더를 생성합니다:
/**
* Prints the sum of the arguments.
* Properly handles the case when the sum doesn't fit in 32-bit integer.
*/
+ (void)printSumA:(int32_t)a b:(int32_t)b __attribute__((swift_name("printSum(a:b:)")));이 기능은 Swift에서도 잘 작동합니다.
KDoc 주석을 Objective-C 헤더로 내보내는 기능을 사용하려면 -Xexport-kdoc 컴파일러 옵션을 사용하세요. 주석을 내보내려는 Gradle 프로젝트의 build.gradle(.kts)에 다음 라인을 추가하십시오:
kotlin {
targets.withType<org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget> {
compilations.get("main").kotlinOptions.freeCompilerArgs += "-Xexport-kdoc"
}
}kotlin {
targets.withType(org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget) {
compilations.get("main").kotlinOptions.freeCompilerArgs += "-Xexport-kdoc"
}
}이 YouTrack 티켓을 통해 피드백을 공유해 주시면 대단히 감사하겠습니다.
컴파일러 버그 수정
Kotlin/Native 컴파일러는 1.5.20에서 여러 버그 수정을 거쳤습니다. 전체 목록은 변경 로그에서 확인할 수 있습니다.
호환성에 영향을 미치는 중요한 버그 수정이 있습니다: 이전 버전에서는 잘못된 UTF 서로게이트 쌍(surrogate pairs)을 포함하는 문자열 상수가 컴파일 중에 해당 값을 잃어버리는 문제가 있었습니다. 이제는 그러한 값이 보존됩니다. 애플리케이션 개발자는 1.5.20으로 안전하게 업데이트할 수 있으며 아무것도 망가지지 않을 것입니다. 하지만 1.5.20으로 컴파일된 라이브러리는 이전 컴파일러 버전과 호환되지 않습니다. 자세한 내용은 이 YouTrack 이슈를 참조하세요.
단일 배열 내에서 Array.copyInto() 성능 향상
원본과 대상이 동일한 배열일 때 Array.copyInto()가 작동하는 방식을 개선했습니다. 이제 이러한 작업은 이 유스케이스에 대한 메모리 관리 최적화 덕분에 (복사되는 객체 수에 따라) 최대 20배 더 빠르게 완료됩니다.
Kotlin/JS
1.5.20과 함께, 프로젝트를 Kotlin/JS의 새로운 IR 기반 백엔드로 마이그레이션하는 데 도움이 되는 가이드를 게시합니다.
JS IR 백엔드용 마이그레이션 가이드
JS IR 백엔드를 위한 새로운 마이그레이션 가이드는 마이그레이션 중 발생할 수 있는 문제들을 식별하고 해결 방법을 제공합니다. 가이드에서 다루지 않는 문제가 발견되면 이슈 트래커에 보고해 주세요.
Gradle
Kotlin 1.5.20은 Gradle 사용 경험을 개선할 수 있는 다음과 같은 기능들을 도입했습니다:
kapt의 어노테이션 프로세서 클래스로더 캐싱
kapt의 어노테이션 프로세서 클래스로더 캐싱은 실험적(Experimental) 단계입니다. 언제든지 삭제되거나 변경될 수 있습니다. 평가 목적으로만 사용하세요. YouTrack을 통해 피드백을 공유해 주세요.
이제 kapt에서 어노테이션 프로세서의 클래스로더를 캐시할 수 있는 새로운 실험적 기능이 제공됩니다. 이 기능은 연속적인 Gradle 실행 시 kapt의 속도를 높일 수 있습니다.
이 기능을 활성화하려면 gradle.properties 파일에서 다음 속성들을 사용하세요:
# 양수 값은 캐싱을 활성화합니다.
# kapt를 사용하는 모듈 수와 동일한 값을 사용하세요.
kapt.classloaders.cache.size=5
# 캐싱이 작동하려면 false로 설정해야 합니다.
kapt.include.compile.classpath=falsekapt에 대해 더 알아보기.
kotlin.parallel.tasks.in.project 빌드 속성 사용 중단
이번 릴리스부터 Kotlin 병렬 컴파일은 Gradle 병렬 실행 플래그 --parallel에 의해 제어됩니다. 이 플래그를 사용하면 Gradle이 태스크를 동시에 실행하여 컴파일 태스크 속도를 높이고 리소스를 더 효율적으로 활용합니다.
더 이상 kotlin.parallel.tasks.in.project 속성을 사용할 필요가 없습니다. 이 속성은 사용 중단(Deprecated)되었으며 다음 메이저 릴리스에서 삭제될 예정입니다.
표준 라이브러리
Kotlin 1.5.20은 문자를 다루기 위한 여러 함수의 플랫폼별 구현을 변경하여 플랫폼 간 통일성을 제공합니다:
- Kotlin/Native 및 Kotlin/JS의 Char.digitToInt()에서 모든 유니코드 숫자 지원.
- 플랫폼 간 Char.isLowerCase()/isUpperCase() 구현 통합.
Kotlin/Native 및 Kotlin/JS의 Char.digitToInt()에서 모든 유니코드 숫자 지원
Char.digitToInt()는 문자가 나타내는 10진수 숫자의 수치 값을 반환합니다. 1.5.20 이전에는 이 함수가 Kotlin/JVM에서만 모든 유니코드 숫자 문자를 지원했으며, Native 및 JS 플랫폼의 구현에서는 ASCII 숫자만 지원했습니다.
이제부터는 Kotlin/Native와 Kotlin/JS 모두에서 유니코드 숫자 문자에 대해 Char.digitToInt()를 호출하여 그 수치 표현을 얻을 수 있습니다.
fun main() {
val ten = '\u0661'.digitToInt() + '\u0039'.digitToInt() // ARABIC-INDIC DIGIT ONE + DIGIT NINE
println(ten)
}플랫폼 간 Char.isLowerCase()/isUpperCase() 구현 통합
Char.isUpperCase() 및 Char.isLowerCase() 함수는 문자의 대소문자 여부에 따라 불리언 값을 반환합니다. Kotlin/JVM의 경우, 구현 시 General_Category와 Other_Uppercase/Other_Lowercase 유니코드 속성을 모두 확인합니다.
1.5.20 이전에는 다른 플랫폼의 구현이 다르게 작동하여 일반 범주(general category)만 고려했습니다. 1.5.20에서는 구현이 플랫폼 간에 통합되어 두 속성을 모두 사용하여 문자 케이스를 결정합니다:
fun main() {
val latinCapitalA = 'A' // "Lu" 일반 범주를 가짐
val circledLatinCapitalA = 'Ⓐ' // "Other_Uppercase" 속성을 가짐
println(latinCapitalA.isUpperCase() && circledLatinCapitalA.isUpperCase())
}