12
10

More than 1 year has passed since last update.

Kotlin 1.8.0の変更点

Posted at

※ソース記事はこちら
※Kotlin/JS、Kotlin/Nativeについては割愛します。
リリース日:2022/12/28
Kotlin 1.8.0リリースが世に出され、ここにいくつかの最大のハイライトがある。

  • JVMのための新しいの実験的機能:ディレクトリ内容の再帰的なコピーと削除
  • kotlin-reflect性能の改善
  • より良いデバッグ経験のための新しい-Xdebugコンパイラオプション
  • kotlin-stdlib-jdk7kotlin-stdlib-jdk8kotlin-stdlibへのマージ
  • Objective-C/Swiftとの相互運用性の向上
  • Gradle 7.3との互換性

IDEのサポート

1.8.0をサポートするKotlinプラグインはこちらで利用可能である。

IDE サポートバージョン
IntelliJ IDEA 2021.3,2022.1,2022.2
Android Studio Electric Eel (221), Flamingo (222)

IntelliJ IDEA 2022.3では、IDEプラグインの更新なしで、プロジェクトをKotlin 1.8.0の更新することができる。
IntelliJ IDEA 2022.3で既存のプロジェクトをKotlin 1.8.0に移行するには、Kotlin versionを1.8.0に変更して、GradleまたはMavenプロジェクトを再インポートする。

Kotlin/JVM

Kotlin 1.8.0から、コンパイラはJVM19に対応したバイトコードバージョンでクラスを生成することができる。新しい言語バージョンには以下も含まれる。

  • JVMアノテーションターゲットの生成をオフに切り替えるコンパイラ引数
  • 最適化を無効にするための新しい-Xdebugコンパイラオプション
  • 古いバックエンドの削除
  • Lombokの@Builderアノテーションのサポート

TYPE_USEとTYPE_PARAMETERアノテーションターゲットを生成しない機能

KotlinアノテーションがKotlinターゲットの間でTYPEを持つ場合、アノテーションはJavaのアノテーションターゲットのリスト内で、java.lang.annotation.ElementType.TYPE_USEをマッピングする。これはTYPE_PARAMETERKotlinアノテーションターゲットがjava.lang.annotation.ElementType.TYPE_PARAMETERターゲットにマッピングすることに似ている。これはAPIレベル26未満のAndroidクライアントにとって問題である。というのはAPIにこれらのターゲットを持っていないためである。
Kotlin 1.8.0から、TYPE_USETYPE_PARAMETERアノテーションターゲットを生成しないようするためには、新しいコンパイラ引数-Xno-new-java-annotation-targetsを使うことができる。

最適化を無効にする新しいコンパイラオプション

Kotlin 1.8.0では、新しい-Xdebugコンパイラオプションが追加され、それにより、より良いデバッグ経験のために最適化が無効になる。今のところ、そのオプションは、コルーチンのための「optimized outされた」機能を無効にする。将来はより多くの最適化を追加した後で、このオプションでそれらも無効にするだろう。
「optimized out」機能は、suspend関数を使うときに、変数を最適化する。しかし変数の値が見れないために、最適化された変数のあるコードをデバッグすることは難しい。

このフラグを本番では使ってはいけない。-Xdebug経由でこの機能を無効にすると、メモリリークが起こりうる。

古いバックエンドの削除

Kotlin 1.5.0で、IRベースのバックエンドがStableになったとアナウンスされた。これはKotiln 1.4.*からの古いバックエンドは非推奨になったという意味である。Kotlin 1.8.0で完全に古いバックエンドが削除された。延長線上で、-Xuse-old-backendコンパイラオプションとGradleのuseOldBackendオプションが削除された。

Lombokの@Builderアノテーションのサポート

コミュニティにより、Kotlin Lombok:生成されたビルダー(@Builder)のサポートのYouTrack課題に多くの投票がされたため、@Builderアノテーションをサポートせざるを得なかった。
@SuperBuilder@Tolerateアノテーションをサポートする計画はまだないが、もし十分に多くの人々が@SuperBuilder@Tolerateの課題のために、投票すれば再考するだろう。
Lombokコンパイラプラグインの設定方法を学ぶ

