6
2

More than 1 year has passed since last update.

Kotlin 1.7.20の変更点

Last updated at Posted at 2022-10-09

※ソース記事はこちら
※Kotlin/JS、Kotlin/Nativeについては割愛します。

Kotlin 1.7.20用のIDEサポートがIntelliJ IDEA 2021.3,2022.1,2022.2で利用可能

リリース日:2022/9/29
Kotlin 1.7.20がリリースされている。こちらに、このリリースからいくつかのハイライトがある。

  • 新しいKotlin K2 コンパイラがall-open、SAM with receiver、Lombok等のコンパイラプラグインをサポート
  • 上限を含まない範囲をつくるための..<演算子のプレビューの導入
  • 新しいKotlin/Nativeのメモリマネージャーが現在デフォルトで有効
  • JVM用の新しい実験的機能の導入:ジェネリックの基底型を持つインラインクラス
    このビデオで、変更の短い概要を見つけることができる。

Kotlin K2コンパイラプラグインのサポート

KotlinチームはK2コンパイラの安定化を続けている。K2はまだ、Alpha状態(Kotlin 1.7.0リリースでアナウンスされているように)であるが、今ではいくつかのコンパイラプラグインをサポートしている。このYouTrack問題をフォローすることで、新しいコンパイラについてのKotlinチームからの更新を得ることができる。
この1.7.20のリリースから、Kotlin K2コンパイラは次のプラグインをサポートしている。

AlphaバージョンのKotlin K2コンパイラはJVMプロジェクトとのみ連携する。Kotlin/JS、Kotlin/Nativeあるいはマルチプラットフォームプロジェクトはサポートしていない。

次のビデオで、新しいコンパイラとそのメリットについての知識を深める。

Kotlin K2コンパイラを有効にする方法

Kotlin K2コンパイラを有効にしてテストするために、次のコンパイラオプションを使うこと。

-Xuse-k2

自分のJVMプロジェクトで性能向上を確認し、古いコンパイラの結果と比較することができる。

新しいK2コンパイラについてフィードバックを残す

我々はどんな形でもフィードバックに本当に感謝している。

言語

Kotlin 1.7.20では、ビルダー推論における制限と、新しい言語機能のプレビューが導入されている。

  • 上限を含まない範囲をつくるための..<演算子のプレビュー
  • 新しいdataオブジェクトの宣言
  • builder型推論の制限

上限を含まない範囲をつくるための..<演算子のプレビュー

新しい演算子は実験的であり、IDEサポートが制限されている。

今回のリリースでは、新しい..<演算子が導入されている。Kotlinには値の範囲を表現するために..演算子がある。新しい..<演算子はuntil関数のようにふるまい、上限を含まない範囲(※未満)を定義するのに役に立つ。

我々の調査によれば、この新しい演算子が上限を含まない範囲を表現し、上限が含まれないことを明確にするのにより良い仕事をする。
こちらにwhen式で..<演算子を使う例がある。

when (value) {
    in 0.0..<0.25 -> // first quarter ※0.25は含まない
    in 0.25..<0.5 -> // second quarter ※0.5は含まない
    in 0.5..<0.75 -> // third quarter ※0.75は含まない
    in 0.75..1.0 ->  // last quarter  <- これは上限を含む範囲
}

標準ライブラリのAPI変更

次の新しい型と演算子が共通のKotlin標準ライブラリにあるkotlin.rangesパッケージに導入されるだろう。

新しいOpenEndRangeインターフェイス

上限を含まない範囲を表す新しいインターフェイスは、既存のClosedRange<T>インターフェイスと非常に似ている。

interface OpenEndRange<T : Comparable<T>> {
    // 下限
    val start: T
    // 上限、範囲には含まれない
    val endExclusive: T
    operator fun contains(value: T): Boolean = value >= start && value < endExclusive
    fun isEmpty(): Boolean = start >= endExclusive
}

既存の反復可能範囲へのOpenEndRangeの実装

開発者が、上限を含まない範囲が必要なとき、現在は同一の値を使って上限を含む反復可能範囲を効率的に作るため、until関数を使っている。これらの範囲をOpenEndRange<T>を取る新しいAPIで容認するため、既存の反復可能な範囲(IntRange,LongRange,CharRange,UIntRange,ULongRange)でインターフェイスを実装したい。そのため、それらはClosedRange<T>OpenEndRange<T>インターフェイス両方を同時に実装するだろう。

