Kotlin 1.9 新機能解説（抄）

Kotlin 1.9 がリリースされました。

https://kotlinlang.org/docs/whatsnew19.html

本稿では Kotlin 1.9 で stable になった、言語仕様および Kotlin/JVM で使える標準ライブラリーの API について、紹介・説明します。




## `Enum` の `entries` プロパティ

`Enum` 派生クラスの関数 `values()` が非推奨になり、代わりにプロパティ `entries` が追加されました。

```Kotlin
enum class Sign {
    PLUS,
    MINUS,
}

// 非推奨になった方法
val values: Array<Sign> =
    Sign.values() // 警告が出る
println(values.asList()) // > [PLUS, MINUS]

// 推奨される方法
val entries: EnumEntries<Sign> =
    Sign.entries
println(entries) // > [PLUS, MINUS]
```

`values()` 関数と同様に、`entries` プロパティはコンパイラによって自動実装されます。

`entries` プロパティの返値型は [`EnumEntries<E>`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.enums/-enum-entries.html) であり、これは `List<E>` を継承するインターフェイスです。

`entris` プロパティの値に入っている要素は `values()` の返値に入っている要素と変わりません。

### 動機

`values()` 関数は配列を返します。

配列は可変です。
そのため、呼び出し元で変更されてしまってもいいように、`values()` 関数が呼び出されるたびにコピーを作ってそれを返します。
大量に呼び出されるとそれがパフォーマンスに影響します。

これを、不変の `List` を一度だけ生成し常に同じものを返すようにすることで、パフォーマンスを改善します。

また、多くの API はコレクションを使用します。
配列を受け取っても結局 `List` などのコレクションに変換する必要があります。
その実装の手間や処理コストも軽減します。

### 既存の `Enum` 派生クラスではどうなるのか

コンパイル済みであったり Java で実装された `Enum` 派生クラスに対しても、`entries` プロパティを呼び出すことができます。
これらはプロパティの呼び出し元にリストを遅延初期化するコードが埋め込まれるような仕組みで実現されます。

### `EnumEntries` インターフェイスを実装するクラス

`EnumEntries` 型は `sealed interface` です。
これを継承する型は private な `EnumEntriesList` クラスだけです。
このクラスでは、次の関数がオーバーライドされ、`Enum.ordinal` プロパティを使って最適化されています。

- `contains()`
- `indexOf()`
- `lastIndexOf()`

### 参考

https://github.com/Kotlin/KEEP/blob/master/proposals/enum-entries.md




## データオブジェクト

データクラスの `object` 版であるデータオブジェクトを定義できるようになりました。

```Kotlin
data object MyDataObject
```

データオブジェクトはデータクラスと同様に次の関数が自動的にオーバーライドされます。

- `toString()`
- `equals()`
- `hashCode()`

`toString()` はパッケージ名修飾なしの短いクラス名を返します。

```Kotlin
println(MyDataObject) // > MyDataObject

// 参考
data class MyDataClass(val value: String)
println(MyDataClass("myValue")) // > MyDataClass(value=myValue)
```

`equals()` 関数は自身と**同じクラス**のインスタンスとの比較でのみ `true` を返します。
通常、`object` はシングルトンなので同じクラスのインスタンスは自身以外には存在しませんが、
リフレクションなどにより作られてしまった場合に、それとの比較でも `true` を返します。

`hashCode()` 関数は当然 `equals()` 関数に合わせた挙動をします。

なおデータオブジェクトにはデータクラスにある次の関数はありません。
- `copy()` 
- `componentN()` 

### 動機

データクラスとの対称性のために導入されました。

特にシールドインターフェイス・シールドクラスで使用すると効果的です。

```Kotlin:公式の例
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
}
```

### 参考

https://github.com/Kotlin/KEEP/blob/data-objects/proposals/data-objects.md




## インラインクラスのセカンダリーコンストラクターのボディ

インラインクラスのセカンダリーコンストラクターにボディを持たせられるようになりました。

```Kotlin
@JvmInline
value class ARGB(
    private val value: UInt
) {
    constructor(
        alpha: Int,
        red: Int,
        green: Int,
        blue: Int,
    ) : this(
        (alpha.toUInt() shl 8 * 3)
                + (red.toUInt() shl 8 * 2)
                + (green.toUInt() shl 8 * 1)
                + (blue.toUInt() shl 8 * 0)
    ) {
        // ここが書けるようになった。

        require(alpha in 0..0xFF)
        require(red in 0..0xFF)
        require(green in 0..0xFF)
        require(blue in 0..0xFF)
    }
}
```