Gradle

Kotlin 1.8.0はGradle 7.2と7.3に完全に対応している。Gradleバージョンを最新のリリースにアップデートすることも可能だが、もしそうすると、非推奨警告やいくつかの新しいGradleの機能が動作しないかもしれないということに注意してほしい。
このバージョンでは多くの変更がもたらされている。

Gradle lazyプロパティとしてKotllinコンパイラオプションの公開

Gradle lazyプロパティとしてKotlinコンパイラオプションを公開し、Kotlinタスクによりよく統合するため、多くの変更が行われた。

  • compileタスクは新しいcompilerOptions入力を持ち、それは既存のkotlinOptionsと同じだが、Gradle Properties APIからのPropertyを戻り型として使う。
tasks.named("compileKotlin", org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile::class.java) {
    compilerOptions {
        useK2.set(true)
    }
}
  • KotlinツールタスクのKotlinJsDceKotlinNativeLinkは新しいtoolOptions入力を持ち、それは既存のkotlinOptions入力と似ている。
  • 新しい入力は@Nested Gradleアノテーションを持つ。入力内部のそれぞれのプロパティは、@Input@Internalのような関連するGradleアノテーションを持つ。
  • Kotlin GradleプラグインAPIの生成物は、二つの新しいインターフェイスを持つ。
    • org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask これはcompilerOptions入力とcompileOptions()メソッドを持つ。すべてのKotlinコンパイルタスクはこのインターフェイスを実装する。
    • org.jetbrains.kotlin.gradle.tasks.KotlinToolTask これはtoolOptions入力とtoolOptions()メソッドを持つ。すべてのKotlinツールタスク(KotlinJsDceKotlinNativeLinkKotlinNativeLinkArtifactTask)はこのインターフェイスを実装する。
  • いくつかのcompilerOptionsString型の代わりに新しい型を使う。
  • Kotlin GradleプラグインAPIは、以前のリリースとバイナリレベルで互換性がある。しかしいくつかのソースと、ABI(Application Binary Interface)を破る変更がkotlin-gradle-plugin成果物に存在する。これらの変更のほとんどは、いくつかの内部型に対する追加のジェネリックパラメータに関係している。一つの重要な変更は、KotlinNativeLinkタスクはもはやAbstractKotlinNativeCompileタスクを継承していないということである。
  • KotlinJsCompilerOptions.outputFileと関連するKotlinJsOptions.outputFileオプションは非推奨である。代わりにKotlin2JsCompile.outputFilePropertyタスクを使ってほしい。

Kotlin Gradleプラグインは、Android拡張に対してKotlinJvmOptionsDSLがまだ追加されている。

android {
    kotlinOptions {
        jvmTarget = "11"
    }
}

これはcompilerOptionsDSLがモジュールレベルで追加されるときに、この問題の範囲内において変更されるだろう。

制限

kotilnOptionsタスク入力と、kotlinOptions{....}タスクDSLは、サポートモードであり、今後のリリースで非推奨になるだろう。改善はcompilerOptionstoolOptionsに対してのみ、なされるだろう。

kotlinOptionsに対するどのようなセッター、ゲッター呼び出しも、compilerOptions内の関連するプロパティに移譲されている。これは次の制限をもたらしている。

  • compilerOptionskotlinOptionsは、タスクの実行フェーズでは変更できない。(上の段落での一つの例外を参照)
  • freeCompilerArgsは変更不可能なList<String>を返却し、それは例えばkotlinOptions.freeCompilerArgs.remove("something")が失敗することを意味する。
  • kotlin-DSLJetpack Composeを有効にしたAndroid Gradleプラグイン(AGP)を含むいくつかのプラグインは、タスク実行フェーズでfreeCompilerArgs属性を修正しようとしている。Kotlin 1.8.0ではそのための回避策が追加されている。この回避策により、タスク実行フェーズでfreeCompilerArgs属性を修正する、どのようなスクリプトもプラグインも許可されるが、ビルドログに警告が生成される。この警告を無効にするにはkotlin.options.suppressFreeCompilerArgsModificationWarning=trueGradleプロパティを使うこと。Gradleはkotlin-dslプラグインJetpack Composeを有効にしたAGPのために修正を追加する予定である。