class IntRange : IntProgression(...), ClosedRange<Int>, OpenEndRange<Int> {
    override val start: Int
    override val endInclusive: Int
    override val endExclusive: Int
}

標準型用のrangeUntil演算子

現在rangeTo演算子によって定義されているのと同じ型と組み合わせに対して、rangeUntil演算子が提供されるだろう。我々はプロトタイプのため、それらを拡張関数として提供しているが、上限を含まない範囲のAPIを安定化する前に、それらをメンバにする計画がある。

..<演算子の有効化方法

..<演算子を使うか、自分自身の型のためにその演算子規約を実装するには、-language-version 1.8コンパイラオプションを有効にする。
標準型の上限を含まない範囲をサポートするために導入された新しいAPI要素は、実験的な標準API用としていつものように、次のようなオプトインが必要である。
@OptIn(ExperimentalStdlibApi::class)あるいは、コンパイラオプションで、-opt-in=kotlin.ExperimentalStdlibApiを使うこともできる。
このKEEP文書で新しい演算子について続きを読む

data objectによるシングルトンとsealedクラス階層のための改善された文字列表現

data objectは実験的であり、IDEサポートが制限されている。

このリリースでは、data objectという新しいobject宣言の型が導入されている。data objectは概念的には通常のobjectと同一にふるまうが、すぐに使えるきれいなtoString表現を持っている。

package org.example
object MyObject
data object MyDataObject

fun main() {
    println(MyObject) // org.example.MyObject@1f32e575
    println(MyDataObject) // MyDataObject
}

このことにより、dataオブジェクト宣言がsealdクラス階層にとって完全になり、そこでそれらをdata classとともに使うことができる。このスニペットでは、EndOfFileを単なるobjectの代わりにdata objectとすることで、手動でオーバーライドする必要なく、整形されたtoStringを得られることを意味しており、付随するdata class宣言と調和を維持している。

sealed class ReadResult {
    data class Number(val value: Int) : ReadResult()
    data class Text(val value: String) : ReadResult()
    data object EndOfFile : ReadResult()
}

fun main() {
    println(ReadResult.Number(1)) // Number(value=1)
    println(ReadResult.Text("Foo")) // Text(value=Foo)
    println(ReadResult.EndOfFile) // EndOfFile
}

data objectの有効化方法

自分のコードでdata object宣言を使うには、-language-version 1.8コンパイラオプションを有効にする。Gradleプロジェクトでは、build.gradle(.kts)に次のように追加することで可能である。

tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
    // . . .
    kotlinOptions.languageVersion = "1.8"
}

個別のKEEP文書で、data objectについて続きを読み、その実装に関してフィードバックを共有する。

新しいbuilder推論の制限

Kotlin 1.7.20では、自分のコードに影響を与えうるbuilder推論の使用に関して、いくつかの主要な制限を設けている。これらの制限は、builderラムダ関数を含むコードに当てはまり、それはラムダ自身を解析しないとパラメータを導出することができないためである。そのパラメータは引数として使われている。現在では、コンパイラはそのようなコードに対して常にエラーを表示し、明示的に型を指定するように依頼しているだろう。
これは破壊的な変更であるが、我々の調査によれば、これらのケースは非常にまれであり、制限はあなたのコードに影響しないはずである。もしその場合は、次のケースを考慮すること。

  • 拡張を持つbuilder推論がメンバを隠している
    もしあなたのコードが、同一の名前で拡張関数を含んでおり、builder推論の間使われていると、コンパイラはエラーを表示するだろう。
class Data {
    fun doSmth() {} // 1
}

fun <T> T.doSmth() {} // 2

fun test() {
    buildList {
        this.add(Data())
        this.get(0).doSmth() // 2に解決し、エラーとなる
    }
}

コードを修正するには、型を明示的に指定しなければならない。

class Data {
    fun doSmth() {} // 1
}

fun <T> T.doSmth() {} // 2

fun test() {
    buildList<Data> { // 型引数
        this.add(Data())
        this.get(0).doSmth() // 1に解決する
    }
}
  • 複数のラムダを持つbuilder推論で、型引数が明示的に指定されていない
    もしbuilder推論で2つ以上のラムダがある場合、それらは型に影響を及ぼす。エラーを防ぐため、コンパイラは型を指定することを必要とする。
