※ソース記事はこちら
※Kotlin/JSやKotlin/Nativeについては個人的に使っていないので、割愛します。
Kotlin 1.6.20により、将来の言語機能のプレビューが明らかになり、マルチプラットフォームのプロジェクトのために階層構造がデフォルトになり、他のコンポーネントに進化的な改善がもたらされている。
こちらの動画でも、変更の短い概要を見ることができる。
言語
Kotlin 1.6.20では、2つの言語の特徴を試すことができる。
- Kotlin/JVMのためのコンテキストレシーバーのプロトタイプ
- 明確なnon-nullable型
Kotlin/JVMのためのコンテキストレシーバーのプロトタイプ
この機能はKotlin/JVMのためにのみ利用可能なプロトタイプである。-Xcontext-receivers
を有効にすることで、コンパイラは本番コードでは利用できないプレリリースのバイナリを生成するだろう。おもちゃのプロジェクトでのみコンテキストレシーバーを使うこと。You Trackでフィードバックをいただけると幸いである。
Kotlin 1.6.20では、もはや一つのレシーバーに限定されることはない。より必要なら、宣言にコンテキストレシーバーを追加することで、関数、プロパティ、クラスをコンテキスト依存(あるいはコンテキスト的)にすることができる。コンテキストの宣言は次のように行う。
- すべてのコンテキストレシーバーは、暗黙のレシーバーとして、呼び出し側のスコープに存在する必要がある。
- 宣言されたコンテキストレシーバーは、暗黙のレシーバーとして、本体のスコープにもたらされる。
interface LoggingContext {
val log: Logger // このコンテキストはloggerへの参照を提供する
}
context(LoggingContext)
fun startBusinessOperation() {
// LoggingContextは暗黙のレシーバーなので、logプロパティにアクセスすることができる
log.info("Operation has started")
}
fun test(loggingContext: LoggingContext) {
with(loggingContext) {
// startBusinessOperation()を呼び出すためには、スコープ内でLoggingContextを
// 暗黙のレシーバーにする必要がある
startBusinessOperation()
}
}
コンテキストレシーバーをプロジェクトで有効にするには、-Xcontext-receivers
コンパイラオプションを使う必要がある。この機能と文法についての詳細な説明は、KEEPで見つけられる。
留意すべきは、実装はプロトタイプであるということである。
-
-Xcontext-receivers
を有効にすると、コンパイラは本番コードでは利用できないプレリリースのバイナリを生成するだろう。 - コンテキストレシーバーのためのIDEのサポートは、現状最小限である。
この機能を試し、こちらのYouTrack課題で、この機能に対する考えと体験について、共有してほしい。何か問題にぶつかったら、是非新しい課題を提出していただきたい。
明確なnon-nullable型
明確なnon-nullableな型はBetaである。これらはほぼ安定しているが、将来、移行手順が必要になるかもしれない。しなければならない変更を最小限にするため、ベストを尽くすつもりである。
ジェネリクスのJavaのクラスとインターフェイスを継承するときに、より良い相互運用性を提供するため、Kotlin 1.6.20では、新しい文法である、T & Any
とともに、ジェネリクスの型パラメータを利用場所で明確にnon-nullableと印をつけることが可能である。この文法の形は、交差型の記法から由来しており、現在、&
の左側のnullableな上限境界を持つ型パラメータと、右側のnon-nullableなAny
に限定されている。
fun <T> elvisLike(x: T, y: T & Any): T & Any = x ?: y
fun main() {
// OK
elvisLike<String>("", "").length
// Error: 'null' は、non-null型の値として許可されない
elvisLike<String>("", null).length
// OK
elvisLike<String?>(null, "").length
// Error: 'null' は、non-null型の値として許可されない
elvisLike<String?>(null, null).length
}
この機能を有効にするには、言語バージョンを1.7
に設定すること。
kotlin {
sourceSets.all {
languageSettings.apply {
languageVersion = "1.7"
}
}
}
こちらのKEEPで、明確なnon-nullable型について、知識を深める。
Kotlin/JVM
Kotlin 1.6.20では次のものが導入されている。
- JVMインターフェイスのデフォルトメソッドの互換性の改善:インターフェイスのための新しい
@JvmDefaultWithCompatibility
アノテーションと、-Xjvm-default
モードでの互換性の変更 - JVMバックエンドでの単一のモジュールの並列コンパイルのサポート
- 関数インターフェイスのコンストラクタに対する関数参照のサポート
インターフェイスのための新しい@JvmDefaultWithCompatibilityアノテーション
Kotlin 1.6.20では、新しいアノテーションである、@JvmDefaultWithCompatibility
が導入されており、-Xjvm-default=all
コンパイラオプションとともに使い、すべてのKotlinインターフェイスのうちすべての抽象でないメンバーのために、JVMインターフェイスデフォルトメソッドを作る。
もし-Xjvm-default=all
無しでコンパイルされたKotlinインターフェイスを使うクライアントがある場合、このオプションでコンパイルされたコードとはバイナリ非互換になるかもしれない。Kotlin 1.6.20より前では、この互換性の問題を避けるため、推奨するアプローチは、-Xjvm-default=all-compatibility
モードでかつ、この種の互換性が不要なインターフェイスに@JvmDefaultWithoutCompatibility
アノテーションを使うことだった。
このアプローチはいくつか不利な点があった。
- 新しいインターフェイスが追加されたときに、アノテーションを追加することを容易に忘れうる。
- 大抵、公開APIよりも非公開な部分で多くのインターフェイスがあり、コードの中の多くの箇所でこのアノテーションを持つことになる。
今では、-Xjvm-default=all
モードと、@JvmDefaultWithCompatibility
アノテーションで、インターフェイスをマークすることができる。これにより、公開API内の全インターフェイスに一度このアノテーションを追加し、新しい公開でないコードには一つもアノテーションを使う必要がなくなる。
このYou Trackチケットで、この新しいアノテーションについてのフィードバックを残してほしい。
-Xjvm-defaultモードでの互換性の変更
Kotlin 1.6.20では、-Xjvm-default=all
または-Xjvm-default=all-compatibility
でコンパイルされたモジュールに対して、デフォルトモード(-Xjvm-default=disable
コンパイラオプション)でモジュールをコンパイルするオプションが追加されている。従来通り、すべてのモジュールが-Xjvm-default=all
または-Xjvm-default=all-compatibility
モードを持っている場合にも、コンパイルは成功するだろう。You Track課題にフィードバックを残していただくことができる。
Kotlin 1.6.20では、-Xjvm-default
コンパイラオプションの、compatibility
とenable
が非推奨である。互換性に関する他のモードの説明に変更があるが、全体的なロジックは同じ状態のままである。更新された説明を、調べることができる。
Java相互運用性におけるデフォルトメソッドに関しての、より詳細については、相互運用性ドキュメントとこちらのブログ投稿を参照のこと。
JVMバックエンドでの単一のモジュールの並列コンパイルのサポート
新しいJVM中間表現のバックエンドにおいて、コンパイル時間の改善が継続的に行われれている。Kotlin 1.6.20では、並列にモジュール内の全ファイルをコンパイルするための実験的なJVM中間表現モードが追加されている。並列コンパイルは、全体のコンパイル時間を最大15%減らすことができる。
-Xbackend-threads
コンパイラオプションで、実験的な並列バックエンドモードを有効にすること。このオプションには、次の引数を使う。
-
N
は、使うスレッド数である。これはCPUコア数よりも大きくすべきではない。そうしないとスレッド間のコンテキストスイッチのために、並列化の効果がなくなってしまう。 -
0
はそれぞれのCPUコアで別々のスレッドを分けて使う。
Gradleでは、タスクを並列に実行することができるが、この種の並列化は、プロジェクト(あるいはプロジェクトの大部分)がGradleの視点から、大きな一つのタスクであるときには、あまり役に立たない。もし非常に大きなモノシリックなモジュールがある場合は、より早くコンパイルするには、並列コンパイルを使うこと。もしプロジェクトが多くの小さなモジュールから構成されており、Gradleによって並列にビルドされている場合は、別の並列化レイアが追加されることで、コンテキストスイッチにより、性能が劣化するかもしれない。
並列コンパイルはいくつかの制約がある。
- kaptは中間表現バックエンドを無効にするため、kaptとは連携しない。
- 設計により、より多くのJVMヒープ領域を必要とする。ヒープの量はスレッド数に比例する。
関数インターフェイスのコンストラクタに対する関数参照のサポート
関数インターフェイスのコンストラクタに対する関数参照のサポートにより、コンストラクタ関数を持つインターフェイスから、関数型のインターフェイスへ移行する、ソースレベルで互換性のある方法が追加される。
次のコードを考えてみよう。
interface Printer {
fun print()
}
fun Printer(block: () -> Unit): Printer = object : Printer { override fun print() = block() }
関数インターフェイスのコンストラクタに対する関数参照により、このコードは単に関数インターフェイスの宣言に置き換えられる。
fun interface Printer {
fun print()
}
コンストラクタは暗黙的に作られ、::Printer
関数参照を使うすべてのコードはコンパイルされる。例えば次のように。
documentsStorage.addPrinter(::Printer)
レガシーなPrinter
関数は、DeprecationLevel.HIDDEN
を持つ@Deprecated
アノテーションがマークして、バイナリ互換性を保持すること。
@Deprecated(message = "非推奨についてのメッセージを追加する", level = DeprecationLevel.HIDDEN)
fun Printer(...) {...}
この機能を有効にするには、-XXLanguage:+KotlinFunInterfaceConstructorReference
コンパイラオプションを使うこと。
セキュリティ ※割愛
Gradle
Kotlin 1.6.20では、次の変更がKotlin Gradleプラグインにもたらされている。
- Kotlinコンパイラの実行戦略を定義する、新しい
kotlin.compiler.execution.strategyとcompilerExecutionStrategy
プロパティ kapt.use.worker.api
、kotlin.experimental.coroutines
、kotlin.coroutines
オプションの非推奨kotlin.parallel.tasks.in.project
ビルドオプションの削除
Kotlinコンパイラ実行戦略を定義するプロパティ
Kotlin 1.6.20より前は、Kotlinコンパイラ実行戦略を定義するために、-Dkotlin.compiler.execution.strategy
システムプロパティが使われていた。このプロパティはいくつかのケースで使いづらいかもしれなかった。Kotlin 1.6.20では、同じ名前である、kotlin.compiler.execution.strategy
Gradleプロパティと、コンパイルタスクプロパティであるcompilerExecutionStrategy
が導入されている。
システムプロパティはまだ動作するが、将来のリリースで削除されるだろう。
プロパティの現在の優先順位は次のとおりである。
- タスクプロパティである
compilerExecutionStrategy
は、システムプロパティと、Gradleプロパティであるkotlin.compiler.execution.strategy
よりも優先する。 - Gradleプロパティはシステムプロパティよりも優先する。
これらのプロパティには3つのコンパイラ実行戦略を割り当てることができる。
戦略 | Kotlinコンパイラが実行される場所 | インクリメンタルコンパイル | その他の特徴 |
---|---|---|---|
Daemon | 自身のデーモンプロセス内 | 可能 | デフォルトの戦略。異なるGradleデーモン間で共有可能 |
In process | Gradleデーモンプロセス内 | 不可 | Gradleデーモンとヒープを共有可能 |
Out of process | それぞの呼び出しごとに別プロセス内 | 不可 | - |
さらに、kotlin.compiler.execution.strategy
プロパティ(システムとGradle両方)に対して利用可能な値は以下である。
-
daemon
(デフォルト) in-process
out-of-process
gradle.properties
内で、kotlin.compiler.execution.strategy
Gradleプロパティを次のように使うこと。
# gradle.properties
kotlin.compiler.execution.strategy=out-of-process
compilerExecutionStrategy
タスクプロパティの利用可能な値は以下である。
-
org.jetbrains.kotlin.gradle.tasks.KotlinCompilerExecutionStrategy.DAEMON
(デフォルト) org.jetbrains.kotlin.gradle.tasks.KotlinCompilerExecutionStrategy.IN_PROCESS
org.jetbrains.kotlin.gradle.tasks.KotlinCompilerExecutionStrategy.OUT_OF_PROCESS
compilerExecutionStrategy
タスクプロパティは、build.gradle.kts
ビルドスクリプト内で、次のように使うこと。
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import org.jetbrains.kotlin.gradle.tasks.KotlinCompilerExecutionStrategy
// …
tasks.withType<KotlinCompile>().configureEach {
compilerExecutionStrategy.set(KotlinCompilerExecutionStrategy.IN_PROCESS)
}
こちらのYou Track課題にフィードバックを是非残してもらいたい。
kaptとコルーチンのためのビルドオプションの非推奨
Kotlin 1.6.20では、プロパティの非推奨レベルが変更された。
-
kapt.use.worker.api
で、Kotlinデーモン経由で、kaptを実行できることが非推奨になった。現在は、Gradleの出力に警告が生成される。デフォルトでは、1.3.70のリリースからkaptはGradleワーカーを使っており、この方法を踏襲することを推奨する。
将来のリリースでは、kapt.use.worker.api
オプションは削除される予定である。 -
kotlin.experimental.coroutines
Gradle DSLオプションとgradle.properties
内のkotlin.coroutines
プロパティは非推奨になった。build.gradle(.kts)
ファイルで、単にsuspend関数を使うか、kotlinx.coroutine
の依存性を追加すること。
Coroutineガイド
でコルーチンについての知識を深める。
kotlin.parallel.tasks.in.projectビルドオプションの削除
Kotlin 1.5.20では、 kotlin.parallel.tasks.in.project
ビルドオプションの非推奨がアナウンスされた。このオプションはKotlin 1.6.20で削除された。
プロジェクトによっては、Kotlinデーモンで並列コンパイルするのは、より資金が必要になるかもしれない。資金の浪費を減らすため、Kotlinデーモンのヒープサイズを増やすこと。
Kotlin Gradleプラグインで、現在サポートされているコンパイラオプションについての知識を深める。