最小サポートバージョンのバージョンアップ

Kotlin 1.8.0から、最小のサポートGradleバージョンが6.8.3であり、最小のサポートAndroid Gradleプラグインバージョンは4.1.3である。

Kotlinデーモンの代替戦略を無効にする機能

新しいkotlin.daemon.useFallbackStrategyGradleプロパティがあり、デフォルト値はtrueである。値をfalseすると、デーモンの起動や通信の問題でビルドが失敗する。Kotlinコンパイルタスクにも新しいuseDaemonFallbackStrategyプロパティがあり、両方使うときはこちらがGradleプロパティより優先する。コンパイルを実行するのに十分なメモリが無い場合、ログにそのことについてのメッセージを見ることができる。
Kotlinコンパイラの代替戦略は、デーモンがなぜかで失敗する場合、コンパイルをKotlinデーモンの外部で実行することである。Gradleデーモンが無効の場合、コンパイラは「プロセス外」の戦略を使う。これらの実行戦略についての文書でより詳細を確認すること。注意すべきは、別の戦略への無言の代替は、多くのシステムリソースを消費するか、非決定的なビルドにつながるということである。より詳細については、YouTrack問題を参照のこと。

推移的な依存性における最新のkotlin-stdlibバージョンの使用

dependencies内で、例えばimplementation("org.jetbrains.kotlin:kotlin-stdlib:1.8.0")のように明示的にKotlinバージョン1.8.0以上を書く場合、Kotlin Gradleプラグインは、推移的なkotlin-stdlib-jdk7kotlin-stdlib-jdk8の依存性のためのKotlinバージョンを使う。これは、異なるstdlibバージョンからクラスの重複を避けるためになされる。(kotlin-stdlib-jdk7kotlin-stdlib-jdk8kotlin-stdlibへのマージについてより詳細を確認のこと)kotlin.stdlib.jdk.variants.version.alignment Gradleプロパティを使って、このふるまいを無効にすることができる。

kotlin.stdlib.jdk.variants.version.alignment=false

バージョンを揃えるのに問題が発生したときは、ビルドスクリプト内で、kotlin-bomに関するプラットフォーム依存性を宣言することで、Kotlin BOM経由ですべてのバージョンを揃えること。

implementation(platform("org.jetbrains.kotlin:kotlin-bom:1.8.0"))

その他のケースと推奨の解決法については、ドキュメント内で確認すること。

関連するKotlinとJavaのコンパイルタスクに関連するJVMターゲットの必須チェック

このセクションは、たとえソースファイルがKotlinのみであり、Javaを使わない場合でも、JVMプロジェクトには当てはまる。

このリリースから、Gradle 8.0+(このGradleバージョンはまだリリースされていない)におけるプロジェクトのための、kotlin.jvm.target.validation.modeプロパティのデフォルト値は、errorであり、プラグインはJVMターゲットの非互換性のイベントで、ビルドが失敗するだろう。
warningからerrorにデフォルト値を移行するのは、Gradle 8.0へのスムースな移行のための準備ステップである。このプロパティをerrorにし、ツールチェーンを設定するか、JVMバージョンを手動で揃えることを推奨している。
より詳細には、ターゲットの互換性チェックをしないとどんな問題が起こるかを確認のこと。

Kotlin Gradleプラグインの推移的依存性の解決

