2021년 4월 14일
Kotlin 1.5.0-RC가 출시되었으며 1.5.0 버전에 계획된 모든 기능이 포함되었습니다. 이번 릴리스에서는 새로운 언어 기능, 표준 라이브러리 업데이트, 향상된 테스트 라이브러리 및 다른 많은 변경 사항이 마지막 점검을 받고 있습니다. 릴리스 전에 추가로 변경될 사항은 수정 사항뿐입니다.
1.5.0-RC를 사용하여 실제 프로젝트에서 현대적인 Kotlin API를 시도하고 릴리스 버전을 더 나아지게 만들 수 있습니다. 발견한 문제를 이슈 트래커인 YouTrack에 보고해 주시면 감사하겠습니다.
1.5.0-RC 설치하기
이 게시물에서는 1.5.0-RC에서 Kotlin 표준 라이브러리와 테스트 라이브러리에 대한 변경 사항을 안내해 드리겠습니다:
- 안정적인 부호 없는 정수 형식
- java.nio.file.Path에 대한 확장 기능
- 문자열 및 문자 API 개선
- Duration API 변경 사항
- 모듈러 산술을 위한 새로운 수학 연산
- 새로운 컬렉션 함수
- 테스트 라이브러리 변경 사항
자세한 내용은 아래에서 확인하실 수 있습니다!
안정적인 부호 없는 정수 형식
표준 라이브러리에는 음수가 아닌 정수 작업에 유용한 부호 없는 정수 API가 포함되어 있습니다.
포함된 내용은 다음과 같습니다:
- 부호 없는 숫자 형식: UInt, ULong, UByte, UShort 및 변환과 관련된 함수
- 부호 없는 정수의 배열, 범위 및 진행에 대한 집계 형식: UIntArray, UIntRange 및 다른 유형을 위한 유사한 컨테이너
부호 없는 정수 형식은 Kotlin 1.3부터 베타 버전으로 제공되었습니다. 이제 부호 없는 정수 형식 및 연산을 안정적으로 분류하여 옵트인(opt-in) 없이 실제 프로젝트에서 안전하게 사용할 수 있도록 만들었습니다.
주로 안정적인 새 API는 다음과 같습니다:
- 부호 없는 정수 형식
- 부호 없는 정수 형식의 범위 및 진행
- 부호 없는 정수 형식과 작동하는 함수
val zero = 0U // 리터럴 접미사를 사용하여 부호 없는 숫자 정의
val ten = 10.toUInt() // 음수가 아닌 정수를 변환하여 정의
val range: UIntRange = zero..ten // 범위 및 진행을 위한 별도의 유형
for (i in range) print(i)
println()
println("UInt covers the range from ${UInt.MIN_VALUE} to ${UInt.MAX_VALUE}") // UInt는 0부터 4294967295까지의 범위를 포함합니다
배열의 부호 없는 정수 및 배열을 기반으로 하는 부호 없는 정수 varargs는 여전히 베타 상태입니다. 이러한 기능을 코드에서 사용하려면 @ExperimentalUnsignedTypes 주석을 사용하여 옵트인을 선택할 수 있습니다.
Kotlin에서 부호 없는 정수에 대한 자세한 내용을 알아보려면 Kotlin 문서를 확인하십시오.
java.nio.file.Path API의 확장 기능
Kotlin은 이제 확장 함수를 통해 Java의 현대 비동기 IO를 Kotlin 스타일로 사용할 수 있도록 기본적으로 제공합니다. 이 확장 함수는 java.nio.file.Path를 대상으로 합니다.
다음은 작은 예제입니다:
import kotlin.io.path.*
import java.nio.file.Path
fun main() {
// div (/) 연산자로 경로 생성
val baseDir = Path("/base")
val subDir = baseDir / "subdirectory"
// 디렉터리 내의 파일 목록 나열
val kotlinFiles = Path("/home/user").listDirectoryEntries("*.kt")
// 모든 Kotlin 파일의 라인 수 계산
val totalLines = kotlinFiles.sumOf { file -> file.useLines { lines -> lines.count() } }
}
이러한 확장 함수들은 Kotlin 1.4.20에서 실험적으로 도입되었으며 이제 선택 옵션 없이 사용할 수 있습니다. 사용 가능한 함수 목록은 kotlin.io.path 패키지를 확인하십시오.
File API에 대한 기존 확장 기능도 여전히 사용 가능하므로 가장 좋아하는 API를 선택할 수 있습니다.
로케일에 관계없는 대문자 및 소문자용 API
문자열 및 문자의 대소문자를 변경하는 데 사용되는 stdlib 함수(toUpperCase(), toLowerCase(), toTitleCase()) 중 많은 분들이 익숙할 것입니다. 이러한 함수들은 일반적으로 잘 작동하지만 다양한 플랫폼 로캘과 관련하여 문제를 일으킬 수 있습니다. 이러한 함수들은 로캘에 따라 결과가 다를 수 있기 때문입니다. 예를 들어, "Kotlin".toUpperCase()는 무엇을 반환할까요? "당연히 KOTLIN"이라고 할 것입니다. 그러나 터키 로캘에서는 대문자 i가 İ이므로 결과가 다릅니다: KOTLİN.
이제 문자열 및 문자의 대소문자를 변경하는 로케일에 관계없는 새로운 API가 있습니다. 이 API에는 uppercase(), lowercase(), titlecase() 확장 함수 및 이와 관련된 *Char() 변형 함수가 포함되어 있을 것입니다. 이미 1.4.30에서 미리 봤을 수 있습니다.
새 함수들은 플랫폼 로캘 설정에 관계없이 항상 동일한 방식으로 작동합니다. 이러한 함수를 호출하고 나머지는 stdlib에 맡기십시오.
// 이전 API 대체
println("Kotlin".toUpperCase()) // KOTLIN 또는 KOTLİN 또는?..
// 새 API 사용
println("Kotlin".uppercase()) // 항상 KOTLIN
JVM에서는 현재 로캘을 인수로 사용하여 로캘에 민감한 대문자 변경을 수행할 수 있습니다.
"Kotlin".uppercase(Locale.getDefault()) // 로캘에 민감한 대문자 변경
이러한 새로운 함수들은 이제 이전 함수들을 완전히 대체하며, 현재는 이전 함수들을 사용하지 않도록 권장하고 있습니다.
문자를 코드로 변환 및 문자를 숫자로 변환하는 명확한 함수
문자의 UTF-16 코드를 얻는 연산인 toInt() 함수는 문자열 "4"에 대한 String.toInt()와 매우 유사하기 때문에 흔한 함정이었습니다. 이는 Int로 표시된 이 숫자를 생성하는 단일 문자열에 대한 String.toInt()와 매우 유사하기 때문입니다.
"4".toInt() // 4를 반환합니다.
'4'.toInt() // 52를 반환합니다.
게다가, Char '4'에 대한 숫자 값 4를 반환하는 공통 함수가 없었습니다.
이러한 문제를 해결하기 위해 문자와 해당 정수 코드 및 숫자 값 사이의 변환을 위한 새로운 함수 집합이 있습니다.
- Char(code) 및 Char.code는 문자와 그 코드 간의 변환을 수행합니다.
- Char.digitToInt(radix: Int) 및 그 *OrNull 버전은 지정된 진법의 숫자에서 정수를 생성합니다.
- Int.digitToChar(radix: Int)는 지정된 진법에서 정수를 나타내는 숫자로부터 문자를 생성합니다.
이러한 함수들은 이름이 명확하며 코드를 더 읽기 쉽게 만듭니다.
val capsK = Char(75) // ‘K’
val one = '1'.digitToInt(10) // 1
val digitC = 12.digitToChar(16) // 16진수 숫자 ‘C’
println("${capsK}otlin ${one}.5.0-R${digitC}") // “Kotlin 1.5.0-RC”
println(capsK.code) // 75
이러한 새로운 함수들은 Kotlin 1.4.30에서 미리 보기로 제공되었으며 이제 안정적으로 사용할 수 있습니다. 문자(char)에서 숫자로의 변환을 위한 이전 함수(Char.toInt() 및 다른 숫자 형식에 대한 유사한 함수) 및 숫자에서 문자로의 변환을 위한 이전 함수(Long.toChar() 및 Int.toChar()를 제외한 다른 형식에 대한 유사한 함수)는 이제 사용이 피권장되고 있습니다.
확장된 멀티플랫폼 Char API
우리는 표준 라이브러리의 멀티플랫폼 부분을 계속 확장하여 그 모든 기능을 멀티플랫폼 프로젝트 공통 코드에서 사용할 수 있도록 했습니다.
이제 모든 플랫폼 및 공통 코드에서 사용 가능한 여러 Char 함수들이 있습니다. 이러한 함수들은 다음과 같습니다:
- Char.isDigit(), Char.isLetter(), Char.isLetterOrDigit(): 문자가 글자인지 또는 숫자인지 확인합니다.
- Char.isLowerCase(), Char.isUpperCase(), Char.isTitleCase(): 문자의 대소문자를 확인합니다.
- Char.isDefined(): 문자가 Cn(정의되지 않음) 이외의 Unicode 일반 카테고리를 가지고 있는지 확인합니다.
- Char.isISOControl(): 문자가 ISO 제어 문자인지 확인합니다. 이는 코드 범위 \u0000..\u001F 또는 \u007F..\u009F 내에 있는 문자를 가리킵니다.
Char.category 속성 및 그 반환 형식인 enum class CharCategory는 이제 멀티플랫폼 프로젝트에서 사용할 수 있습니다.
val array = "Kotlin 1.5.0-RC".toCharArray()
val (letterOrDigit, punctuation) = array.partition { it.isLetterOrDigit() }
val (upperCase, notUpperCase ) = array.partition { it.isUpperCase() }
println("$letterOrDigit, $punctuation") // [K, o, t, l, i, n, 1, 5, 0, R, C], [ , ., ., -]
println("$upperCase, $notUpperCase") // [K, R, C], [o, t, l, i, n, , 1, ., 5, ., 0, -]
if (array[0].isDefined()) println(array[0].category)
Strict 버전의 String?.toBoolean()
Kotlin의 String?.toBoolean() 함수는 문자열에서 부울 값을 만들 때 널 가능성을 고려하여 널이 아닌 문자열 "true"의 경우 항상 true를 반환하고, 그 외의 모든 문자열, 포함하여 널인 경우에는 항상 false를 반환합니다.
이 동작은 자연스럽게 보일 수 있지만, 잠재적으로 오류가 있는 상황을 숨길 수 있습니다. 이 함수로 변환하는 것은 해당 문자열이 예상치 못한 값을 가지더라도 항상 부울 값을 얻게 됩니다.
새로운 대소문자 구분하는 Strict 버전의 String?.toBoolean()은 이러한 오류를 피하기 위해 도움을 줍니다:
String.toBooleanStrict(): "true" 및 "false" 리터럴 이외의 모든 입력에 대해 예외를 던집니다.
String.toBooleanStrictOrNull(): "true" 및 "false" 리터럴 이외의 모든 입력에 대해 null을 반환합니다.
println("true".toBooleanStrict()) // True
// println("1".toBooleanStrict()) // 예외 발생
println("1".toBooleanStrictOrNull()) // null
println("True".toBooleanStrictOrNull()) // null: 함수는 대소문자를 구분합니다
Duration API 변경 사항
실험적인 기간 및 시간 측정 API는 stdlib에서 1.3.50 버전부터 사용할 수 있었습니다. 이 API는 시간 간격을 정확하게 측정하기 위한 API를 제공합니다.
이 API의 주요 클래스 중 하나는 Duration입니다. 이는 두 시간 순간 간의 시간 양을 나타냅니다. 1.5.0에서 Duration은 API 및 내부 표현 방식 모두에서 중요한 변경 사항을 받았습니다.
Duration은 이제 Double 대신에 내부 표현에 Long 값을 사용합니다. Long 값의 범위는 나노초 정밀도로 백 년 이상 또는 밀리초 정밀도로 천 백만 년 이상을 나타낼 수 있게 해줍니다. 그러나 이전에 지원되던 서브-나노초 지속 시간은 더 이상 사용할 수 없습니다.
또한 정수 값을 사용하여 Duration 인스턴스를 만들기 위한 새로운 팩토리 함수 집합을 도입하고 있습니다. 이 함수들은 Duration.inWholeMinutes, Duration.inWholeSeconds 등과 같은 다양한 시간 단위에 대해 사용할 수 있습니다. 이러한 함수들은 Duration.inMinutes와 같은 Double 기반 속성을 대체합니다.
import kotlin.time.ExperimentalTime
import kotlin.time.Duration
@ExperimentalTime
fun main() {
val duration = Duration.milliseconds(120000)
println("There are ${duration.inWholeSeconds} seconds in ${duration.inWholeMinutes} minutes")
}
이러한 큰 변경 사항을 고려하여 1.5.0에서는 전체 기간 및 시간 측정 API가 실험적인 상태로 유지되며 @ExperimentalTime 주석을 통해 활성화해야 합니다. 새 버전을 사용하고 의견을 공유해주시기 바랍니다.
수학 연산: 바닥 나눗셈과 모듈로 연산자
Kotlin에서 정수의 나눗셈 연산자 (/)은 소수 부분을 버리는 잘린(divided) 나눗셈을 나타냅니다. 모듈러 산술(modular arithmetic)에서는 다른 대안인 바닥(floor) 나눗셈이 있으며 이는 결과를 내림(작은 정수 쪽으로)하여 얻습니다. 이것은 음수 숫자에 대해서는 다른 결과를 생성합니다.
이전에는 바닥 나눗셈을 위해 다음과 같은 사용자 정의 함수가 필요했습니다.
fun floorDivision(i: Int, j: Int): Int {
var result = i / j
if (i != 0 && result <= 0) result--
return result
}
1.5.0-RC에서는 정수에서 바닥 나눗셈을 수행하는 floorDiv() 함수를 제공합니다.
println("Truncated division -5/3: ${-5 / 3}")
println("Floored division -5/3: ${(-5).floorDiv(3)}")
1.5.0에서는 새로운 mod() 함수를 소개합니다. 이제 이 함수는 이름 그대로 작동합니다. 즉, 바닥 나눗셈의 나머지를 반환합니다.
이것은 Kotlin의 rem() (또는 % 연산자)와 다릅니다. 모듈러(modulus)는 a와 a.floorDiv(b) * b의 차이입니다. 0이 아닌 모듈러는 항상 b와 동일한 부호를 가지며 a % b는 다른 부호를 가질 수 있습니다. 이것은 예를 들어 순환 리스트를 구현할 때 유용할 수 있습니다.
fun getNextIndexCyclic(current: Int, size: Int) = (current + 1).mod(size)
fun getPreviousIndexCyclic(current: Int, size: Int) = (current - 1).mod(size)
// %와 달리, mod()는 (current - 1)이 0보다 작아도 예상한 양수 값을 생성합니다.
val size = 5
for (i in 0..(size * 2)) print(getNextIndexCyclic(i, size))
println()
for (i in 0..(size * 2)) print(getPreviousIndexCyclic(i, size))
컬렉션: firstNotNullOf() 및 firstNotNullOfOrNull()
Kotlin 컬렉션 API는 내장 함수를 사용하여 컬렉션에서 일반적인 작업 범위를 다룹니다. 흔하지 않은 경우에는 이러한 함수들을 조합하여 사용합니다. 이렇게하면 동작하지만 항상 우아하게 보이지는 않고 오버헤드를 일으킬 수 있습니다.
예를 들어, 컬렉션 요소의 선택자 함수(selector function)의 첫 번째 null이 아닌 결과를 얻으려면 mapNotNull()와 first()를 호출할 수 있습니다. 1.5.0에서는 이를 새로운 함수 firstNotNullOf()의 단일 호출로 수행할 수 있습니다. firstNotNullOf()와 함께, 값을 반환할 것이 없으면 null을 생성하는 *orNull() 대응 함수도 추가합니다.
다음은 코드를 간소화할 수 있는 방법의 예제입니다.
가정하겠습니다. 널 가능한 속성을 가진 클래스가 있으며 이 클래스 인스턴스 목록에서 첫 번째 null이 아닌 값을 필요로 합니다.
class Item(val name: String?)
컬렉션을 반복하고 속성이 null이 아닌지 확인하는 방법을 구현할 수 있습니다.
// Option 1: 수동 구현
for (element in collection) {
val itemName = element.name
if (itemName != null) return itemName
}
return null
또 다른 방법은 이전에 존재한 함수 mapNotNull()와 firstOrNull()을 사용하는 것입니다. mapNotNull()는 중간 컬렉션을 생성하므로 특히 큰 컬렉션의 경우 추가 메모리가 필요하며 여기에는 시퀀스로 변환하기도 필요할 수 있습니다.
// Option 2: 이전 stdlib 함수
return collection
// .asSequence() // 큰 컬렉션의 중간 목록 생성 방지
.mapNotNull { it.name }
.firstOrNull()
새 함수를 사용한 경우는 다음과 같습니다.
// Option 3: 새로운 firstNotNullOfOrNull()
return collection.firstNotNullOfOrNull { it.name }
Kotlin 테스트 라이브러리 변경 사항
우리는 Kotlin 테스트 라이브러리 kotlin-test에 대한 주요 업데이트를 여러 버전 동안 출시하지 않았지만 이제 여러 기대되는 변경 사항을 제공합니다. 1.5.0-RC 버전에서 새로운 기능을 시도해 볼 수 있습니다.
멀티플랫폼 프로젝트에서 단일 kotlin-test 종속성.
Kotlin/JVM 소스 세트에 대한 테스트 프레임워크의 자동 선택.
단언 함수 업데이트.
멀티플랫폼 프로젝트에서의 kotlin-test 종속성
우리는 멀티플랫폼 프로젝트의 구성 프로세스를 계속 개발하고 있습니다. 1.5.0에서는 모든 소스 세트에 대한 kotlin-test 종속성을 설정하는 것이 더 쉬워졌습니다.
이제 공통 테스트 소스 세트에서 kotlin-test 종속성만 추가하면 됩니다. Gradle 플러그인은 다른 소스 세트에 대한 해당 플랫폼 종속성을 추론할 것입니다.
- JVM 소스 세트의 경우 kotlin-test-junit을 사용합니다. kotlin-test-junit-5 또는 kotlin-test-testng를 명시적으로 활성화하면 해당 프레임워크로 전환할 수도 있습니다(자세한 내용은 아래 참조).
- Kotlin/JS 소스 세트의 경우 kotlin-test-js를 사용합니다.
- 공통 소스 세트의 경우 kotlin-test-common 및 kotlin-test-annotations-common을 사용합니다.
- Kotlin/Native 소스 세트의 경우 별도의 아티팩트가 필요하지 않습니다. Kotlin/Native은 kotlin-test API의 내장 구현을 제공합니다.
자세한 내용은 1.5.0 공식 릴리스 노트를 참조하십시오.
Kotlin/JVM 소스 세트에 대한 테스트 프레임워크의 자동 선택
공통 테스트 소스 세트에서 위에서 설명한대로 kotlin-test 종속성을 지정하면 JVM 소스 세트가 자동으로 JUnit 4 종속성을 받습니다. 이게 전부입니다! 바로 테스트를 작성하고 실행할 수 있습니다!
아래는 Groovy DSL에서 보이는 방법입니다:
kotlin {
sourceSets {
commonTest {
dependencies {
// 이로써 종속성이 포함됩니다
implementation kotlin('test')
}
}
}
}
Kotlin DSL에서는 다음과 같습니다:
kotlin {
sourceSets {
val commonTest by getting {
dependencies {
// 이로써 종속성이 포함됩니다
implementation(kotlin("test"))
}
}
}
}
JUnit 5 또는 TestNG로 전환하려면 테스트 작업 내에서 useJUnitPlatform() 또는 useTestNG() 함수를 호출하면 됩니다.
kotlin {
jvm {
testRuns["test"].executionTask.configure {
// TestNG 지원 활성화
useTestNG()
// 또는
// JUnit Platform (또는 JUnit 5) 지원 활성화
useJUnitPlatform()
}
}
}
동일한 작업을 JVM 전용 프로젝트에서 kotlin-test 종속성을 추가하면 동작합니다.
단언 함수 업데이트
1.5.0에서는 새로운 단언 함수와 기존 함수의 개선 사항을 포함한 여러 단언 함수를 준비했습니다.
먼저 새로운 함수를 간단히 살펴보겠습니다:
- assertIs<T>() 및 assertIsNot<T>()는 값의 유형을 확인합니다.
- assertContentEquals()는 배열, 시퀀스 및 모든 Iterable에 대한 컨테이너 내용을 비교합니다. 더 정확하게는 예상 값과 실제 값이 동일한 순서로 동일한 요소를 포함하는지 확인합니다.
- Double 및 Float에 대한 assertEquals() 및 assertNotEquals()에는 새로운 오버로드가 있으며, 세 번째 매개변수로 정밀도(precision)를 사용합니다.
- assertContains()는 contains() 연산자가 정의된 배열, 리스트, 범위 등 어떤 객체에 항목이 있는지 확인합니다.
다음은 이러한 함수를 사용하는 방법을 보여주는 간단한 예제입니다:
@Test
fun test() {
val expectedArray = arrayOf(1, 2, 3)
val actualArray = Array(3) { it + 1 }
val first: Any = actualArray[0]
assertIs<Int>(first)
// first가 Int로 스마트 캐스트됩니다
println("${first + 1}")
assertContentEquals(expectedArray, actualArray)
assertContains(expectedArray, 2)
val x = sin(PI)
// 정밀도 매개변수
val tolerance = 0.000001
assertEquals(0.0, x, tolerance)
}
기존 단언 함수에 대해서는 이제 assertTrue(), assertFalse() 및 expect()에 전달되는 람다 내에서 일시 중단 함수를 호출할 수 있습니다. 이 함수들은 이제 인라인 함수로 구현되었기 때문입니다.
1.5.0의 모든 기능을 시도해 보세요
1.5.0-RC로 모던 Kotlin API를 실제 프로젝트에 가져와 보세요!
IntelliJ IDEA 또는 Android Studio에서 Kotlin 플러그인 1.5.0-RC를 설치하세요. EAP 플러그인 버전을 얻는 방법을 알아보세요.
기존 프로젝트를 1.5.0-RC로 빌드하여 1.5.0에서 어떻게 작동하는지 확인하세요. 미리보기 릴리스를 위한 새로운 간소화된 구성으로 인해 Kotlin 버전을 1.5.0-RC로 변경하고 필요한 경우 종속성 버전을 조정하기만 하면 됩니다.
1.5.0-RC 설치
항상 Kotlin Playground에서 최신 버전을 온라인으로 시도해 볼 수 있습니다.
호환성
모든 기능 릴리스와 마찬가지로 Kotlin 1.5.0에서 이전에 발표된 변경 사항의 일부를 종료하는 기간이 끝나고 있습니다. 모든 경우는 언어 위원회에서 주의 깊게 검토했으며 Kotlin 1.5.0의 호환성 가이드 및 YouTrack에서 이러한 변경 사항을 탐색할 수 있습니다.
릴리스 후보 노트
이제 Kotlin 1.5.0의 릴리스 후보에 도달했으므로 컴파일 및 게시를 시작할 때입니다! Kotlin 1.5.0-RC로 생성된 바이너리는 Kotlin 1.5.0과 호환되는 것으로 보장됩니다.
피드백 공유
이제 다음 기능 릴리스에 영향을 미칠 수 있는 마지막 기회입니다! 발견한 문제를 이슈 트래커에서 공유해 주세요. Kotlin 1.5.0을 여러분과 커뮤니티를 위해 더 나은 것으로 만들어 보세요!
원문
https://blog.jetbrains.com/kotlin/2021/04/kotlin-plugin-2021-1-released/
댓글