## `..<` 演算子

2項演算子 `..<` とそれに関連する API が追加されました。

```Kotlin
for (i in 0..<3) {
    println(i)
} // 0 1 2
```

### `..` 演算子との比較

おなじみの演算子 `..` は閉区間を表します。
閉区間はその両端を区間内に含みます。
たとえば `0.0..3.0` は 0.0 から 3.0 までの区間であり、0.0 も 3.0 も区間内に含みます。
数学では `[0.0, 3.0]` のように書いたりします。

新しい演算子 `..<` は右半開区間を表します。
右半開区間は左端は区間内に含みますが、右端は含みません。
たとえば `0.0..<3.0` は 0.0 から 3.0 までの区間であり、0.0 は区間内に含みますが、3.0 は含みません。
数学では `[0.0, 3.0)` のように書いたりします。

`..` 演算子は [`ClosedRange`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.ranges/-closed-range/) 型オブジェクトを返し、
`..<` 演算子は新しく追加された [`OpenEndRange`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.ranges/-open-end-range/) 型オブジェクトを返します。

`..` 演算子の関数表記は [`rangeTo`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.ranges/range-to.html)、
`..<` 演算子の関数表記は [`rangeUntil`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.ranges/range-until.html) です。
（関数表記は、演算子オーバーロードや、式の結合順を変えるためにあえて2項演算子ではなく関数として書くときに使います。）

| 演算子                     | `..`          | `..<`          |
| -------------------------- | ------------- | -------------- |
| 区間                       | 閉区間        | 右半開区間     |
| 区間の左端を範囲内に含むか | 含む          | 含む           |
| 区間の右端を範囲内に含むか | 含む          | 含まない       |
| 数学での表記               | \[a, b]       | \[a, b)        |
| 関数表記                   | `rangeTo`     | `rangeUntil`   |
| 返値型                     | `ClosedRange` | `OpenEndRange` |

### 動機

整数のように「1つ前」の値が明確な型の場合、半開区間を閉区間で代替することが可能です。
たとえば `[0, 3)` は `[0, 2]` で代替できます。

しかし実数の場合はどうでしょうか。
`[0.0, 3.0)` は `[0.0, 2.0]` では代替できません。（前者には 2.9 が含まれますが、後者には含まれません。）

このように開区間は閉区間では代替しきれません。
特にプログラミングでは右半開区間を使用することがよくあります。
そこで右半開区間を表す型 `OpenEndRange`（と、それを便利に扱うために `..<` 演算子）が必要になります。

### 中置関数 `until`

中置関数 [`until`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.ranges/until.html) は皆さんおなじみでしょう。

`until` 関数は `..<` 演算子と同じく右半開区間を表します。

```Kotlin
for (i in 0 until 3) {
    println(i)
} // 0 1 2

for (i in 0..<3) {
    println(i)
} // 0 1 2
```

しかしこれまでは `OpenEndRange` インターフェイスがなかったため、`ClosedRange` インターフェイスを実装した型（`IntRange` クラスなど）を返して、閉区間で代替していました。（`unitl` 関数は整数型に対してのみオーバーロードされています。）
これからは返値型が `OpenEndRange` インターフェイスも実装しましたので、正しく右半開区間を表すものになりました。

なお整数型の `..<` 演算子は `until` 関数と同じ型のオブジェクトを返すようにオーバーロードされています。

`until` は直観的ではないので、間違いを防ぐため、今後は `..<` 演算子を使うようにしていくのがよいでしょう。


### 参考

https://qiita.com/_ken_/items/25cede552e3e325b9ef1




## 時間計測 API

時間計測 API が追加されました。

### コード実行時間計測

コードブロックの実行に要した時間を計測します。

[`measureTimedValue`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.time/measure-timed-value.html) 関数はコードブロックが返した値と所要時間を [`TimedValue`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.time/-timed-value/) 型オブジェクトとして返します。

```Kotlin
val timedValue = measureTimedValue {
    // 所要時間を計測したい処理
    buildList<Int> {
        (2..<100).forEach { i ->
            if (this.none { i % it == 0 }) {
                this += i
            }
        }
    }
}
println("duration: ${timedValue.duration}, value: ${timedValue.value}")
// ^ > duration: 24.032068ms, value: [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97]
```