Kotlin 1.7.0で、Gradleプラグインバリアントのサポートが導入された。このプラグインバリアントにより、ビルドクラスパスはいくつかの依存性、大抵はkotlin-gradle-plugin-apiの異なるバージョンに依存する、Kotlin Gradleプラグインの異なるバージョンを持つことができる。これは解決問題につながるため、kotlin-dslプラグインを例として使い、次のような回避方法を提案したい。
Gradle 7.6のkotlin-dslプラグインは、org.jetbrains.kotlin.plugin.sam.with.receiver:1.7.10プラグインに依存しており、それはkotlin-gradle-plugin-api:1.7.10に依存している。もしorg.jetbrains.kotlin.gradle.jvm:1.8.0プラグインを追加すると、このkotlin-gradle-plugin-api:1.7.10の推移的な依存性は、バージョン(1.8.01.7.10)間と、バリアント属性のorg.gradle.plugin.api-versionの値のミスマッチのために、依存性の解決エラーを引き起こすかもしれない。回避策として、バージョンを揃えるためにこのconstraintを追加する。この回避策は、計画にあるKotlin Gradle Plugin libraries alignment platformが実装されるまで、必要かもしれない。

dependencies {
    constraints {
        implementation("org.jetbrains.kotlin:kotlin-sam-with-receiver:1.8.0")
    }
}

このconstraintは、org.jetbrains.kotlin:kotlin-sam-with-receiver:1.8.0バージョンを推移的な依存性のためのビルドクラスパスとして使うように強制する。Gradle issue trackerで一つの似たケースについてより詳細を確認できる。

非推奨と削除

Kotlin 1.8.0では、次のプロパティとメソッドのための非推奨ライフサイクルが継続している。

標準ライブラリ

JVMコンパイルターゲットの更新

Kotlin 1.8.0では標準ライブラリ(kotlin-stdlib,Kotlin-reflect,kotlin-script-*)はJVMターゲット1.8でコンパイルされている。以前は標準ライブラリはJVMターゲット1.6でコンパイルされていた。
Kotlin 1.8.0ではJVMターゲット1.6と1.7はもはやサポートされていない。結果としてkotlin-stdlib-jdk7kotlin-stdlib-jdk8は、それらの成果物の内容がkotlin-stdlibにマージされたため、もはやビルドスクリプトでそれらを個別に宣言する必要がない。

ビルドスクリプトで依存性として、明示的にkotlin-stdlib-jdk7kotlin-stdlib-jdk8を宣言していた場合、kotlin-stdlibに置き換えるべきである。

注意すべきは、標準ライブラリ成果物の異なるバージョンが混在することで、classの重複や不足を引き起こすことである。それを避けるため、Kotlin Gradleプラグインによって、標準ライブラリのバージョンを揃えるのに役立つことができる。

cbrt()

cbrt()関数は、doublefloatの実立方根を計算することができ、現在Stableである。

import kotlin.math.*

fun main() {
    val num = 27
    val negNum = -num

    println("The cube root of ${num.toDouble()} is: " + 
            cbrt(num.toDouble())) // 3.0
    println("The cube root of ${negNum.toDouble()} is: " + 
            cbrt(negNum.toDouble())) // -3.0
}

JavaとKotlin間でのTimeUnit変換

kotlin.time内のtoTimeUnit()toDurationUnit()関数が現在Stableである。
Kotlin 1.6.0でExperimentalとして導入されたが、これらの関数はKotlinとJavaの相互運用性を改善する。今ではJavaのjava.util.concurrent.TimeUnitとKotlinのkotlin.time.DurationUnit間の変換を簡単にすることができる。これらの関数はJVMのみでサポートされている。

import kotlin.time.*

// For use from Java
fun wait(timeout: Long, unit: TimeUnit) {
    val duration: Duration = timeout.toDuration(unit.toDurationUnit())
    ...
}

比較可能、減算可能なTimeMark

TimeMarksの新しい機能はExperimentalであり、使うには@OptIn(Experimental::class)@ExperimentalTimeを使ってオプトインが必要である。