fun <T: Any> buildList(
    first: MutableList<T>.() -> Unit,
    second: MutableList<T>.() -> Unit
): List<T> {
    val list = mutableListOf<T>()
    list.first()
    list.second()
    return list
}

fun main() {
    buildList(
        first = { // this: MutableList<String>
            add("")
        },
        second = { // this: MutableList<Int>
            val i: Int = get(0)
            println(i)
        }
    )
}

エラーを修正するには、型を明示的に指定し、型のミスマッチを修正しなければならない。

fun main() {
    buildList<Int>(
        first = { // this: MutableList<Int>
            add(0)
        },
        second = { // this: MutableList<Int>
            val i: Int = get(0)
            println(i)
        }
    )
}

もし、上であげたケースが見つからない場合、われわれのチームに問題を提出してほしい。
builder推論の更新についてのさらなる情報のため、このYouTrack問題を参照のこと。

Kotlin/JVM

Kotlin 1.7.20では、ジェネリックインラインクラスが導入され、delegatedプロパティのためのバイトコードの最適化が追加され、kaptのスタブ生成タスクでIRがサポートされ、kaptですべての最新のKotlinの機能を使うことができるようになっている。

ジェネリックインラインクラス

ジェネリックインラインクラスは、実験的機能である。いつでも削除されたり、変更されるかもしれない。オプトインが必要(詳細は下記)であり、評価目的のためにのみ使うべきである。YouTrackでのフィードバックに感謝する。

Kotlin 1.7.20では、JVMインラインクラスの基底型が型パラメータであることが許可されている。コンパイラはそれをAny?あるいは一般的には型パラメータの上方境界にマップする。

次の例を考えてみよう。

@JvmInline
value class UserId<T>(val value: T)

fun compute(s: UserId<String>) {} // コンパイラはfun compute-<ハッシュコード>(s: Any?)を生成する

この関数はインラインクラスをパラメータとして受け取る。パラメータは型引数ではなく、上方境界にマップされる。
この機能を有効にするには、-language-version 1.8コンパイラオプションを使用する。
YouTrackにて、この機能についてのフィードバックに感謝する。

delegatedプロパティのより多くの最適化ケース

Kotlin 1.6.0で、$delegateフィールドを省略し、参照されるプロパティへの直接のアクセスを生成することで、プロパティに対するdelegateのケースが最適化された。1.7.20では、より多くのケースで最適化を実装した。現在は以下の場合に$delegateが省略されるだろう。

  • 名前つきオブジェクト
object NamedObject {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String = ...
}

val s: String by NamedObject
val impl: ReadOnlyProperty<Any?, String> = ...

class A {
    val s: String by impl
}
  • 定数表現、enum定数、thisnullのいずれか。ここではthisの例を挙げる。
class A {
    operator fun getValue(thisRef: Any?, property: KProperty<*>) ...

    val s by this
}

delegatedプロパティについて、知識を深める。
この機能について、YouTrackでのフィードバックに感謝する。

kaptのスタブ生成タスクでのJVM IRバックエンドのサポート

kaptのスタブ生成タスクでのJVM IRバックエンドのサポートは、実験的機能である。いつでも削除されたり、変更されるかもしれない。オプトインが必要(詳細は下記)であり、評価目的のためにのみ使うべきである。

1.7.20より前では、kaptのスタブ生成タスクは古いバックエンドを使用しており、繰り返し可能なアノテーションkaptとは連携しなかった。Kotlin 1.7.20で、kaptのスタブ生成タスクでJVM IRバックエンドのサポートを追加した。これにより、kaptで繰り返し可能なアノテーションを含むすべての最新のKotlinの機能を使用できるようになる。
kaptでIRバックエンドを使用するには、gradle.propertiesファイルで次のオプションを追加する。

kapt.use.jvm.ir=true

YouTrackで、この機能についてのフィードバックに感謝する。

Gradle

Kotlin Gradleプラグインの更新は、新しいGradleの機能と最新のGradleバージョンとの互換性にフォーカスされている。
Kotlin 1.7.20には、Gradle 7.1のサポートが含まれている。非推奨メソッドとプロパティは削除あるいは置換され、Kotlin Gradleプログインによって生成される多くの非推奨警告が削減され、Gradle 8.0のための未来のサポートのブロックを解除している。
しかし、いくつか潜在的に互換性を破る変更があり、注意が必要かもしれない。

