背景
8/17にKotlin 1.4
がリリースされました。
Androidエンジニアの自分から見てこれは使いたいと思えるものが多かったので、使いたいものを抜粋・紹介したいと思います。
新機能
SAM変換のKotlin対応
これまでSAM(=Single Abstract Method)変換はJavaのコードにのみ対応しているという状況でした。
例えばクリックイベントでよく使うView.OnClickListener
はjavaで書かれているためラムダ式の省略など様々な形でかけていたわけですね。
/**
* Interface definition for a callback to be invoked when a view is clicked.
*/
public interface OnClickListener {
/**
* Called when a view has been clicked.
*
* @param v The view that was clicked.
*/
void onClick(View v);
}
それが.kt
のものに対してもSAM変換ができるようになりました。
名前付き引数とポジショナル引数の混在が可能に
データクラスやメソッドにて名前付き引数が以前からサポートされていて、使っている方もいらっしゃるのではないでしょうか。
それを使う際に、引数が複数ある状態で名前付き引数とそのままを混在することはこれまでできませんでした。
例えば、
data class Book(val title: String, val author: String, val translator: String, val price: Int)
というデータクラスを定義した場合、これを宣言する際に
val book = Book(
title = "Sample Title",
"Sample Author", // author=としないとダメ
(略)
)
というように混在して宣言することができなかったわけですね。これが1.4から混在して宣言することが可能になりました。
そのため、わかりづらかったり混同しやすい引数に関しては名前付き引数を適用、そうでないものはそのまま値を入れるなど皆がとても理解しやすい形でコーディングできそうです。
末尾カンマ
JavaScriptはじめ他言語ではよく見かける末尾カンマがサポートされるようになりました。上述したデータクラスを例にとると、
data class Book(val title: String, val author: String, val translator: String, val price: Int,)
このように最後にカンマを宣言してもコンパイルエラーにならなくなりました。環境によってはLintで指摘をもらう可能性もありますがコーディング中はとても便利そうです。
デフォルト引数を持つ関数に対しての関数呼び出し(::)が可能に
fun main() {
println(apply(::toLength))
}
fun toLength(s: String? = null) = if (s == null) {
0
} else {
s.length
}
fun apply(func: () -> Int): Int = func()
apply(::toLength)
の部分を見てもらうと、引数宣言をしないと動かないのでは?と思いますが、デフォルト引数を参照してくれるようになりました
このコードの場合、toLength
デフォルトがnull
のため0
が返りそれがprintされていることになります。
これまではapply(::toLength)
とするには、apply
部分をそのまま使うことはできず、
fun apply(func: (String?) -> Int): Int = func(null)
このような形で関数に対してデフォルト値を宣言する必要がありました。
Unitを返す関数に対して任意の型を返す関数呼び出し(::)が可能に
同様にUnitを返す関数に対しても関数呼び出しができるようになりました。
fun main() {
foo { hoge() } // 1.4以前での書き方
foo(::hoge) // 1.4からの書き方
}
fun foo(f: () -> Unit) {}
fun hoge() = "hoge"
IDE上の新しいツール
Coroutine Debugger
RxからCoroutineに乗り換えた、Coroutineを使っているという人は多いのではないでしょうか。
ただし、デバッグするうえではやりづらいことも多くありました。コルーチンはスレッド間を行き来するので、特定のコルーチンでの処理が知りたかったり、そのコンテキストをチェックすることが困難でした。
場合によっては、ブレークポイント上のステップの追跡が機能しなかったりしていました。
そこで、IDEのDebug Tool Window
上にCoroutine
のタブができました。
これによって以下のことが可能になりました。
- 各コルーチンの状態を簡単に確認できる
- 実行中と一時停止中の両方のコルーチンのローカル変数とキャプチャされた変数の値を確認できる
- コルーチンのスタックを見ることができる
Coroutineを導入している場合は、デバッグするうえでとても助かる機能ですね。
※ kotlinx-coroutines-coreのバージョンが1.3.8以上である必要があります
コンパイラ
速度改善はじめ後述する型推論まわりも強化されました。
ラムダ式のスマートキャスト
val result = run {
var str = getNullableTitle() // String?が返る
if (str == null) {
str = "Sample Title"
}
str
}
このコードを例にとると、返る値は明らかにnon-null
であるとわかるのですが、スマートキャストはこれまでされずString?
が返ってきていました。
1.4からはこの値がString
として返ってくるようになります。
デリゲートプロパティの型推論の改善
import kotlin.properties.Delegates
fun main() {
var prop: String? by Delegates.observable(null) { p, old, new ->
println("$old → $new")
}
prop = "abc"
prop = "xyz"
}
以前ではこのような書き方だとコンパイルエラーになっていましたが、1.4からこのold
, new
に対してString?
と認識してくれるようになりました。
標準ライブラリ
collectionsに対する機能追加
setOfNotNull()
non-nullなもののみ返してくれます
val set = setOfNotNull(null, 1, 2, 0, null)
println(set) // [1, 2, 0]
shuffled() がsequencesに対して適応可能に
val numbers = (0 until 50).asSequence()
val result = numbers.map { it * 2 }.shuffled().take(5)
IndexedがonEach()/flatMap()に追加
listOf("a", "b", "c", "d").onEachIndexed {
index, item -> println(index.toString() + ":" + item)
}
val list = listOf("hello", "kot", "lin", "world")
val kotlin = list.flatMapIndexed { index, item ->
if (index in 1..2) item.toList() else emptyList()
}
flatMapは自身よく使うのでさらに快適にかけそうです
OrNullがrandom/reduce/reduceIndexedに追加
例えばemptyListに対してrandom()をするとCollection is empty
と例外が出されます
val empty = emptyList<Int>()
println(empty.random())
それに対してOrNullが追加されたことでその場合にnullが返るようになりました。
val empty = emptyList<Int>()
println(empty.randomOrNull()) //nullが返る
emptyListが返ることも多くあると思うのでとても便利になりましたね。reduceで結構使うのではないでしょうか。
runningFold()/runningReduce()
これまでfold/reduce
を使っていてその途中結果も含めた配列を取得したいと思うことがあったのではないでしょうか。わざわざその要素をappend
するのは面倒だったのですが、それがサポートされるようになりました。
val numbers = mutableListOf(0, 1, 2, 3, 4, 5)
val runningReduceSum = numbers.runningReduce { sum, item -> sum + item } // [0, 1, 3, 6, 10, 15]
val runningFoldSum = numbers.runningFold(10) { sum, item -> sum + item } // [10, 10, 11, 13, 16, 20, 25]
sumOf()/minOf()/maxOf()
条件に対する合計値/最小値/最大値を返すことができます
val order = listOf<OrderItem>(
OrderItem("Cake", price = 10.0, count = 1),
OrderItem("Coffee", price = 2.5, count = 3),
OrderItem("Tea", price = 1.5, count = 2))
val total = order.sumOf { it.price * it.count} // Double
val count = order.sumOf { it.count } // Int
JSON serialization
1.4からkotlinx.serializationがstableとなりました。
Gson
がdeprecatedになり、Moshi
を使う方が多かったと思いますが、この機会にserialization
を試してみてはどうでしょうか。
おわりに
以上かいつまみつつ一部紹介しました。下の参考には他機能もたくさんあるのでチェックしてみてください。
1.4でかなり進化したKotlinを是非是非導入して快適なコーディングを!
間違い等あれば遠慮なく指摘いただければと思います
参考