Kotlin 1.8.0より前では、複数のTimeMarknowの間で計算したい場合、同時に一つのTimeMarkにおいてelapsedNow()を呼び出すだけだった。これにより、二つのelapsedNow()関数呼び出しを厳密に同時に実行することができないため、結果を比較することが困難だった。
これを解決するため、Kotlin 1.8.0では同じタイムソースから、TimeMarkを減算、比較することができる。現在は、nowを表すTimeMarkインスタンスを生成し、それから他のTimeMarkを減算することができる。このように、これらの計算から収集された結果が、お互いに相対的があることが保証されている。

import kotlin.time.*
fun main() {
    val timeSource = TimeSource.Monotonic
    val mark1 = timeSource.markNow()
    Thread.sleep(500) // Sleep 0.5 seconds
    val mark2 = timeSource.markNow()

    // Before 1.8.0
    repeat(4) { n ->
        val elapsed1 = mark1.elapsedNow()
        val elapsed2 = mark2.elapsedNow()

        // elapsed1とelapsed2の差は、二つのelapsedNow()呼び出しの間の経過時間に応じて異なる。
        println("Measurement 1.${n + 1}: elapsed1=$elapsed1, " +
                "elapsed2=$elapsed2, diff=${elapsed1 - elapsed2}")
    }
    println()

    // Since 1.8.0
    repeat(4) { n ->
        val mark3 = timeSource.markNow()
        val elapsed1 = mark3 - mark1
        val elapsed2 = mark3 - mark2

        // 現在は、経過時間はmark3から相対的に計算され、固定値である。
        println("Measurement 2.${n + 1}: elapsed1=$elapsed1, " +
                "elapsed2=$elapsed2, diff=${elapsed1 - elapsed2}")
    }
    // TimeMarkをお互いに比較することも可能。
    // これはTrueである。mark2はmark1よりも後にキャプチャされたため。
    println(mark2 > mark1)
}
Measurement 1.1: elapsed1=500.718274ms, elapsed2=6.515158ms, diff=494.203116ms
Measurement 1.2: elapsed1=523.904389ms, elapsed2=23.334300ms, diff=500.570089ms
Measurement 1.3: elapsed1=523.975547ms, elapsed2=23.399769ms, diff=500.575778ms
Measurement 1.4: elapsed1=524.035870ms, elapsed2=23.459935ms, diff=500.575935ms

Measurement 2.1: elapsed1=525.623872ms, elapsed2=25.044030ms, diff=500.579842ms
Measurement 2.2: elapsed1=525.691691ms, elapsed2=25.111849ms, diff=500.579842ms
Measurement 2.3: elapsed1=525.809190ms, elapsed2=25.229348ms, diff=500.579842ms
Measurement 2.4: elapsed1=525.866802ms, elapsed2=25.286960ms, diff=500.579842ms
true

この新しい機能は、アニメーションの計算において、異なるフレームを表す複数のTimeMark間の差異を計算したり、比較したいときに、特に有用である。

ディレクトリの再帰的なコピーと削除

java.nio.file.Pathのためのこれらの新しい関数はExperimentalである。それらを使うには、@OptIn(kotlin.io.path.ExperimentalPathApi::class)@kotlin.io.path.ExperimentalPathApiでオプトインが必要である。あるいは-opt-in=kotlin.io.path.ExperimentalPathApiコンパイラオプションを使うことができる。

java.nio.file.Pathのための二つの新しい拡張関数、copyToRecursively()deleteRecursively()が導入され、それらは再帰的に、

  • ディレクトリとその内容を別の場所にコピー
  • ディレクトリとその内容を削除
    する。これらの機能はバックアップ処理の一部として非常に有用となりうる。

エラーハンドリング

copyToRecursively()を使うとき、コピー中に例外が発生した場合に何が起こるべきか、onErrorラムダ関数をオーバーロードすることによって、定義することができる。

sourceRoot.copyToRecursively(destinationRoot, followLinks = false,
    onError = { source, target, exception ->
        logger.logError(exception, "Failed to copy $source to $target")
        OnErrorResult.TERMINATE
    })

deleteRecursively()を使うとき、ファイルまたはフォルダを削除中、例外が発生した場合に、ファイルまたはフォルダはスキップされる。削除が完了すると、deleteRecursively()は抑制された例外として、発生したすべての例外を含むIOExceptionをスローする。