Target設定

  • org.jetbrains.kotlin.gradle.dsl.SingleTargetExtensionは現在SingleTargetExtension<T : KotlinTarget>ジェネリックパラメータを持つ。
  • kotlin.targets.fromPreset()規約は非推奨になった。代わりにkotlin.targets { fromPreset() }がまだ使ええるが、よりターゲットを作ることに特化した方法を使うことを推奨している。
  • Gradleによって自動生成されたTargetアクセサは、kotlin.targets { }ブロック内では使用できなくなっている。代わりにfindByName("targetName")メソッドを使っていただきたい。
    注意すべきは、そのようなアクセサは例えばkotlin.targets.linuxといった、kotlin.targetsのケース内ではまだ利用可能である。

ソースディレクトリ設定

Kotlin Gradleプラグインは現在、JavaのSourceSetグループに対するkotlin拡張としてKotlinのSourceDirectorySetが追加されている。これにより、JavaやGroovyやScalaで設定する方法と同様に、build.gradle.kts内でソースディレクトリを設定することができる。

sourceSets {
    main {
        kotlin {
            java.setSrcDirs(listOf("src/java"))
            kotlin.setSrcDirs(listOf("src/kotlin"))
        }
    }
}

もはや非推奨のGradle規約を使い、Kotlinのソースディレクトリを指定する必要はない。
KotlinSourceSetにアクセスするためにkotlin拡張を使うこともできることを覚えておいてほしい。

kotlin {
    sourceSets {
        main {
        // …
        }
    }
}

JVMツールチェーン設定のための新しいメソッド

このリリースでは、JVMツールチェーン機能を有効にするため、新しいjvmToolChain()メソッドが提供されている。implementationvendorといった、追加の設定フィールドが不要な場合、Kotlin拡張からこのメソッドを使うことができる。

kotlin {
    jvmToolchain(17)
}

これは追加の設定なしで、Kotlinのプロジェクトのセットアップを簡単にする。このリリースの前は、次の方法でJDKバージョンのみを指定することができた。

kotlin {
    jvmToolchain {
        languageVersion.set(JavaLanguageVersion.of(17))
    }
}

標準ライブラリ

Kotlin 1.7.20では、java.nio.file.Pathクラスのための新しい拡張関数が提供されており、それにより、ファイルツリーを歩いて通ることができる。

  • walk()は、指定したパスをルートとするファイルツリーを遅延して横断する。
  • fileVisitor()は、別々にFileVisitorを作ることができる。FileVisitorはファイルやディレクトリを横断するときのアクションを定義する。
  • visitFileTree(fileVisitor: FileVisitor, ...)は、準備したFileVisitorを消費し、内部でjava.nio.file.Files.walkFileTree()を使う。
  • visitFileTree(..., builderAction: FileVisitorBuilder.() -> Unit)は、VisitorActionを持つFileVisitorを作り、visitFileTree(fileVisitor, ...)関数を呼び出す。
  • FileVisitorResultFileVisitor型の返却し、ファイルの処理を継続する、CONTINUEデフォルト値を持つ。

java.nio.file.Pathクラスのための新しい拡張関数は、実験的機能である。いつでも削除されたり、変更されるかもしれない。オプトインが必要(詳細は下記)であり、評価目的のためにのみ使うべきである。

こちらにこれらの新しい関数で処理することができるものがいくつかある。

  • 明示的にFileVisitorを作り、その後使う
val cleanVisitor = fileVisitor {
    onPreVisitDirectory { directory, attributes ->
        // ここでディレクトリ訪問時のロジック
        FileVisitResult.CONTINUE
    }

    onVisitFile { file, attributes ->
        // ここでファイル訪問時のロジック
        FileVisitResult.CONTINUE
    }
}

// ここにロジックが可能

projectDirectory.visitFileTree(cleanVisitor)
  • builderActionを持つFileVisitorを作り、すぐに使う
