Kotlin 1.9, 2.0 미리보기
1.9버전 변경점 알아보고, 2.0버전 미리보기
Kotlin 1.9 변경점
until 연산자에 대한 특수 구문 도입
1.8 이하에서는 아래와 같이 until
연산자를 사용하여, 0부터 9까지의 범위를 표현할 수 있습니다.
for (i in 0 until 10) { ... }
1.9 부터는 ..<
연산자로 동일한 기능을 수행할 수 있습니다.
for (i in 0 ..< 10) { ... }
Jetbrains 설명에 따르면, Kotlin의 범위 구문이 구체적이지 않다는 의견을 받아왔고, 이를 해결하기 위해 ..<
연산자를 도입하였다고 합니다. UX연구 결과에 따르면, ..<
가 until
에 비해 20~30% 정도 개발자의 실수를 줄이는 효과가 있다고 합니다.
data object 도입
class에 대하여 toString()
. equals()
, hashCode()
를 자동으로 처리하여 주는 data class와 달리, object는 이러한 기능을 제공하지 않습니다. 하지만, 1.9 부터는 data object를 도입하여 data class
와 처럼 3가지의 함수를 자동 처리하여 줍니다.
sealed interface ReadResult
data class Number(val number: Int) : ReadResult
data class Text(val text: String) : ReadResult
data object EndOfFile : ReadResult
fun main() {
println(Number(7)) // Number(number=7)
println(EndOfFile) // EndOfFile
}
Enum.values()
를 대체하기 위해 Enum.entries
도입
- 기존
Enum.values()
의 문제점values()
를 사용하면 모든 enum 항목을 Array로 만들어 반환합니다.- 이때 항상 새로운 Array의 instance를 할당하기 때문에 메모리 성능 문제가 일으킵니다.
- Array를 반환하기 때문에, 임의적으로 외부에서 Array의 값을 변경할 수 있는 문제도 있습니다.
- 참고
- (https://mail.openjdk.org/pipermail/compiler-dev/2018-July/012242.html)
- (https://dzone.com/articles/memory-hogging-enumvalues-method)
위의 문제점을 개선한 entries
가 도입되었습니다.
Array
가 아닌List<E>
를 상속한 타입인EnumEntries
를 반환합니다.- 함수가 아닌 속성으로, 미리 할당되어 있기 때문에 메모리 문제가 개선됩니다.
enum class Color(val colorName: String, val rgb: String) {
RED("Red", "#FF0000"),
ORANGE("Orange", "#FF7F00"),
YELLOW("Yellow", "#FFFF00")
}
fun findByRgb(rgb: String): Color? = Color.entries.find { it.rgb == rgb }
value class에서 보조 생성자 선언 가능
1.8 까지는 value class에서 보조 생성자를 선언할 수 없었으나, 1.9 부터는 가능해졌습니다.
@JvmInline
value class Person(private val fullName: String) {
// 1.4.30 부터 도입
init {
check(fullName.isNotBlank()) {
"Full name shouldn't be empty"
}
}
// 1.9 부터 도입
constructor(name: String, lastName: String) : this("$name $lastName") {
check(lastName.isNotBlank()) {
"Last name shouldn't be empty"
}
}
}
fun main() {
val name1 = Person("Kotlin", "Mascot")
val name2 = Person("Kodee")
name1.greet() // Hello, Kotlin Mascot
println(name2.length) // 5
}
버전 2 미리보기
버전이 1.9 이후 바로 2.0으로 넘어갑니다.
K2 컴파일러 안정화
2022년 6월에 1.7 버전에서 알파 버전으로 공개되었던 새로운 Kotlin 컴파일러(K2)가 2.0 버전에서 안정화됩니다.
1.5 KLOC/s : 1초당 1,500줄의 코드를 처리함을 의미
특징
- 약 2배 이상의 빠른 컴파일 속도
- 신 기능
- Static extensions
- Colletion literals
- Name-based destructuring
- Context receivers
- Explicit fields
새로운 기능은 아직 정확히 어떠한 버전에서 도입될지 정해지지 않은 상태입니다.
Static extensions
(https://github.com/Kotlin/KEEP/blob/statics/proposals/statics.md#kotlin-statics-and-static-extensions)
static 키워드가 도입됩니다.
static members/extensions/objects 개념이 새롭게 등장하게 됩니다.
static interface, overrides 등장
static interface Parseable<T> {
fun parse(s: String): T // static interface with `parse` function
}
class Color(val rgb: Int) : Parseable<Color> {
static {
override fun parse(s: String): Color { /* impl */ }
}
}
fun main() {
val parser: Parseable<Color> = Color.static
val color = parser.parse("red")
println(color is Parseable<Color>) // false
}
static objects
static object Namespace {
val property = 42 // static property
fun doSomething() { // static function
property // OK: Can refer to static property in the same scope
this // ERROR: Static objects have no instance
}
}
fun main() {
Namespace.doSomething() // OK
val x = Namespace // ERROR: Cannot reference static object as value
val y: Namespace? = null // ERROR: Cannot reference static object as type
}
static extensions
예를 들어, CsvFile 이라는 클래스에 대하여 open(fileName: String)
정적 확장 함수를 만들고자 한다면, CsvFile클래스에 Companion object가 선언된 경우에만 fun CsvFile.Companion.open(fileName: String)
으로 확장 함수를 선언할 수 있습니다.
2.X 버전 부터는 static도 확장이 가능해져, fun CsvFile.static.open(fileName: String)
로 Companion object가 없더라도 확장 함수 선언이 가능해집니다.
최종 사용 구문은 section vs modifier 두 가지 중에서 현재 논의 중입니다.
각각 장단점을 가지고 있는데, 현재 Section이 더 많은 찬성을 받고 있습니다.
- Section
- 장점 : Companion object에서 마이그레이션 하기에 용이합니다.
- 단점 : 다른 언어들에서 주로 사용되는 구문과 달라 Kotlin 첫 사용자에게 학습장벽이 될 수 있습니다.
- Modifier
- 장점 : 다른 언어들에서 주로 사용되는 구문과 유사하여 Kotlin 첫 사용자에게 학습장벽이 낮습니다.
- 단점 : 모든 코드 줄을 바꿔야 하기 때문에 Companion object에서 마이그레이션 하기에 어려움이 있습니다.
class C {
// Static section syntax
static {
val property = println("initialized")
}
// Static modifier syntax
static val property = println("initialized")
}
Collection literals
[1, 2, 3]
과 같은 방식으로 Collection을 선언할 수 있게 됩니다. 다른 주요 언어 에서는 이미 가능한 방식이죠. 찾아보니 5년 전에 제안된 개선안 인데 좀 늦게 적용되는 경향이 있습니다.
val arr: Array<String> = ["a", "b", "c"]
val set: Set<Boolean> = [true, false, true]
val list: List<Int> = [1, 2, 3]
val map: Map<String, Int> = ["one": 1, "two": 2, "three": 3]
Name-based destructuring
아래의 경우 처럼, firstName과 lastName의 배치 순서가 바뀌어도 현재는 그대로 컴파일 되는데 이는 로직에 문제를 일으키게 됩니다.
이 문제를 해결한다고 하는데, 아직 추가 내용은 없습니다.
data class Person(
val firstName: String,
val lastName: String
)
fun main() {
val person = Person("Kotlin", "Mascot")
val (firstName, lastName) = person
val (lastName, firstName) = person
}
Context receivers
Context Receivers라는 기능이 추가됩니다.
특정 context을 통해서만 사용 가능한 함수나 속성을 선언할 수 있게 해줍니다. 이를 통해 코드의 모듈성과 구조화가 향상시킬 수 있을 것 같습니다.
사용 방법
context(...)
를 붙여주면 됩니다.
Context receivers를 사용할 때의 차이점을 같은 동작을 하는 코드를 통해 비교하면 아래와 같습니다.
fun doSomething(scope: CoroutineScope) {
scope.launch {
// CoroutineScope 내에서 작동하는 코드
}
}
val scope = CoroutineScope(Dispatchers.Default)
doSomething(scope)
context(CoroutineScope)
fun doSomething() {
launch {
// CoroutineScope 내에서 작동하는 코드
}
}
val scope = CoroutineScope(Dispatchers.Default)
scope.doSomething()
특징
- 코드 간결성: context를 사용하면, context를 명시적으로 전달할 필요가 없어 코드가 더 간결해집니다.
- 다중 context 지원: 하나의 함수가 여러 context에서 동작할 수 있으므로, 더 다양한 사용 사례를 지원할 수 있습니다.
Explicit fields
private val _applicationState = MutableStateFlow(State())
val applicationState: StateFlow<State>
get() = _applicationState
현재 변수를 클래스 내부에서 사용하기 위해서 private으로 선언한 후, 외부에서 읽기 전용으로 쓰도록 하기 위해 추가로 public 변수를 선언하는 것이 일반적입니다.
어쩔 수 없는 선언 방식이지만 코드 길이가 2배가 되는 문제가 있습니다.
차후에는 아래와 같이 간편하게 사용할 수 있게 됩니다. 코드가 간결해지고, 가독성이 향상될 것으로 기대됩니다.
val applicationState: StateFlow<State>
field = MutableStateFlow(State())