`TimedValue` はデータクラスなので、結果を分解して受け取ることもできます。

```Kotlin
val (value, duration) = measureTimedValue {
    // ...
}
```

コードブロックが返す値がない場合は [`measureTime`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.time/measure-time.html) 関数を使います。

```Kotlin
val duration = measureTime {
    // ...
}
println("duration: $duration")
// ^ > duration: 67.798286ms
```

### 時間をマークして差を計測する

任意の瞬間の時間をマークしておき、後でそれらの間の差を求めることができます。

```Kotlin
val timeSource = TimeSource.Monotonic

val mark1 = timeSource.markNow()

Thread.sleep(100)

val mark2 = timeSource.markNow()

Thread.sleep(200)

val mark3 = timeSource.markNow()

println("mark2 - mark1: ${mark2 - mark1}") // > mark2 - mark1: 102.062205ms
println("mark3 - mark2: ${mark3 - mark2}") // > mark3 - mark2: 205.087097ms
println("mark3 - mark1: ${mark3 - mark1}") // > mark3 - mark1: 307.149302ms
```

[`TimeSource.markNow()`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.time/-time-source/mark-now.html) 関数は [`TimeMark`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.time/-time-mark/) オブジェクトを返します。
`TimeMark` オブジェクト同士の差を取ると、期間を表す [`Duration`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.time/-time-mark/elapsed-now.html) オブジェクトが返ります。

`TimeMark` オブジェクトに `Duration` オブジェクトを加えることで、新しい `TimeMark` オブジェクトを作ることができます。
[`TimeMark.hasPassedNow`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.time/-time-mark/has-passed-now.html) 関数を使えば、現在時刻がその `TimeMark` オブジェクトが表す時刻を過ぎているかどうかを判定できます。

```Kotlin
val timeSource = TimeSource.Monotonic
// 現在
val markStart: TimeMark = timeSource.markNow()
// 1秒間
val duration1Sec: Duration = 1.seconds
// 1秒後
val mark1SecAfter: TimeMark = markStart + duration1Sec
for (i in 0..<100) {
    Thread.sleep(100)

    // 「1秒後」の時間を過ぎていたら現在の値を出力して終了する。
    if (mark1SecAfter.hasPassedNow()) {
        println(i) // > 9
        break
    }
}
```

### 動機

以前からコードブロックの所要時間を計測する関数として [`measureTimeMillis`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.system/measure-time-millis.html) 関数と [`measureNanoTime`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.system/measure-nano-time.html) 関数がありました。
これらは名前が似ており、計測の精度だけが違うように見えますが、実際には仕組みが全く異なります。

`measureTimeMillis` 関数はコードブロックの開始時と終了時のエポックタイム（1970-01-01T00:00:00 からの時間）を記録し、その差を結果とします。
そのため計測中にシステムの日時設定が変更されると正しい経過時間を返しません。

`measureNanoTime` 関数はシステムが起動してからの経過時間に基づくため、正確な時間を計測できます。

この違いが混乱を招くため、またこれらはコードブロックが返した値を呼び出し元に返せないなどの不便があったため、これらを置き換える新しい API が作られました。

新しい API では `measureNanoTime` と同様、システムが起動してからの経過時間に基づいた、システムの日時設定に依存しない計測を行います。

### 参考

https://www.baeldung.com/kotlin/measure-elapsed-time




## 親ディレクトリーを生成するためのパスユーティリティ

`java.nio.file.Path` クラスに、そのパスの親ディレクトリーまでを生成する拡張関数 [`createParentDirectories`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.io.path/java.nio.file.-path/create-parent-directories.html) が追加されました。

```Kotlin
// 既存の移動させたいファイルのパス
val srcPath = Path(".") / "text.txt"
// 移動先のパス
val dstPath = Path(".") / "grand_parent" / "parent" / "child.txt"
// 移動先のパスの親ディレクトリーまでを作って、そのディレクトリーにファイルを移動させる。
srcPath.moveTo(
    dstPath.createParentDirectories()
)
```

既存の関数 [`createDirectories`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.io.path/java.nio.file.-path/create-directories.html) とよく似ていますが、
そのパス自身ではなく親ディレクトリーまでを生成します。
返値は、生成対象である親ディレクトリーではなく、レシーバーであるパス自身です。


／以上