projectDirectory.visitFileTree {
// builderActionの定義
    onPreVisitDirectory { directory, attributes ->
        //ここでディレクトリ訪問時のロジック
        FileVisitResult.CONTINUE
    }

    onVisitFile { file, attributes ->
        // ここでファイル訪問時のロジック
        FileVisitResult.CONTINUE
    }
}
  • walk()関数で指定したパスをルートとするファイルツリーを横断
@OptIn(kotlin.io.path.ExperimentalPathApi::class)
fun taverseFileTree() {
    val cleanVisitor = fileVisitor {
        onPreVisitDirectory { directory, _ ->
            if (directory.name == "build") {
                directory.toFile().deleteRecursively()
                FileVisitResult.SKIP_SUBTREE
            } else {
                FileVisitResult.CONTINUE
            }
        }

        onVisitFile { file, _ ->
            if (file.extension == "class") {
                file.deleteExisting()
            }
            FileVisitResult.CONTINUE
        }
    }

    val rootDirectory = createTempDirectory("Project")

    rootDirectory.resolve("src").let { srcDirectory ->
        srcDirectory.createDirectory()
        srcDirectory.resolve("A.kt").createFile()
        srcDirectory.resolve("A.class").createFile()
    }

    rootDirectory.resolve("build").let { buildDirectory ->
        buildDirectory.createDirectory()
        buildDirectory.resolve("Project.jar").createFile()
    }


// walk関数を使う
    val directoryStructure = rootDirectory.walk(PathWalkOption.INCLUDE_DIRECTORIES)
        .map { it.relativeTo(rootDirectory).toString() }
        .toList().sorted()
    assertPrints(directoryStructure, "[, build, build/Project.jar, src, src/A.class, src/A.kt]")

    rootDirectory.visitFileTree(cleanVisitor)

    val directoryStructureAfterClean = rootDirectory.walk(PathWalkOption.INCLUDE_DIRECTORIES)
        .map { it.relativeTo(rootDirectory).toString() }
        .toList().sorted()
    assertPrints(directoryStructureAfterClean, "[, src, src/A.kt]")
}

実験的APIの通常通り、新しい拡張関数には次のオプトインが必要である。
@OptIn(kotlin.io.path.ExperimentalPathApi::class)または@kotlin.io.path.ExperimentalPathApi
あるいは、-opt-in=kotlin.io.path.ExperimentalPathApiコンパイラオプションが使用できる。
YouTrackで、walk()関数と、visit拡張関数についてのフィードバックに感謝する。

ドキュメント更新

以前のリリースから、Kotlinドキュメントにはいくつかの注目すべき変更がある。

改定と改善されたページ

  • 基本型の概要 - Kotlinで使われる基本的な型を学習する。数値、ブーリアン、文字、文字列、配列、符号なし整数
  • Kotlin開発のためのIDE - Kotlinが公式にサポートするIDEの一覧と、コミュニティがサポートするツールを参照

Kotlinマルチプラットフォームジャーナルの新しい記事

新規、更新チュートリアル

リリースドキュメントの変更

我々はもはやそれぞれのリリースのために推奨するkotlinxのライブラリの一覧を提供していない。このリストには、Kotlin自体で推奨され、テストされたバージョンのみが含まれていた。それは、いくつかのライブラリが、お互いに依存し、推奨されるKotlinバージョンと異なる特定のkotlinxバージョンを必要とすることが考慮されていなかった。
我々はライブラリがどのように相互に関連し、お互いに依存するかについての情報提供の方法を見つけることに取り組んでいる。それにより、プロジェクトでKotlinバージョンをアップグレードするときに使うべきkotlinxライブラリのバージョンが明らかになるだろう。

Kotlin 1.7.20のインストール

IntelliJ IDEA 2021.3と2022.1と2022.2は自動的にKotlinプラグインを1.7.20への更新を提案する。

Android Studio Dolphin (213), Electric Eel (221), and Flamingo (222)用にKotlinプラグイン1.7.20が今後のAndroid Studioの更新とともに配布されるだろう。

新しいコマンドラインコンパイラーが、GitHubリリースページで利用可能である。

Kotlin 1.7.20互換性ガイド

Kotlin 1.7.20は差分リリースであるが、Kotlin 1.7.0で導入された問題の拡大を制限するために行った互換性の無い変更がすでにある。
そのような変更の詳細な一覧がKotlin 1.7.20互換性ガイドで見つけることができる。

6
2
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
2