55
47

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Kotlin1.4で使いたい新機能を一部紹介する

Last updated at Posted at 2020-08-25

背景

8/17にKotlin 1.4がリリースされました。
Androidエンジニアの自分から見てこれは使いたいと思えるものが多かったので、使いたいものを抜粋・紹介したいと思います。

新機能

SAM変換のKotlin対応

これまでSAM(=Single Abstract Method)変換はJavaのコードにのみ対応しているという状況でした。
例えばクリックイベントでよく使うView.OnClickListenerはjavaで書かれているためラムダ式の省略など様々な形でかけていたわけですね。

View.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変換ができるようになりました。

名前付き引数とポジショナル引数の混在が可能に

データクラスやメソッドにて名前付き引数が以前からサポートされていて、使っている方もいらっしゃるのではないでしょうか。
それを使う際に、引数が複数ある状態で名前付き引数とそのままを混在することはこれまでできませんでした。

例えば、

Book.kt
data class Book(val title: String, val author: String, val translator: String, val price: Int)

というデータクラスを定義した場合、これを宣言する際に

Snippet.kt
val book = Book(
    title = "Sample Title",
    "Sample Author", // author=としないとダメ
    ()
)

というように混在して宣言することができなかったわけですね。これが1.4から混在して宣言することが可能になりました。
そのため、わかりづらかったり混同しやすい引数に関しては名前付き引数を適用、そうでないものはそのまま値を入れるなど皆がとても理解しやすい形でコーディングできそうです。

末尾カンマ

JavaScriptはじめ他言語ではよく見かける末尾カンマがサポートされるようになりました。上述したデータクラスを例にとると、

Book.kt
data class Book(val title: String, val author: String, val translator: String, val price: Int,)

このように最後にカンマを宣言してもコンパイルエラーにならなくなりました。環境によってはLintで指摘をもらう可能性もありますがコーディング中はとても便利そうです。

デフォルト引数を持つ関数に対しての関数呼び出し(::)が可能に

Snippeet.kt
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を返す関数に対しても関数呼び出しができるようになりました。

Snippet.kt
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以上である必要があります

コンパイラ

速度改善はじめ後述する型推論まわりも強化されました。

ラムダ式のスマートキャスト

Snippet.kt
val result = run {
    var str = getNullableTitle() // String?が返る
    if (str == null) {
        str = "Sample Title"
    }
    str
}

このコードを例にとると、返る値は明らかにnon-nullであるとわかるのですが、スマートキャストはこれまでされずString?が返ってきていました。
1.4からはこの値がStringとして返ってくるようになります。

デリゲートプロパティの型推論の改善

Snippet.kt
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なもののみ返してくれます

Snippet.kt
val set = setOfNotNull(null, 1, 2, 0, null)
println(set) // [1, 2, 0]

shuffled() がsequencesに対して適応可能に

Snippet.kt
val numbers = (0 until 50).asSequence()
val result = numbers.map { it * 2 }.shuffled().take(5)

IndexedがonEach()/flatMap()に追加

Snippet.kt
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と例外が出されます

Snippet.kt
val empty = emptyList<Int>()
println(empty.random())

それに対してOrNullが追加されたことでその場合にnullが返るようになりました。

Snippet.kt
val empty = emptyList<Int>()
println(empty.randomOrNull()) //nullが返る

emptyListが返ることも多くあると思うのでとても便利になりましたね。reduceで結構使うのではないでしょうか。

runningFold()/runningReduce()

これまでfold/reduceを使っていてその途中結果も含めた配列を取得したいと思うことがあったのではないでしょうか。わざわざその要素をappendするのは面倒だったのですが、それがサポートされるようになりました。

Snippet.kt
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()

条件に対する合計値/最小値/最大値を返すことができます

Snippet.kt
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を是非是非導入して快適なコーディングを!

間違い等あれば遠慮なく指摘いただければと思います

参考

55
47
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
55
47

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?