Kotlin 1.3の新機能
リリース日: 2018年10月29日
コルーチンのリリース
長い期間にわたる広範な実戦テストを経て、コルーチンが正式にリリースされました!これは、Kotlin 1.3以降、言語サポートとAPIが完全に安定版になったことを意味します。新しいコルーチンの概要ページをご覧ください。
Kotlin 1.3では、suspend
関数における呼び出し可能参照と、リフレクションAPIにおけるコルーチンのサポートが導入されました。
Kotlin/Native
Kotlin 1.3では、Nativeターゲットの改善と洗練が引き続き行われています。詳細はKotlin/Nativeの概要をご覧ください。
マルチプラットフォームプロジェクト
1.3では、表現力と柔軟性を向上させ、共通コードの共有を容易にするために、マルチプラットフォームプロジェクトのモデルを完全に再構築しました。また、Kotlin/Nativeもターゲットの1つとしてサポートされるようになりました!
古いモデルとの主な違いは次のとおりです。
- 古いモデルでは、共通コードとプラットフォーム固有のコードは、
expectedBy
依存関係によってリンクされた個別のモジュールに配置する必要がありました。 現在では、共通コードとプラットフォーム固有のコードは、同じモジュールの異なるソースルートに配置されるため、プロジェクトの設定が容易になります。 - 現在、さまざまなサポート対象プラットフォーム向けに多数のプリセットプラットフォーム構成が用意されています。
- 依存関係の構成が変更されました。依存関係は 各ソースルートで個別に指定されるようになりました。
- ソースセットは、プラットフォームの任意のサブセット間で共有できるようになりました (たとえば、JS、Android、iOSをターゲットとするモジュールでは、AndroidとiOSの間でのみ共有されるソースセットを持つことができます)。
- マルチプラットフォームライブラリの公開がサポートされるようになりました。
詳細については、マルチプラットフォームプログラミングのドキュメントを参照してください。
コントラクト
Kotlinコンパイラは、警告を提供し、ボイラープレートを削減するために広範な静的解析を行います。最も注目すべき機能の1つはスマートキャストです。これは、実行された型チェックに基づいて自動的にキャストを実行する機能です。
fun foo(s: String?) {
if (s != null) s.length // Compiler automatically casts 's' to 'String'
}
しかし、これらのチェックが別の関数に抽出されるとすぐに、すべてのスマートキャストが消滅してしまいます。
fun String?.isNotNull(): Boolean = this != null
fun foo(s: String?) {
if (s.isNotNull()) s.length // No smartcast :(
}
このような場合の挙動を改善するため、Kotlin 1.3ではコントラクトと呼ばれる実験的なメカニズムが導入されています。
コントラクトを使用すると、関数がその振る舞いをコンパイラが理解できる形で明示的に記述できます。 現在、2つの幅広いケースがサポートされています。
- 関数の呼び出し結果と渡された引数の値との関係を宣言することにより、スマートキャスト解析を改善します。
fun require(condition: Boolean) {
// This is a syntax form which tells the compiler:
// "if this function returns successfully, then the passed 'condition' is true"
contract { returns() implies condition }
if (!condition) throw IllegalArgumentException(...)
}
fun foo(s: String?) {
require(s is String)
// s is smartcast to 'String' here, because otherwise
// 'require' would have thrown an exception
}
- 高階関数が存在する場合の変数初期化解析を改善します。
fun synchronize(lock: Any?, block: () -> Unit) {
// It tells the compiler:
// "This function will invoke 'block' here and now, and exactly one time"
contract { callsInPlace(block, EXACTLY_ONCE) }
}
fun foo() {
val x: Int
synchronize(lock) {
x = 42 // Compiler knows that lambda passed to 'synchronize' is called
// exactly once, so no reassignment is reported
}
println(x) // Compiler knows that lambda will be definitely called, performing
// initialization, so 'x' is considered to be initialized here
}
標準ライブラリにおけるコントラクト
stdlib
は既にコントラクトを利用しており、上記の解析の改善につながっています。 このコントラクトの機能は安定版であり、追加のオプトインなしで今すぐ改善された解析の恩恵を受けることができます。
fun bar(x: String?) {
if (!x.isNullOrEmpty()) {
println("length of '$x' is ${x.length}") // Yay, smartcast to not-null!
}
}
fun main() {
bar(null)
bar("42")
}
カスタムコントラクト
独自の関数に対してコントラクトを宣言することは可能ですが、この機能は実験的であり、現在の構文は初期プロトタイプの状態であり、変更される可能性が非常に高いです。また、現在Kotlinコンパイラはコントラクトを検証しないため、正確で健全なコントラクトを記述するのはプログラマの責任であることに注意してください。
カスタムコントラクトは、DSLスコープを提供するcontract
標準ライブラリ関数の呼び出しによって導入されます。
fun String?.isNullOrEmpty(): Boolean {
contract {
returns(false) implies (this@isNullOrEmpty != null)
}
return this == null || isEmpty()
}
構文の詳細と互換性に関する注意については、KEEPを参照してください。
when
式の主題を変数にキャプチャ
Kotlin 1.3では、when
式の主題を変数にキャプチャできるようになりました。
fun Request.getBody() =
when (val response = executeRequest()) {
is Success -> response.body
is HttpError -> throw HttpException(response.status)
}
以前はwhen
式の直前でこの変数を抽出することも可能でしたが、when
内のval
はそのスコープがwhen
の本体に適切に制限されるため、名前空間の汚染を防ぐことができます。when
式に関する完全なドキュメントはこちらをご覧ください。
インターフェースのコンパニオンにおける@JvmStatic
と@JvmField
Kotlin 1.3では、インターフェースのcompanion
オブジェクトのメンバーに@JvmStatic
および@JvmField
アノテーションを付けることが可能になりました。 クラスファイルでは、そのようなメンバーは対応するインターフェースに引き上げられ、static
としてマークされます。
たとえば、次のKotlinコードは、
interface Foo {
companion object {
@JvmField
val answer: Int = 42
@JvmStatic
fun sayHello() {
println("Hello, world!")
}
}
}
このJavaコードと同等です。
interface Foo {
public static int answer = 42;
public static void sayHello() {
// ...
}
}
アノテーションクラス内のネストされた宣言
Kotlin 1.3では、アノテーションがネストされたクラス、インターフェース、オブジェクト、およびコンパニオンを持つことが可能になりました。
annotation class Foo {
enum class Direction { UP, DOWN, LEFT, RIGHT }
annotation class Bar
companion object {
fun foo(): Int = 42
val bar: Int = 42
}
}
引数なしのmain
関数
慣例として、Kotlinプログラムのエントリポイントはmain(args: Array<String>)
のようなシグネチャを持つ関数であり、args
はプログラムに渡されるコマンドライン引数を表します。しかし、すべてのアプリケーションがコマンドライン引数をサポートしているわけではないため、このパラメータが使用されないままになることがよくあります。
Kotlin 1.3では、パラメータを取らないよりシンプルな形式のmain
関数が導入されました。これで、Kotlinの「Hello, World」は19文字短縮されます!
fun main() {
println("Hello, world!")
}
多数の引数を持つ関数
Kotlinでは、関数型は異なる数のパラメータを取るジェネリッククラスとして表現されます。Function0<R>
、Function1<P0, R>
、Function2<P0, P1, R>
、...。このアプローチには、このリストが有限であり、現在Function22
で終わるという問題がありました。
Kotlin 1.3ではこの制限が緩和され、より多数の引数を持つ関数のサポートが追加されました。
fun trueEnterpriseComesToKotlin(block: (Any, Any, ... /* 42 more */, Any) -> Any) {
block(Any(), Any(), ..., Any())
}
プログレッシブモード
Kotlinはコードの安定性と後方互換性を非常に重視しており、Kotlinの互換性ポリシーでは、破壊的変更(たとえば、以前は問題なくコンパイルされていたコードがコンパイルできなくなる変更)はメジャーリリース(1.2、1.3など)でのみ導入できるとされています。
多くのユーザーは、致命的なコンパイラのバグ修正がすぐに適用され、コードがより安全で正確になるような、はるかに速いサイクルを望んでいると私たちは考えています。そこで、Kotlin 1.3では、コンパイラに-progressive
引数を渡すことで有効にできるプログレッシブコンパイラモードが導入されました。
プログレッシブモードでは、言語セマンティクスにおけるいくつかの修正がすぐに適用される可能性があります。これらの修正はすべて、2つの重要な特性を持っています。
- それらは、古いコンパイラとのソースコードの後方互換性を維持します。つまり、プログレッシブコンパイラでコンパイル可能なすべてのコードは、非プログレッシブコンパイラでも問題なくコンパイルされます。
- それらは、ある意味でコードをより安全にするだけです。たとえば、いくつかの不健全なスマートキャストが禁止されたり、生成されたコードの振る舞いがより予測可能/安定するように変更されたりする可能性があります。
プログレッシブモードを有効にすると、コードの一部を書き直す必要があるかもしれませんが、それほど多くはありません。プログレッシブモードで有効になるすべての修正は、慎重に厳選され、レビューされ、ツールによる移行支援が提供されています。 プログレッシブモードは、最新の言語バージョンに迅速に更新される活発にメンテナンスされているコードベースにとって、良い選択肢となると期待しています。
インラインクラス
インラインクラスはアルファ版です。将来、互換性のない変更があり、手動での移行が必要になる場合があります。 YouTrackでのフィードバックをお待ちしております。 詳細はリファレンスをご覧ください。
Kotlin 1.3では、新しい種類の宣言であるinline class
が導入されました。インラインクラスは通常のクラスの制限されたバージョンと見なすことができ、特にインラインクラスは正確に1つのプロパティを持つ必要があります。
inline class Name(val s: String)
Kotlinコンパイラはこの制限を利用して、インラインクラスの実行時表現を積極的に最適化し、可能な場合はそのインスタンスを基盤となるプロパティの値に置き換えることで、コンストラクタ呼び出しの削除、GC負荷の軽減、およびその他の最適化を可能にします。
inline class Name(val s: String)
fun main() {
// In the next line no constructor call happens, and
// at the runtime 'name' contains just string "Kotlin"
val name = Name("Kotlin")
println(name.s)
}
インラインクラスの詳細はリファレンスをご覧ください。
符号なし整数
符号なし整数はベータ版です。 その実装はほぼ安定していますが、将来、移行手順が必要になる場合があります。 変更を最小限に抑えるよう最善を尽くします。
Kotlin 1.3では、符号なし整数型が導入されました。
kotlin.UByte
: 符号なし8ビット整数、0から255の範囲kotlin.UShort
: 符号なし16ビット整数、0から65535の範囲kotlin.UInt
: 符号なし32ビット整数、0から2^32 - 1の範囲kotlin.ULong
: 符号なし64ビット整数、0から2^64 - 1の範囲
符号付き型のほとんどの機能は、符号なしの対応する型でもサポートされています。
fun main() {
// You can define unsigned types using literal suffixes
val uint = 42u
val ulong = 42uL
val ubyte: UByte = 255u
// You can convert signed types to unsigned and vice versa via stdlib extensions:
val int = uint.toInt()
val byte = ubyte.toByte()
val ulong2 = byte.toULong()
// Unsigned types support similar operators:
val x = 20u + 22u
val y = 1u shl 8
val z = "128".toUByte()
val range = 1u..5u
println("ubyte: $ubyte, byte: $byte, ulong2: $ulong2")
println("x: $x, y: $y, z: $z, range: $range")
}
詳細はリファレンスをご覧ください。
@JvmDefault
@JvmDefault
は実験的です。いつでも削除または変更される可能性があります。 評価目的でのみ使用してください。YouTrackでのフィードバックをお待ちしております。
Kotlinは、Java 6やJava 7など、インターフェースでのデフォルトメソッドが許可されていない幅広いJavaバージョンをターゲットとしています。 利便性のため、Kotlinコンパイラはその制限を回避しますが、この回避策はJava 8で導入されたdefault
メソッドと互換性がありません。
これはJavaとの相互運用性の問題になる可能性があるため、Kotlin 1.3では@JvmDefault
アノテーションが導入されました。 このアノテーションが付けられたメソッドは、JVM用にdefault
メソッドとして生成されます。
interface Foo {
// Will be generated as 'default' method
@JvmDefault
fun foo(): Int = 42
}
警告!APIに
@JvmDefault
をアノテーション付けすることは、バイナリ互換性に深刻な影響を及ぼします。 本番環境で@JvmDefault
を使用する前に、リファレンスページを注意深くお読みください。
標準ライブラリ
マルチプラットフォーム乱数
Kotlin 1.3より前は、すべてのプラットフォームで乱数を生成する統一された方法がなく、JVMではjava.util.Random
のようなプラットフォーム固有のソリューションに頼る必要がありました。このリリースでは、すべてのプラットフォームで利用可能なkotlin.random.Random
クラスを導入することで、この問題が解決されます。
import kotlin.random.Random
fun main() {
val number = Random.nextInt(42) // number is in range [0, limit)
println(number)
}
isNullOrEmpty
およびorEmpty
拡張機能
isNullOrEmpty
およびorEmpty
拡張機能は、一部の型については既に標準ライブラリに存在します。前者はレシーバがnull
または空である場合にtrue
を返し、後者はレシーバがnull
である場合に空のインスタンスにフォールバックします。 Kotlin 1.3では、コレクション、マップ、およびオブジェクトの配列に対しても同様の拡張機能が提供されます。
2つの既存の配列間で要素をコピー
既存の配列型(符号なし配列を含む)に対するarray.copyInto(targetArray, targetOffset, startIndex, endIndex)
関数により、純粋なKotlinで配列ベースのコンテナを簡単に実装できるようになりました。
fun main() {
val sourceArr = arrayOf("k", "o", "t", "l", "i", "n")
val targetArr = sourceArr.copyInto(arrayOfNulls<String>(6), 3, startIndex = 3, endIndex = 6)
println(targetArr.contentToString())
sourceArr.copyInto(targetArr, startIndex = 0, endIndex = 3)
println(targetArr.contentToString())
}
associateWith
キーのリストがあり、これらのキーのそれぞれを何らかの値に関連付けてマップを構築したいという状況は非常に一般的です。 以前はassociate { it to getValue(it) }
関数でこれを行うことができましたが、今回はより効率的で探しやすい代替手段としてkeys.associateWith { getValue(it) }
を導入しました。
fun main() {
val keys = 'a'..'f'
val map = keys.associateWith { it.toString().repeat(5).capitalize() }
map.forEach { println(it) }
}
ifEmpty
およびifBlank
関数
コレクション、マップ、オブジェクト配列、文字シーケンス、およびシーケンスにifEmpty
関数が追加されました。これにより、レシーバが空の場合に代わりに使用されるフォールバック値を指定できます。
fun main() {
fun printAllUppercase(data: List<String>) {
val result = data
.filter { it.all { c -> c.isUpperCase() } }
.ifEmpty { listOf("<no uppercase>") }
result.forEach { println(it) }
}
printAllUppercase(listOf("foo", "Bar"))
printAllUppercase(listOf("FOO", "BAR"))
}
文字シーケンスと文字列には、ifEmpty
と同じことを行うが、空ではなく文字列がすべて空白であるかをチェックするifBlank
拡張機能も追加されています。
fun main() {
val s = "
"
println(s.ifBlank { "<blank>" })
println(s.ifBlank { null })
}
リフレクションにおけるsealedクラス
kotlin-reflect
に新しいAPIが追加され、sealed
クラスのすべての直接的なサブタイプを列挙できるようになりました。具体的にはKClass.sealedSubclasses
です。
その他の変更
Boolean
型にcompanion
が追加されました。Any?.hashCode()
拡張機能が、null
に対して0を返すようになりました。Char
にMIN_VALUE
とMAX_VALUE
定数が提供されるようになりました。- プリミティブ型の
companion
にSIZE_BYTES
とSIZE_BITS
定数が追加されました。
ツール
IDEでのコードスタイルサポート
Kotlin 1.3では、IntelliJ IDEAで推奨されるコードスタイルがサポートされます。 移行ガイドラインについては、このページをご覧ください。
kotlinx.serialization
kotlinx.serializationは、Kotlinでオブジェクトの(デ)シリアライズをマルチプラットフォームでサポートするライブラリです。以前は個別のプロジェクトでしたが、Kotlin 1.3以降、他のコンパイラプラグインと同等にKotlinコンパイラの配布物とともに提供されるようになりました。主な違いは、Serialization IDEプラグインが使用しているKotlin IDEプラグインのバージョンと互換性があるかを手動で監視する必要がなくなったことです。現在、Kotlin IDEプラグインにはシリアライゼーションが含まれています!
詳細についてはこちらをご覧ください。
kotlinx.serializationは現在Kotlin Compilerの配布物とともに提供されていますが、Kotlin 1.3では依然として実験的な機能と見なされています。
スクリプトの更新
スクリプトは実験的です。いつでも削除または変更される可能性があります。 評価目的でのみ使用してください。YouTrackでのフィードバックをお待ちしております。
Kotlin 1.3では、スクリプトAPIの進化と改善が続けられており、外部プロパティの追加、静的または動的依存関係の提供など、スクリプトのカスタマイズに対する実験的なサポートが導入されています。
詳細については、KEEP-75を参照してください。
スクラッチファイルのサポート
Kotlin 1.3では、実行可能なKotlin スクラッチファイルのサポートが導入されました。スクラッチファイルは、.kts拡張子を持つKotlinスクリプトファイルであり、エディタで直接実行し、評価結果を取得できます。
詳細については、スクラッチファイルの一般ドキュメントをご覧ください。