ファイルの上書き

copyToRecursively()が、宛先ディレクトリにすでにファイルが存在していることを見つけた場合、例外が発生する。代わりにファイルを上書きしたい場合、引数としてoverwriteを持つオバーロードを使い、trueをセットすること。

fun setUpEnvironment(projectDirectory: Path, fixtureName: String) {
    fixturesRoot.resolve(COMMON_FIXTURE_NAME)
        .copyToRecursively(projectDirectory, followLinks = false)
    fixturesRoot.resolve(fixtureName)
        .copyToRecursively(projectDirectory, followLinks = false,
          overwrite = true) // common fixtureにパッチをあてる
}

カスタムコピー処理

コピーのためのカスタムロジックを定義するには、追加の引数としてcopyActionを持つオーバーロードを使う。copyActionを使うことで、例えば好ましい動作を持つ、ラムダ関数を提供することができる。

sourceRoot.copyToRecursively(destinationRoot, followLinks = false) { source, target ->
    if (source.name.startsWith(".")) {
        CopyActionResult.SKIP_SUBTREE
    } else {
        source.copyToIgnoringExistingDirectory(target, followLinks = false)
        CopyActionResult.CONTINUE
    }
}

これらの拡張関数のより詳細な情報については、APIリファレンスを参照のこと。

JavaのOptionalの拡張関数

Kotlin 1.7.0で導入された拡張関数が、現在Stableである。これらの関数は、単にJavaにおけるOptionalクラスと連携する。それらはJVM上のOptionalオブジェクトをアンラップし、変換するために使うことができ、Java APIとの連携をより簡潔にする。詳しくはKotlin 1.7.0の変更点を参照のこと。

kotlin-reflectの性能改善

kotlin-reflectが今ではJVMターゲット1.8でコンパイルされているという事実を利用するため、JavaのClassValueに対する内部的なキャッシュメカニズムを移行した。以前はKClassのみがキャッシュされていたが、現在はKTypeKDeclarationContainerもキャッシュしている。これらの変更は、typeOf()を呼び出すとき、かなりの性能改善がもたらされる。

ドキュメントの更新

Kotlinドキュメントにいくつかの注目に値する変更があった。

刷新と新しいページ

  • Gradle概要 - GradleビルドシステムでKotlinプロジェクトを構成、ビルドする方法、Kotlin Gradleプラグイン内の利用可能なコンパイラオプション、コンパイル、キャッシュについて学ぶ
  • JavaとKotlinのNullability - JavaとKotlinで、おそらくNullの可能性がある変数を扱うアプローチの違いを確認する。
  • Lincheckガイド - JVM上で並列アルゴリズムをテストするため、Lincheckフレームワークをセットアップして使う方法を学ぶ。

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

Kotlin 1.8.0のインストール

IntelliJ IDEA 2021.3, 2022.1, 2022.2は自動的にKotlinプラグインをバージョン1.8.0へのアップデート提案をする。IntelliJ IDEA 2022.3は将来のマイナーアップデートで、Kotlinプラグインの1.8.0バージョンがバンドルされるだろう。

IntelliJ IDEA 2022.3で、既存のプロジェクトをKotlin 1.8.0に移行するには、Kotlinバージョンを1.8.0に変更し、GradleまたはMavenプロジェクトを再インポートすること。

Android Studio Electric Eel (221)とFlamingo (222)のために、Kotlinプラグインのバージョン1.8.0が将来のAndsroid Studioのアップデートで配信されるだろう。新しいコマンドラインコンパイラは、GitHubリリースページでダウンロード可能である。

Kotlin 1.8.0互換性ガイド

Kotlin 1.8.0は機能リリースであり、そのため以前の言語バージョンで書かれたコードと互換性が無い変更がもたらされている。Kotlin 1.8.0互換性ガイド内で、これらの変更の詳細な一覧を確認してほしい。

12
10
0

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
12
10