코틀린 1.4.0 버전에서 새로 추가된 사항들
코틀린 1.4.0에서는 여러 구성요소들에 대해 질적인 면, 성능적인 면에 초점을 둔 개선사항들을 담았습니다.
아래에서 코틀린 1.4.0에서는 주요한 변화들을 찾을 수 있을 것입니다.
언어의 특징 및 개선사항
코틀린 1.4.0은 다양한 언어들의 특징과 개선사항을 담고 있습니다.
- 코틀린 인터페이스를 위한 SAM 변환
- 라이브러리 저자에 대한 명시적 API 모드
- 네임드 파라미터와 일반 파라미터를 혼합하기
- Trailing comma
- Callable reference 개선사항
- 표현식이 루프 내 있는 경우 break, continue의 사용
1. 코틀린 인터페이스를 위한 SAM 변환
코틀린 1.4.0 이전에는, SAM (Single Abstract Method) 변환을 오직 자바 메소드, 코틀린에서 사용하는 자바 인터페이스에만 적용할 수 있었습니다. 이제부터는, SAM 변환을 코틀린 인터페이스에서도 사용할 수 있습니다. 그렇게 하기 위해서는 코틀린 인터페이스에 명시적으로 fun 키워드를 표기해주어야 합니다.
fun interface IntPredicate {
fun accept(i: Int): Boolean
}
val isEven = IntPredicate { it % 2 == 0 }
fun main() {
println("Is 7 even? - ${isEven.accept(7)}")
}
코틀린 함수형 인터페이스와 SAM 변환에 대해 더 알고싶다면 아래 링크를 클릭하세요
https://kotlinlang.org/docs/fun-interfaces.html
2. 라이브러리 개발자를 위한 명시적 API 모드
코틀린 컴파일러는 라이브러리 개발자를 위하여 명시적 API 모드를 제공합니다. 이 모드에서는, 컴파일러가 라이브러리 API를 보다 명확하고 일관성 있게 만드는데 도움을 주는 추가적인 작업을 수행합니다. 명시적 API 모드에서는 라이브러리의 public API에 대해 아래 요구사항을 추가하도록 하였습니다.
- 만약 public API에 기본 가시성이 노출되었다면, 접근제한자를 필수적으로 선언하도록 변경하였습니다. 이는 public API가 의도하지 않게 메소드를 노출시키는 것을 확실하게 막기 위해서입니다.
- public API에 노출되는 속성과 함수들에 명시적으로 타입을 선언하도록 변경하였습니다. 이는 API를 사용하는 유저가 API 멤버의 타입을 확실하게 인식하는 것을 보장해주기 위해서입니다.
설정에 따라, 이러한 명시적 API는 에러 (strick mode) 혹은 경고 (warning mode) 를 발생시킬 수 있습니다. 선언의 몇 가지 종류의 경우 일반적인 상식과, 가독성을 위해서 제외하였습니다.
- 주 생성자
- data 클래스의 프로퍼티들
- getter와 setter의 프로퍼티
- override 메소드
만약 command-line 컴파일러를 사용하고 있다면, strick, warning 옵션과 함께 -Xexplicit-api 를 추가하는 것으로 명시적 API 모드로 변환할 수 있습니다.
-Xexplicit-api={strict|warning}
명시적 API 모드에 대한 더 많은 정보를 얻고 싶다면 아래 링크를 참조하세요
https://github.com/Kotlin/KEEP/blob/master/proposals/explicit-api-mode.md
3. 네임드 파라미터와 일반 파라미터를 혼합하기
코틀린 1.3에서는, 함수를 네임드 파라미터로 호출할 경우, 일반 파리미터를 첫 번째 네임드 파라미터 이전으로 선언해두어야 합니다. 예를들어서 f(1, y=2)를 호출할 수 있었지만, f(x=1, 2)를 호출할 수는 없었습니다.
이런 상황은 모든 파라미터가 제 위치에 있는 상황에서 중간에 있는 파라미터만 네임드 파라미터로 호출하고 싶을 때 굉장히 짜증하는 상황입니다.
코틀린 1.4에서는 이러한 제한사항이 없습니다. 이제는 여러 파라미터들 중에서 중간에 있는 파라미터라도 네임드 파라미터로 호출할 수 있습니다. 더 나아가서, 이제는 만약 파라미터 순서를 정확하게 호출했을 경우에 네임드 파라미터와 일반 파라미터를 어떠한 위치라도 혼합하여 사용할 수 있습니다.
fun reformat(
str: String,
uppercaseFirstLetter: Boolean = true,
wordSeparator: Char = ' '
) {
// ...
}
//Function call with a named argument in the middle
reformat("This is a String!", uppercaseFirstLetter = false , '-')
4. Trailing comma
코틀린 1.4.0 에서는, 이제 파라미터, 파라미터 리스트, when 엔트리, 분해 구조 선언 등에 trailing comma를 사용할 수 있습니다. trailing comma를 이용하면서, 추가적으로 comma를 넣거나, 제거하지 않아도 새로운 아이템을 추가하거나, 순서를 변경할 수 있습니다.
특히 다중라인 파라미터 문법을 사용할 경우 유용합니다. trailing comma를 추가한 뒤에, 쉽게 파라미터나 값 라인 순서를 조정할 수 있습니다.
fun reformat(
str: String,
uppercaseFirstLetter: Boolean = true,
wordSeparator: Character = ' ', //trailing comma
) {
// ...
}
val colors = listOf(
"red",
"green",
"blue", //trailing comma
)
5. Callable reference 개선사항
코틀린 1.4.0에서는 대부분의 경우에 callable reference를 지원합니다.
- default 파라미터 값을 가지는 함수 reference
- Unit을 리턴하는 함수 reference
- 함수안에 가변 파라미터를 사용하는 경우의 reference
- callable reference에서의 suspend conversion
5.1 default 파라미터 값을 가지는 함수 reference
이제부터 default 파라미터 값을 가지는 함수에도 callable reference를 사용할 수 있습니다. 아래 예제에서 callable reference가 사용된 foo 함수의 경우 파라미터가 없으면 default 값인 0을 사용합니다.
fun foo(i: Int = 0): String = "$i!"
fun apply(func: () -> String): String = func()
fun main() {
println(apply(::foo))
}
이전에, default 파라미터 값을 사용할 수 있도록 apply 함수에 추가적인 오버로딩을 해야합니다.
// some new overload
fun applyInt(func: (Int) -> String): String = func(0)
5.2 Unit을 리턴하는 함수 reference
코틀린 1.4.0에서는 Unit을 리턴하는 함수에 어떤 타입을 리턴하는 함수이던 callable reference를 사용할 수 있습니다.
코틀린 1.4.0이전에는, 이 경우에 람다 파라미터만 사용할 수 있었습니다. 하지만, 이제는 람다 파라미터와 callable reference 둘 다 사용가능합니다.
fun foo(f: () -> Unit) { }
fun returnsInt(): Int = 42
fun main() {
foo { returnsInt() } // this was the only way to do it before 1.4
foo(::returnsInt) // starting from 1.4, this also works
}
5.3 가변 파라미터를 가진 함수에서의 reference
이제 vararg 파라미터를 전달하는 함수에 대해서도 callable reference를 사용할 수 있습니다. 이제부터 같은 타입의 파라미터를 숫자에 상관없이 파라미터 리스트의 뒤쪽에 전달할 수 있습니다.
fun foo(x: Int, vararg y: String) {}
fun use0(f: (Int) -> Unit) {}
fun use1(f: (Int, String) -> Unit) {}
fun use2(f: (Int, String, String) -> Unit) {}
fun test() {
use0(::foo)
use1(::foo)
use2(::foo)
}
5.4 callable reference에서의 suspend conversion
코틀린 1.4.0부터 callable reference에서의 suspend conversion을 지원합니다.
fun call() {}
fun takeSuspend(f: suspend () -> Unit) {}
fun test() {
takeSuspend { call() } // OK before 1.4
takeSuspend(::call) // In Kotlin 1.4, it also works
}
6. 표현식이 루프 내 있는 경우 break, continue의 사용
코틀린 1.3에서는, when 표현식이 루프 내부에 있는 경우 break, continue를 그대로 사용할 수 없었습니다. 그 이유는 해당 키워드들이 when 표현식 안에서의 fall-through 동작를 가능하게 하기 위해서 예약된 키워드들이기 때문입니다.
결국 루프 내부의 when 표현식에서 break와 continue를 사용하기 위해서는 라벨을 달수밖에 없었고, 이건 매우 귀찮은 일이었습니다.
fun test(xs: List<Int>) {
LOOP@for (x in xs) {
when (x) {
2 -> continue@LOOP
17 -> break@LOOP
else -> println(x)
}
}
}
kotlin 1.4.0에서는, 이제 break, continue를 루프 안에 when 표현식에서 라벨 없이 사용할 수 있게 되었습니다. 이 경우 가장 가까운 루프에 맞추어서 동작을 하게 됩니다.
fun test(xs: List<Int>) {
for (x in xs) {
when (x) {
2 -> continue
17 -> break
else -> println(x)
}
}
}
새로운 컴파일러
새로운 코틀린 컴파일러는 정말 빠르게 동작합니다. 지원되는 모든 플래폼을 확장하고, 컴파일러 확장을 위한 API를 제공할 것입니다. 이건 정말 오랜 기간을 두고 진행한 프로젝트이고, 코틀린 1.4.0에서 여러 단계를 완성하였습니다.
- 새롭고, 더욱 강력한 타입 인터페이스 알고리즘이 default로 활성화 되었습니다.
- 새로운 JVM, JS IR backend. 추후에 안정화 되면 default가 될 에정입니다.
1. 새롭고, 더욱 강력한 타입 인터페이스 알고리즘
코틀린 1.4.0에서는, 새롭고 더욱 강력한 타입 인터페이스 알고리즘을 사용합니다. 이 새로운 알고리즘은 이미 kotlin 1.3에서 컴파일러 옵션을 설정해주는 것으로 사용할 수 있었습니다. 그리고, 이제 1.4.0 버전으로 오면서 default 값이 되었습니다. 이 새로운 알고리즘에 대한 이슈 해결 리스트는 YouTrack에서 확인할 수 있습니다. 주목할만한 개선사항은 아래와 같습니다.
YouTrack 링크 : https://youtrack.jetbrains.com/issues/KT?q=Tag:%20fixed-in-new-inference%20&_gl=1*8ydjfy*_ga*MTU5NDkxMzAzMy4xNjI2NDQxODk2*_ga_J6T75801PF*MTYyNzE4ODczOC43LjEuMTYyNzE5MDI1Mi42MA..&_ga=2.138956979.1699857455.1627130250-1594913033.1626441896
- 대부분의 경우에 자동적으로 타입 유추
- 람다 마지막 표현식에 대한 스마트 캐스트
- callable reference에 대한 스마트 캐스트
- 위임된 프로퍼티들에 대하여 발전된 인터페이스
- 자바 인터페이스에 대해 다른 파라미터를 가졌을 경우 SAM 변환
- 코틀린에서의 자바 SAM 인터페이스
1.1 대부분의 경우에 자동적으로 타입 유추
새로운 추론 알고리즘은 이전의 알고리즘이 명시적으로 타입을 선언해야 했던 여러 케이스들에서 타입을 유추할 수 있습니다.
예를 들어서, 아래 예제에서 람다 파라미터 it은 정확하게 String? 타입으로 추론됩니다.
val rulesMap: Map<String, (String?) -> Boolean> = mapOf(
"weak" to { it != null },
"medium" to { !it.isNullOrBlank() },
"strong" to { it != null && "^[a-zA-Z0-9]+$".toRegex().matches(it) }
)
코틀린 1.3에서는, 명시적인 람다 파라미터를 사용하거나, to를 명시적인 제네릭 인자를 가진 Pair 생성자로 대체할 필요가 있었습니다.
1.2 람다 마지막 표현식에 대한 스마트 캐스트
kotlin 1.3에서는, 람다 내부의 마지막 표현식은 예상되는 타입을 특정짓지 않으면 스마트 캐스트되지 않았습니다. 따라서, kotlin 1.3에서 아래 예제는 result 변수의 타입을 String?으로 추론합니다.
val result = run {
var str = currentValue()
if (str == null) {
str = "test"
}
str // the Kotlin compiler knows that str is not null here
}
// The type of 'result' is String? in Kotlin 1.3 and String in Kotlin 1.4
코틀린 1.4에서는 새로운 추론 알고리즘을 적용하면서, 람다 내부의 마지막 표현식에서 스마트 캐스트를 사용할 수 있게 되었고, 이제는 결과 람다 타입에 좀 더 간결한 타입을 추론할 수 있게 되었습니다. 따라서, result 변수의 타입은 String이 되었습니다.
코틀린 1.3에서는 String 타입을 얻기 위해서 명시적인 !! 캐스팅을 사용해서 동작할 수 있도록 해야 했지만, 이제는 이러한 캐스팅 과정은 불필요하게 되었습니다.
1.3 callable references에 대한 스마트 캐스트
코틀린 1.3에서는 스마트 캐스트 타입에 대한 멤버 참조에 접근할 수 없었습니다. 하지만 이제 코틀린 1.4에서는 가능하게 되었습니다.
fun perform(animal: Animal) {
val kFunction: KFunction<*> = when (animal) {
is Cat -> animal::meow
is Dog -> animal::woof
}
kFunction.call()
}
animal 변수가 Cat, Dog과 같은 구체적인 타입에 스마트 캐스트가 된 이후라면, animal::mewo와 animal::woof 같은 멤버 참조를 사용할 수 있게 되었습니다. 타입 체크 이후, 서브타입에 해당하는 멤버 참조에 접근할 수 있습니다.
1.4 delegated properties에 대한 더 나은 추론
그동안 by 키워드에 따라오는 delegate 표현식을 분석하는 도중에는 delegated 프로퍼티의 타입이 고려되지 않았습니다. 예를 들어서, 아래 코드는 이전에 컴파일 되지 않았습니다. 하지만 이제는, 컴파일러는 정확하게 old, new 파라미터를 String? 타입으로 추론할 수 있게 되었습니다.
import kotlin.properties.Delegates
fun main() {
var prop: String? by Delegates.observable(null) { p, old, new ->
println("$old → $new")
}
prop = "abc"
prop = "xyz"
}
1.5 다른 인자를 가진 자바 인터페이스에 대한 SAM 변환
코틀린은 초기부터 자바 인터페이스에 대한 SAM 변환을 지원했었습니다. 하지만, 하나의 케이스가 지원되지 않았었고, 이건 자바 라이브러리로 작업할 때 성가신 일이었습니다. 만약 두개의 SAM 인터페이스를 파라미터로 가지고 있는 자바 메소드를 호출한다면, 파리미터 둘 모두 람다, 일반적인 객체가 필요했습니다. 람다나, 다른 객체 파라미터 하나로 전달할 수가 없었습니다.
새로운 알고리즘은 이 이슈를 해결했고, 이제는 어떤 경우에도 SAM 인터페이스 대신에 람다를 전달할 수 있습니다.
// FILE: A.java
public class A {
public static void foo(Runnable r1, Runnable r2) {}
}
// FILE: test.kt
fun test(r1: Runnable) {
A.foo(r1) {} // Works in Kotlin 1.4
}
1.6 코틀린에서의 자바 SAM 인터페이스
kotlin 1.4에서는, SAM 인터페이스를 코틀린에서 사용할 수 있고, SAM 변환을 적용할 수 있게 되었습니다.
import java.lang.Runnable
fun foo(r: Runnable) {}
fun test() {
foo { } // OK
}
코틀린 1.3에서는, SAM 변환을 수행하기 위해서 위 자바 코드에서 foo 함수를 명시해주어야 합니다.
2. 통합 백엔드 및 확장성
코틀린 내부에서는, 세 개의 실행가능한 백엔드가 있습니다. 코틀린/JVM, 코틀린/JS, 코틀린/Native 입니다.
코틀린/JVM과 코틀린/JS의 경우 지금까지는 많은 코드들을 공유하고 있지 않았고, 서로 독립적으로 개발되어 왔습니다. 코틀린/Native는 코틀린 코드에 해당하는 iintermediate representation (IR)을 기반으로한 새로운 인프라스트럭쳐에 기반을 두고 있습니다.
우리는 이제 코틀린/JVM, 코틀린/JS를 동일한 IR로 마이그레이션을 진행하였습니다. 결과적으로, 세 개의 백엔드 모두 많은 양의 로직과 통합된 파이프라인을 공유하게 되었습니다. 이를 통해서 대부분의 기능, 성능개선, 버그 수정을 모두 한 플랫폼에서 향상시킬 수 있게 되었습니다. JVM, JS 모두 새로운 IR 기반의 백엔드는 알파단계에 있습니다.
일반적인 백엔드 인프라스트럭쳐 역시 멀티플랫폼 컴파일러 확장에 열려있습니다. 파이프라인에 접목시키거나, 커스텀한 절차를 추가하거나, 변환하는 것 역시 모든 플랫폼에서 자동적으로 동작할 수 있습니다.
우리는 새로운 JVM IR, JS IR 백엔드를 사용하는것을 추천합니다. 추후에, 피드백을 공유해주시면 감사드리겠습니다.
코틀린/JVM
코틀린 1.4.0에서는 아래와 같이 JVM에 해당되는 여러 개선사항들이 있습니다.
- 새로운 JVM IR 백엔드
- 새로운 default 메소드 생성 방식
- null 체크를 위한 통합 exception 타입
- JVM 바이트코드 내부에서의 타입 어노테이션
1. 새로운 JVM IR 백엔드
코틀린/JS와 함께, 코틀린/JVM을 모든 플랫폼에서 한번에 기능을 개발할 수 있게 해주고, 버그 수정을 가능하게 해주는 통합 IR 백엔드로 마이그레이션 시켰습니다. 이 마이그레이션으로 우리는 모든 플랫폼에서 동작 가능할 예정인 멀티플랫폼 확장 생성에 혜택을 볼 수 있게 되었습니다.
코틀린 1.4.0에서는 이러한 확장에 대해 아직 public API를 제공하고 있지 않지만, 이미 우리의 새로운 백엔드를 사용하여 컴파일러 플러그인을 개발한 Jetpack Compose를 포함한 우리의 파트너들과 아주 긴밀하게 작업하고 있습니다.
우리는 현재 알파단계인 새로운 코틀린/JVM 백엔드를 사용해보는걸 추천드립니다. 그리고 이슈나 새로운 기능 요청을 이슈 트랙커에 요청을 주시면 감사드리겠습니다. 이를 통해서 우리가 컴파일러 파이프라인을 통합하는데 도움을 주고, Jetpack Compose 같은 컴파일러 확장들을 코틀린 커뮤니티에 좀 더 빠르게 제공할 수 있게 될 것입니다.
새로운 JVM IR 백엔드를 활성화 시키기 위해서는, 그레들 빌드 스크립트 내부에 추가적인 컴파일러 옵션을 적어주어야 합니다.
kotlinOptions.useIR = true
2. 새로운 default 메소드 생성 방식
코틀린 코드를 컴파일 할 때 JVM 버전 타겟을 1.8이상으로 잡을 경우, 코틀린 인터페이스 중 추상 메소드가 아닌 것을 자바의 default 메소드로 컴파일 할 수 있습니다.
이러한 목적으로, @JVMDefault 어노테이션이 마킹되어 있는 메소드를 찾는 기능이나, -Xjvm-default와 같이 애노테이션 처리를 활성화 할 수 있는 컴파일러 옵션이 매커니즘에 포함되어 있습니다.
1.4.0 에서는, default 메소드를 생성할 수 있는 새로운 방식을 추가하였습니다. -Xjvm-default=all 모드로 설정하면 코틀린 인터페이스 중에서 추상 메소드가 아닌 것들을 모두 자바의 default 메소드로 컴파일할 수 있습니다. default 없이 컴파일된 인터페이스들을 사용하는 코드와의 호환성을 위해서 all-compatibility 모드도 추가하였습니다.
3. null 체크를 위한 통합 exception 타입
코틀린 1.4.0부터, 모든 런타임 null check는 KotlinNullPointerException, IllegalStateException, IllegalArgumentException, TypeCastException 대신에 java.lang.NullPointerException을 throw 하게 됩니다.
이 변화는 !! 연산자, 메소드 내부에서 파리미터의 null check를 서투르게 하는 경우, 플랫폼 타입의 null check, non-null 타입과 함께 as 연산자를 쓸 때 해당됩니다.
이 변화는 lateinit null check나 checkNotNull, requireNotNull같이 라이브러리로 명시적으로 null check를 하는 경우에는 해당되지 않습니다.
이 변경사항을 통해서, 코틀린 컴파일러나, 다양한 바이트코드 처리 툴인 안드로이드 R8 옵티마이저 같은 곳에서 수행될 수 있는 여러 null check 성능 개선들이 가능해졌습니다.
주목할만한 것은 개발자 관점에서는 변하는 점이 많이 없다는 것입니다. 코틀린 코드는 이전과 같이 같은 메세지로 exception을 throw할 것입니다. exception 타입이 변경되었지만, 그 안에 전달되는 정보들은 동일합니다.
4. JVM 바이트코드에서의 타입 어노테이션
코틀린에서 이제는 JVM 바이트코드 내부에서 타입 어노테이션을 생성할 수 있게 되었습니다. ( 1.8 버전 이상을 타겟으로 잡을 경우 ). 그래서 런타임에 자바 리플랙션에서도 그것들을 사용할 수 있게 되었습니다. 바이트 코드 내부에서 타입 어노테이션을 노출시키고 싶다면 아래 세 단계를 따라하시면 됩니다.
1) 적절한 어노테이션 타겟을 명시하세요 (자바의 ElementType.TYPE_USE or 코틀린의 AnnotationTarget.TYPE) 그리고 retention을 (AnnotationRetention.RUNTIME)으로 설정해야 합니다.
2) JVM 바이트코드 타겟을 1.8 이상으로 선언하세요. -jvm-target=1.8 컴파일러 옵션을 명시하여야 합니다.
3) -Xemit-jvm-type-annotations 컴파일러 옵션을 추가하세요.
현재는 표준 라이브러리가 1.6 버전을 타겟으로 컴파일되어 있기 때문에 표준 라이브러리에서 타입 어노테이션을 노출시킬 수 없는것을 주목해야 합니다.
그래서 아주 기본적인 경우만 지원되고 있습니다.
- 메소드 파라미터, 메소드 리턴 타입
- 타입 파라미터에 대한 Invariant projections, 예를 들어 Smth<@Ann Foo>, Array<@Ann Foo>
아래 예제에서, String 타입에 대한 @Foo 어노테이션은 바이트코드에서 노출될 수 있고, 라이브러리 코드에서 사용될 수 있습니다.
@Target(AnnotationTarget.TYPE)
annotation class Foo
class A {
fun foo(): @Foo String = "OK"
}
표준 라이브러리
코틀린 1.4.0 표준 라이브러이에서의 중요한 변경 리스트입니다.
- common exception 처리 API
- arrays와 collections에서의 새로운 함수
- string 조작에 대한 함수
- 비트 연산
- Delegated 프로터티 개선사항
- KType을 자바 타입으로 변환하기
- 코틀린 리플랙션에 대한 Proguard 설정들
- 현존하는 API 개선사항
- Deprecation들
- deprecated된 실험적 코루틴 기능에 대한 제외
1. common exception 처리 API
아래 API 요소들이 common 라이브러리로 이동하였습니다.
- 해당 스택 트레이스와 함께 throwable에 대한 자세한 정보를 돌려주는 Throwable.stackTraceToString() 와, 표준 에러 출력에 대한 설명을 출력해주는 Throwable.printStackTrace() 확장 함수들이 common 라이브러리로 이동하였습니다.
- Trowable.addSuppressed() 함수와 Throwable.suppressedEceptions 프로퍼티가 common 라이브러리로 이동하였습니다.
- 함수가 플랫폼 메소드로 컴파일 될 경우 체크되는 exception 타입들을 나열해주는 @Throws 어노테이션이 common 라이브러리로 이동하였습니다.
2. arrays와 collections에서의 새로운 함수
2.1 collections
1.4.0에서, 표준 라이브러리에 collections에서 쓰일 수 있는 유용한 함수들이 추가 여러개 추가되었습니다.
1) 주어진 인자들 사이에 not-null인 item들로 구성된 set을 만들어주는 함수 setOfNotNull()가 추가되었습니다.
val set = setOfNotNull(null, 1, 2, 0, null)
println(set)
[1, 2, 0]
2) sequences에 사용할 수 있는 shuffled() 함수가 추가되었습니다.
val numbers = (0 until 50).asSequence()
val result = numbers.map { it * 2 }.shuffled().take(5)
println(result.toList()) //five random even numbers below 100
[34, 72, 74, 2, 66]
3) onEachIndexed(), flatMapIndexed()가 추가되었습니다.
listOf("a", "b", "c", "d").onEachIndexed {
index, item -> println(index.toString() + ":" + item)
}
val list = listOf("hello", "kot", "lin", "world")
val kotlin = list.flatMapIndexed { index, item ->
if (index in 1..2) item.toList() else emptyList()
}
0:a
1:b
2:c
3:d
[k, o, t, l, i, n]
4) randomOrNull(), reduceOrNull() 그리고 reduceIndexedOrNull() 등이 추가되었습니다. 해당 연산들은 empty 컬렉션에 null을 리턴합니다.
val empty = emptyList<Int>()
empty.reduceOrNull { a, b -> a + b }
//empty.reduce { a, b -> a + b } // Exception: Empty collection can't be reduced.
println(empty)
[]
5) fold(), reduce()와 유사한 runningFold(), scan(), runningReduce()가 collection 원소들에 sequentially하게 적용 가능해졌습니다. 새로운 함수들은 sequence의 중간 연산을 결과로 돌려준다는 점에서 차이가 있습니다.
val numbers = mutableListOf(0, 1, 2, 3, 4, 5)
val runningReduceSum = numbers.runningReduce { sum, item -> sum + item }
val runningFoldSum = numbers.runningFold(10) { sum, item -> sum + item }
[0, 1, 3, 6, 10, 15]
[10, 10, 11, 13, 16, 20, 25]
6) 선택자 함수를 받을 수 있고, collection의 모든 원소의 합을 돌려주는 sumOf()가 추가되었습니다. sumOf()는 Int, Long, Double, UInt, ULong 타입을 돌려줄 수 있습니다. JVM위에서는 BigInteger, BigDecimal도 가능합니다.
val order = listOf<OrderItem>(
OrderItem("Cake", price = 10.0, count = 1),
OrderItem("Coffee", price = 2.5, count = 3),
OrderItem("Tea", price = 1.5, count = 2))
val total = order.sumOf { it.price * it.count } // Double
val count = order.sumOf { it.count } // Int
You've ordered 6 items that cost 20.5 in total
7) min(), max() 함수가 코틀린 collection API의 네이밍 컨벤션을 따르기 위해서 minOrNull(), maxOrNull()로 이름이 변경되었습니다. 함수에서의 *OrNull 네이밍은 collection이 empty라면 null을 돌려주는 것을 의미합니다. 동일하게 minBy(), maxBy(), minWith(), maxWith() 도 OrNull 네이밍을 붙이도록 변경되었습니다.
val order = listOf<OrderItem>(
OrderItem("Cake", price = 10.0, count = 1),
OrderItem("Coffee", price = 2.5, count = 3),
OrderItem("Tea", price = 1.5, count = 2))
val highestPrice = order.maxOf { it.price }
The most expensive item in the order costs 10.0
8) flatMap, flatMapTo에서 receiver 타입과 매칭되지 않는 return 타입도 변환하여 사용할 수 있도록 overload 메소드가 추가되었습니다.
- Iterable, Array, Map에서 Sequence 로의 변환
- Sequence에서 Iterable 로의 변환
val list = listOf("kot", "lin")
val lettersList = list.flatMap { it.asSequence() }
val lettersSeq = list.asSequence().flatMap { it.toList() }
[k, o, t, l, i, n]
[k, o, t, l, i, n]
9) removeFirstOrNull(), removeLastOrNull()이 추가되었습니다.
2.2 Arrays
다른 타입에서도 작업할 때 동일한 경험을 제공해주기 위해서, arrays에 몇가지 함수를 새로 추가하였습니다.
- 임의의 순서로 배열의 원소를 조정해주는 shuffle() 메소드가 추가되었습니다.
- 배열에 주어진 action을 수행하고 배열 그 자체를 돌려주는 onEach() 메소드가 추가되었습니다.
- associateWith(), associateWithTo() 메소드를 통해서 배열의 원소를 key로 하는 map을 만들 수 있게 되었습니다.
- 배열의 일부분을 reverse() 메소드를 통하여 반대로 변경할 수 있습니다.
- 배열의 일부분을 sortDesending() 메소드를 통해 내림차순으로 정렬할 수 있습니다.
var language = ""
val letters = arrayOf("k", "o", "t", "l", "i", "n")
val fileExt = letters.onEach { language += it }
.filterNot { it in "aeuio" }.take(2)
.joinToString(prefix = ".", separator = "")
println(language) // "kotlin"
println(fileExt) // ".kt"
letters.shuffle()
letters.reverse(0, 3)
letters.sortDescending(2, 5)
println(letters.contentToString()) // [k, o, t, l, i, n]
kotlin
.kt
[l, k, t, n, i, o]
추가적으로, CharArray / ByteArray와 String 사이에 새로운 변환함수가 추가되었습니다.
- ByteArray.decodeToString(), String.encodeToByteArray()
- CharArray.concatToString(), String.toCharArray()
val str = "kotlin"
val array = str.toCharArray()
println(array.concatToString())
kotlin
3. ArrayDeque
double-ended queue 구현체인 ArrayDeque 클래스가 추가되었습니다. double ended queue는 큐의 양끝에서 원소를 추가하고 제거할 수 있는 큐입니다. default로 queue나 stack이 코드에서 필요한 경우 double ended queue를 사용할 수 있습니다.
fun main() {
val deque = ArrayDeque(listOf(1, 2, 3))
deque.addFirst(0)
deque.addLast(4)
println(deque) // [0, 1, 2, 3, 4]
println(deque.first()) // 0
println(deque.last()) // 4
deque.removeFirst()
deque.removeLast()
println(deque) // [1, 2, 3]
}
[0, 1, 2, 3, 4]
0
4
[1, 2, 3]
ArrayDeque는 내부에서 사용하는 배열의 resizable 로직 을 사용합니다. 순환 버퍼를 이용하여 원소를 저장하고, 꽉 찼을 때 resize를 진행합니다.
4. String 조작 함수 추가
표준 라이브러리 1.4.0에서는 String 조작에 있어서 개선된 API들이 포함되었습니다.
- StringBuilder에 set(), setRange(), deleteAt(), deleteRange(), appendRange()와 같은 유용한 확장 함수들이 추가되었습니다.
val sb = StringBuilder("Bye Kotlin 1.3.72")
sb.deleteRange(0, 3)
sb.insertRange(0, "Hello", 0 ,5)
sb.set(15, '4')
sb.setRange(17, 19, "0")
print(sb.toString())
Hello Kotlin 1.4.0
- StringBuilder에서 사용되던 몇몇 함수들이 common 라이브러리에서도 사용될 수 있게 되었습니다. append(), insert(), substring(), setLength() 등이 있습니다.
- Appendable.appendLine()과 StringBuilder.appendLine()이 common 라이브러리에 추가되었습니다.
println(buildString {
appendLine("Hello,")
appendLine("world")
})
Hello,
world
5. 비트 연산
비트 연산을 위한 새로운 함수들이 추가되었습니다.
- countOneBits()
- countLeadingZeroBits()
- countTrailingZeroBits()
- takeHighesOneBit()
- takeLowestOneBit()
- rotateLeft(), rotateRight()
val number = "1010000".toInt(radix = 2)
println(number.countOneBits())
println(number.countTrailingZeroBits())
println(number.takeHighestOneBit().toString(2))
2
4
1000000
6. KType에서 Java 타입으로의 변환
표준 라이브러리 내부에 새로운 확장 프로퍼티 KType.javaType을 통해서 KType에서 java.lang.reflect.Type으로 kotlin-reflect 의존성 없이 변환할 수 있게 되었습니다.
import kotlin.reflect.javaType
import kotlin.reflect.typeOf
@OptIn(ExperimentalStdlibApi::class)
inline fun <reified T> accessReifiedTypeArg() {
val kType = typeOf<T>()
println("Kotlin type: $kType")
println("Java type: ${kType.javaType}")
}
@OptIn(ExperimentalStdlibApi::class)
fun main() {
accessReifiedTypeArg<String>()
// Kotlin type: kotlin.String
// Java type: class java.lang.String
accessReifiedTypeArg<List<String>>()
// Kotlin type: kotlin.collections.List<kotlin.String>
// Java type: java.util.List<java.lang.String>
}
Kotlin type: java.lang.String (Kotlin reflection is not available)
Java type: class java.lang.String
Kotlin type: java.util.List<java.lang.String> (Kotlin reflection is not available)
Java type: java.util.List<java.lang.String>
7. 코틀린 리플렉션에서의 Proguard 설정
1.4.0부터, kotlin-reflect.jar 내부에 코틀린 리플렉션을 위한 Proguard/R8을 탑재하였습니다. 이를 통해서 R8, Proguard를 사용하는 대부분의 안드로이드 프로젝트는 kotlin-reflect 같이 추가적인 설정 없이 작동할 수 있게 되었습니다. 이제 더이상 Proguard rule을 kotlin-reflect internal에 복사-붙여넣기 할 필요가 없습니다. 하지만 여전히 리플랙션할 API의 모든 리스트를 명시해주어야 합니다.
8. 기존 API 개선사항
- 여러 함수들이 null receiver에서 동작하게 되었습니다.
- strings에서의 toBoolean()
- arrays에서의 contentEquals(), contentHashcode(), contentToString()
- Double 및 Float에서, NaN, NEGATIVE_INFINITY, POSITIVE_INFINITY가 const로 정의되었고, 어노테이션 인자로 사용할 수 있게 되었습니다.
- Double 및 Float에서 새로운 상수 SIZE_BITS와 SIZE_BYTES가 추가되었고, 바이너리 형태에서 비트, 바이트가 몇개의 숫자로 표현되고 있는지 알 수 있게 되었습니다.
- maxOf, minOf 탑 레벨 함수들이 인자로 vararg를 받을 수 있게 되었습니다.
9. Deprecation
1) Double, Float에서 toShort()와 toByte()
좁은 변수 범위나, 작은 변수 사이즈에서 예상치 못한 값을 돌려주었기 때문에 Double, Float에서 toShort(), toByte()함수가 deprecated되었습니다.
floating-point 숫자를 Byte나 Short로 변경하기 위해서는, 두 가지 단계 변경을 거쳐야 합니다. 첫 번째로 Int로 변경하고, 두 번째로 타겟 타입으로 다시 변경하여야 합니다.
2) floating-point 배열에서 contains(), indexOf(), lastIndexOf()
IEEE 754 표준에 동일해야 하지만, 코너 케이스에서 몇가지 문제가 발생했기 때문에 FloatArray, DoubleArray contains(), indexOf(), lastIndexOf() 확장함수를 deprecated 하였습니다.
3) min(), max() collection 함수들
함수 동작에 대해 좀 더 잘 나타내는것, minOrNull(), maxOrNull()에 대한 선호로 인하여 min(), max() 함수를 deprecated 하였습니다.
10. deprecated된 experimental 코루틴 제외
kotlin.coroutines.experimental API는 kotlin.coroutines에 대한 선호로 인하여 1.3.0에서 deprecated되었습니다. 1.4.0에서는 deprecation 사이클을 지키기 위해 표준라이브러리에서 kotlin.coroutines.experimental을 제거하였습니다. JVM위에서 아직도 이를 사용하고 있다면, 이와 호환되는 kotlin-coroutines-exxperimental-compat.jar artifact를 제공하고 있습니다. Maven에 공개되어 있습니다.
출처
'Kotlin' 카테고리의 다른 글
코틀린 1.4, 1.5 버전에서의 변경사항 (0) | 2021.08.15 |
---|---|
(번역글) kotlin 1.5.20 릴리즈 노트 (0) | 2021.08.15 |
(번역글) 코틀린 1.5.0 버전 릴리즈 노트 (0) | 2021.08.14 |
(번역글) 코틀린 1.4.30 릴리즈 노트 (0) | 2021.08.12 |
(번역글) 코틀린 1.4.20 버전 릴리즈 노트 (0) | 2021.08.11 |