Kotlin 1.3 の新機能
リリース日: 2018年10月29日
Kotlin のリリースサイクルに関する情報は、Kotlin のリリースプロセスを参照してください。
コルーチンの正式リリース
長期にわたる広範な実戦テストを経て、コルーチン(coroutines)が正式にリリースされました!これにより、Kotlin 1.3 から言語サポートと API は完全に安定(fully stable)しました。新しい コルーチンの概要 ページをぜひチェックしてください。
Kotlin 1.3 では、suspend 関数に対する呼び出し可能参照(callable references)と、リフレクション API におけるコルーチンのサポートが導入されました。
Kotlin/Native
Kotlin 1.3 では、Native ターゲットの改善と磨き上げが継続されています。詳細は Kotlin/Native の概要 を参照してください。
マルチプラットフォームプロジェクト
1.3 では、表現力と柔軟性を向上させ、共通コードの共有をより容易にするために、マルチプラットフォームプロジェクトのモデルを完全に刷新しました。また、Kotlin/Native もターゲットの一つとしてサポートされるようになりました。
旧モデルとの主な違いは以下の通りです:
- 旧モデルでは、共通コードとプラットフォーム固有のコードを別々のモジュールに配置し、
expectedBy依存関係でリンクする必要がありました。新モデルでは、共通コードとプラットフォーム固有のコードが同じモジュールの異なるソースルートに配置されるようになり、プロジェクトの設定がより簡単になりました。 - サポートされている様々なプラットフォーム向けに、多数の プリセットされたプラットフォーム設定 が用意されました。
- 依存関係の設定 が変更されました。依存関係はソースルートごとに個別に指定するようになりました。
- ソースセットを、プラットフォームの任意のサブセット間で共有できるようになりました(例えば、JS、Android、iOS をターゲットとするモジュールにおいて、Android と iOS の間だけで共有されるソースセットを持つことができます)。
- マルチプラットフォームライブラリの公開 がサポートされました。
詳細については、マルチプラットフォームプログラミングのドキュメント を参照してください。
コントラクト (Contracts)
Kotlin コンパイラは、警告を表示しボイラープレートを削減するために、広範な静的解析を行います。最も注目すべき機能の一つはスマートキャストです。これは実行された型チェックに基づいて、自動的にキャストを行う機能です。
fun foo(s: String?) {
if (s != null) s.length // コンパイラが自動的に 's' を 'String' にキャストする
}しかし、これらのチェックが別の関数に抽出されると、スマートキャストは即座に機能しなくなります。
fun String?.isNotNull(): Boolean = this != null
fun foo(s: String?) {
if (s.isNotNull()) s.length // スマートキャストが効かない :(
}このようなケースでの動作を改善するために、Kotlin 1.3 では コントラクト (contracts) と呼ばれる実験的なメカニズムが導入されました。
コントラクト を使用すると、関数はその動作をコンパイラが理解できる方法で明示的に記述できます。現在、大きく分けて 2 つのケースがサポートされています。
- 関数の呼び出し結果と渡された引数の値の関係を宣言することで、スマートキャストの解析を向上させる:
fun require(condition: Boolean) {
// これは以下をコンパイラに伝える構文形式です:
// 「この関数が正常にリターンした場合、渡された 'condition' は true である」
contract { returns() implies condition }
if (!condition) throw IllegalArgumentException(...)
}
fun foo(s: String?) {
require(s is String)
// ここで s は 'String' にスマートキャストされます。
// そうでなければ 'require' が例外をスローしているはずだからです。
}- 高階関数の存在下での変数初期化解析を向上させる:
fun synchronize(lock: Any?, block: () -> Unit) {
// これはコンパイラに以下を伝えます:
// 「この関数はここで今すぐ 'block' を呼び出し、かつ、ちょうど1回だけ呼び出す」
contract { callsInPlace(block, EXACTLY_ONCE) }
}
fun foo() {
val x: Int
synchronize(lock) {
x = 42 // コンパイラは 'synchronize' に渡されたラムダがちょうど1回呼ばれることを知っているため、
// 再代入のエラーは報告されません。
}
println(x) // コンパイラはラムダが必ず呼ばれて初期化が行われることを知っているため、
// ここで 'x' は初期化済みであると見なされます。
}標準ライブラリにおけるコントラクト
stdlib はすでにコントラクトを利用しており、上述のような解析の改善に役立っています。コントラクトのこの部分は 安定(stable) しており、追加のオプトインなしで今すぐ改善された解析の恩恵を受けることができます。
fun bar(x: String?) {
if (!x.isNullOrEmpty()) {
println("length of '$x' is ${x.length}") // やった、not-null へのスマートキャスト!
}
}
fun main() {
bar(null)
bar("42")
}カスタムコントラクト
独自の関数に対してコントラクトを宣言することも可能ですが、この機能は 実験的(experimental) です。現在の構文は初期のプロトタイプ段階にあり、将来変更される可能性が非常に高いためです。また、現在の Kotlin コンパイラはコントラクトの妥当性を検証しないため、正しく健全なコントラクトを作成することはプログラマの責任であることに注意してください。
カスタムコントラクトは、DSL スコープを提供する stdlib の 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個 */, Any) -> Any) {
block(Any(), Any(), ..., Any())
}プログレッシブモード
Kotlin はコードの安定性と後方互換性を非常に重視しています。Kotlin の互換性ポリシーによれば、破壊的変更(例:以前は正常にコンパイルできていたコードがコンパイルできなくなるような変更)は、メジャーリリース(1.2, 1.3 など)でしか導入できません。
私たちは、多くのユーザーが、重要なコンパイラのバグ修正が即座に適用され、コードがより安全で正確になるような、より速いサイクルを利用できると考えています。そこで Kotlin 1.3 では、コンパイラに -progressive 引数を渡すことで有効にできる プログレッシブ(progressive) モードを導入しました。
プログレッシブモードでは、言語セマンティクスのいくつかの修正が即座に適用されます。これらの修正にはすべて 2 つの重要な特性があります:
- 旧バージョンのコンパイラとのソースコードの後方互換性を維持します。つまり、プログレッシブコンパイラでコンパイル可能なすべてのコードは、非プログレッシブコンパイラでも正常にコンパイルされます。
- ある意味でコードを より安全 にするだけです。例えば、不健全なスマートキャストを禁止したり、生成されたコードの動作をより予測可能で安定したものに変更したりします。
プログレッシブモードを有効にすると、コードの一部を書き直す必要があるかもしれませんが、それほど多くはないはずです。プログレッシブモードで有効になるすべての修正は、慎重に厳選され、レビューされ、ツールによる移行支援が提供されます。最新の言語バージョンに迅速にアップデートされる、アクティブにメンテナンスされているコードベースにとって、プログレッシブモードは良い選択肢になると期待しています。
インラインクラス (Inline classes)
インラインクラスは アルファ(Alpha) 段階です。将来、互換性のない変更が行われ、手動での移行が必要になる可能性があります。 フィードバックを YouTrack でお待ちしています。 詳細は リファレンス を参照してください。
Kotlin 1.3 では、新しい種類の宣言である inline class が導入されました。インラインクラスは通常のクラスの制限されたバージョンと見なすことができ、特にインラインクラスはちょうど 1 つのプロパティを持つ必要があります。
inline class Name(val s: String)Kotlin コンパイラはこの制限を利用して、インラインクラスのランタイム表現を積極的に最適化し、可能な限りインスタンスを基底のプロパティの値に置き換えます。これにより、コンストラクタの呼び出しの削除、GC への負荷軽減、その他の最適化が可能になります。
inline class Name(val s: String)
fun main() {
// 次の行ではコンストラクタの呼び出しは発生しません。
// ランタイムにおいて 'name' は単なる文字列 "Kotlin" を含みます。
val name = Name("Kotlin")
println(name.s)
}詳細はインラインクラスの リファレンス を参照してください。
符号なし整数 (Unsigned integers)
符号なし整数は ベータ(Beta) 段階です。 実装はほぼ安定していますが、将来的に移行ステップが必要になる可能性があります。 変更を最小限に抑えるよう最善を尽くします。
Kotlin 1.3 では符号なし整数型が導入されました:
kotlin.UByte: 符号なし 8 ビット整数、範囲は 0 〜 255kotlin.UShort: 符号なし 16 ビット整数、範囲は 0 〜 65535kotlin.UInt: 符号なし 32 ビット整数、範囲は 0 〜 2^32 - 1kotlin.ULong: 符号なし 64 ビット整数、範囲は 0 〜 2^64 - 1
符号付き整数の機能の大部分は、符号なしの対応する型でもサポートされています:
fun main() {
// リテラル接尾辞を使用して符号なし型を定義できます
val uint = 42u
val ulong = 42uL
val ubyte: UByte = 255u
// stdlib の拡張機能を使用して、符号付き型から符号なし型へ、またはその逆の変換が可能です:
val int = uint.toInt()
val byte = ubyte.toByte()
val ulong2 = byte.toULong()
// 符号なし型は同様の演算子をサポートしています:
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は 実験的(Experimental) です。いつでも削除または変更される可能性があります。 評価目的でのみ使用してください。フィードバックを YouTrack でお待ちしています。
Kotlin は Java 6 や Java 7 を含む幅広い Java バージョンをターゲットとしていますが、これらのバージョンではインターフェースのデフォルトメソッドが許可されていません。利便性のために Kotlin コンパイラはその制限を回避して動作しますが、この回避策は Java 8 で導入された default メソッドと互換性がありません。
これは Java との相互運用性において問題となる可能性があるため、Kotlin 1.3 では @JvmDefault アノテーションが導入されました。このアノテーションが付いたメソッドは、JVM 向けに default メソッドとして生成されます。
interface Foo {
// 'default' メソッドとして生成されます
@JvmDefault
fun foo(): Int = 42
}警告! API に
@JvmDefaultを付けることは、バイナリ互換性に重大な影響を及ぼします。 プロダクション環境で@JvmDefaultを使用する前に、必ず リファレンスページ を注意深く読んでください。
標準ライブラリ (Standard library)
マルチプラットフォーム対応の Random
Kotlin 1.3 以前は、すべてのプラットフォームで乱数を生成する統一された方法がなく、JVM 上の java.util.Random のようなプラットフォーム固有のソリューションに頼る必要がありました。このリリースでは、すべてのプラットフォームで利用可能な kotlin.random.Random クラスを導入することで、この問題を解決しました。
import kotlin.random.Random
fun main() {
val number = Random.nextInt(42) // 数値は [0, limit) の範囲内
println(number)
}isNullOrEmpty および orEmpty 拡張機能
一部の型に対する isNullOrEmpty および orEmpty 拡張機能は、すでに stdlib に存在していました。前者はレシーバが 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"))
}文字シーケンスと文字列には、さらに ifBlank 拡張機能が追加されました。これは ifEmpty と同じことを行いますが、空であるかどうかに加えて、文字列がすべて空白文字であるかどうかもチェックします。
fun main() {
val s = "
"
println(s.ifBlank { "<blank>" })
println(s.ifBlank { null })
}リフレクションにおける Sealed クラス
kotlin-reflect に、sealed クラスのすべての直接のサブタイプを列挙するために使用できる新しい API、KClass.sealedSubclasses を追加しました。
その他の小さな変更
Boolean型にコンパニオンが追加されました。nullに対して 0 を返すAny?.hashCode()拡張機能。CharにMIN_VALUEおよびMAX_VALUE定数が提供されました。- プリミティブ型のコンパニオンに
SIZE_BYTESおよびSIZE_BITS定数が追加されました。
ツール (Tooling)
IDE でのコードスタイルサポート
Kotlin 1.3 では、IntelliJ IDEA における 推奨コードスタイル のサポートが導入されました。移行ガイドラインについては、このページ を確認してください。
kotlinx.serialization
kotlinx.serialization は、Kotlin におけるオブジェクトの(デ)シリアル化のためのマルチプラットフォームサポートを提供するライブラリです。以前は別のプロジェクトでしたが、Kotlin 1.3 からは他のコンパイラプラグインと同様に Kotlin コンパイラの配布物に含まれるようになりました。主な違いは、使用している Kotlin IDE プラグインのバージョンと Serialization IDE プラグインの互換性を手動で気にする必要がなくなったことです。現在、Kotlin IDE プラグインにはすでに serialization が含まれています。
詳細は こちら を参照してください。
kotlinx.serialization が Kotlin コンパイラの配布物に含まれるようになりましたが、Kotlin 1.3 では依然として実験的機能と見なされています。
スクリプティングのアップデート
スクリプティングは 実験的(Experimental) です。いつでも削除または変更される可能性があります。 評価目的でのみ使用してください。フィードバックを YouTrack でお待ちしています。
Kotlin 1.3 ではスクリプティング API の進化と改善が続けられており、外部プロパティの追加、静的または動的な依存関係の提供など、スクリプトのカスタマイズのための実験的なサポートが導入されています。
追加の詳細については、KEEP-75 を参照してください。
スクラッチ(Scratches)のサポート
Kotlin 1.3 では、実行可能な Kotlin スクラッチファイル(scratch files) のサポートが導入されました。スクラッチファイル は .kts 拡張子を持つ Kotlin スクリプトファイルで、エディタ内で直接実行して評価結果を得ることができます。
詳細は一般的な Scratches のドキュメント を参照してください。
