はじめに
Kotlin 1.3が 2018/10/30 にリリースされました
ここでは以下に記載の変更点をメインでまとめていきます(全部は網羅していません)
実際のサンプルコードがあるところの再生ボタンを押すとそのコードが実行されて結果が参照できます
便利なので試してみてください!
注意点
基本的には上記の公式リファレンスを和訳しつつ、各種参考サイトを利用させていただきながらまとめています
英語力があまり自信ないので、内容に不備等があるかもしれませんがご容赦ください
むしろご指摘いただけるとうれしいです!
また、注目のCoroutinesやKotlin/Nativeに関してはまとめていません。
それだけでかなりのボリュームになりますので、別の投稿などでまとめていければと思ってはいます。
(予定は決まってないですが。。。)
Kotlinのmigration tool
今回は機能が多いためか、Migration Toolが提供されています
Tools > Kotlin > Enable migrations detection (experimental)
主な機能
公式ブログで一通り紹介されています
やはり、注目はCoroutines、Kotlin/Nativeでしょうか
今回はこの中の「その他の改善」に入っているような内容を詳しく書きたいと思います
変更点
Kotlin 1.3から変わる点についてまとめます
Coroutines
正式にリリースされたことでパッケージ名が変更されたようです
- 変更前
import kotlinx.coroutines.experimental.*
- Kotlin 1.3
import kotlinx.coroutines.*
Contracts
便利な機能としてスマートキャストがあります
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
}
こういった課題を解決するためにContracts
という機能が追加されました
Contracts
は以下の2ケースでサポートされています
-
関数の戻り値と引数の関係を宣言することで、スマートキャストを改善する
fun require(condition: Boolean) { // これはコンパイラに以下のような指示をするための構文です: // 「この関数が正常に成功するときは、引数のconditioinは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を一度だけ呼び出します」とコンパイラに指示します contract { callsInPlace(block, EXACTLY_ONCE) } } fun foo() { val x: Int synchronize(lock) { x = 42 // コンパイラは、synchronizeに渡されたラムダが1回だけ呼び出されることを知っているので、 // 再割り当ては報告されません } println(x) // コンパイラはラムダが確実に呼び出され、初期化されることを知っているので // 「x」は初期化されるとみなされます }
Contracts in stdlib
stdlib
はすでにContracts
が導入されているので、追加のオプトインをすることなくすぐに恩恵が受けられます
Kotlin1.3からは以下のメソッドにContracts
が追加されています
-
run
,with
,apply
,also
,let
,takeIf
,takeUnless
,synchronized
- ラムダの引数が1度呼び出される
-
repeat
- ラムダの引数が何回か呼び出される
-
assertTrue
,require
,check
- 引数がtrueである
-
assertFalse
- 引数がfalseである
-
isNullOrEmpty
,isNullOrBlank
,assertNotNull
,checkNotNull
,requireNotNull
- 引数はnullではない
Custom Contracts
カスタムでContracts
宣言をする機能はexperimental
で提供されています
現時点での構文は early prototype なので変更される可能性があります
when
when
でローカル変数を定義できます
when
で利用するローカル変数のスコープがより正確になります
fun Request.getBody() =
when (val response = executeRequest()) {
is Success -> response.body
is HttpError -> throw HttpException(response.status)
}
@JvmStatic
, @JvmField
interface
のcompanion object
メンバーに@JvmField
, @JvmStatic
アノテーションを付与できるようになりました
ただし、interface
にstatic変数、メソッドを持てるのがJava1.8からなので、もし使う場合はJVM targetを1.8にする必要があります
Nested declarations in annotation classes
annotation class
にenum
とcompanion object
をネスト出来るようになりました
具体的には以下のようなコードです
annotation class Foo {
enum class Direction { UP, DOWN, LEFT, RIGHT }
annotation class Bar
companion object {
fun foo(): Int = 42
val bar: Int = 42
}
}
Parameterless main
Androidはあまり関係ありませんが、main
関数の引数が不要になりました
fun main() {
println("Hello, world!")
}
Functions with big arity
今までは関数の引数は、最大22個という制限がありましたが、Kotlin1.3からはより多くの引数がとれるようになりました
特に個数が明記されてないですが、普通に作る上で不都合はないのではないかと思います
Progressive mode
プログレッシブモードを有効にすると、コードの安全性と正確性が向上するコンパイラでコンパイルできるようになります
重要な特性としては以下の2つがあります
- プログレッシブモードが有効な状態でコンパイルできるコードは無効にしてもコンパイルできることを保証している
- 基本的にはよりコードを安全にするだけである
- 例:一部の不正なスマートキャストを禁止する、生成されたコードの動作をより安定に変更する
experimental
Kotlin1.3からexperimentalに導入されている機能を紹介します
Inline classes
inline class
はプロパティや関数の宣言ができますが、以下のような制約があります
- プライマリコンストラクタで初期化される単一のプロパティのみ宣言できる
-
init
ブロックは持てない - 内部クラスは持てない
- バッキングフィールドを持てない
- 単純な計算可能なプロパティ(
lateinit
/delegated
プロパティなし)のみを持つことができます
- 単純な計算可能なプロパティ(
- インターフェースを実装することはできるが、クラス階層に参加することはできない
inline class Name(val s: String) {
val length: Int
get() = s.length
fun greet() {
println("Hello, $s")
}
}
fun main() {
val name = Name("Kotlin")
name.greet() // greet()はstatic関数として呼ばれます
println(name.length) // static関数としてgetterが呼ばれます
}
メインの用途としては、ビジネスロジックの層でプリミティブな型ラップする時に使うようなイメージでしょうか。
Unsigned integers
符号なし整数型が導入されています
-
kotlin.UByte
:符号なし8ビット整数。範囲は0〜255 -
kotlin.UShort
:符号なし16ビット整数、範囲は0〜65535 -
kotlin.UInt
:符号なし32ビット整数、範囲は0〜2 ^ 32 - 1 -
kotlin.ULong
:符号なし64ビット整数、範囲は0〜2 ^ 64 - 1
@JvmDefault
Java1.8のinterface
にdefault関数が定義できるようになっています
Standard library
Multiplatform Random
利用できる全てのプラットフォームで乱数の生成方法が統一されました
import kotlin.random.Random
fun main() {
val number = Random.nextInt(42) // number is in range [0, limit)
println(number)
}
isNullOrEmpty/orEmpty extensions
CharSequence
などにはすでに導入されていますが、Collection
, Map
, Array
にも導入されました
Copying elements between two existing arrays
array.copyInto(targetArray, targetOffset, startIndex, endIndex)
で要素のコピーができます
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
key
のリストから値を関連づけてマップを作成する際に利用します
val keys = 'a'..'f'
val map = keys.associateWith { it.toString().repeat(5).capitalize() }
map.forEach { println(it) }
ifEmpty and ifBlank functions
Collections
, maps
, 配列などにifEmpty
, ifBlank
が追加されました
空の場合に代わりに使用できる値が指定できます
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"))
Sealed classes in reflection
KotlinのReflectionにsealed class
のサブクラスを列挙することができるAPIが追加されました
まとめ
コードが正確かつ安全に書ける機能が多く追加されている印象ですね。
個人的にはスマートキャスト周りの改善やwhen
で変数持てるのが特にうれしいです。
ますますKotlinでの開発がしやすくなっていきそうです。