読者です 読者をやめる 読者になる 読者になる

visible true

技術的なメモを書く

Kotlin 1.1で追加された標準ライブラリの関数をざっくり見る

Kotlin 1.1でいくつか標準ライブラリに追加された関数があるのでちら見しながら感想を述べます。

Kotlin 1.1: What’s coming in the standard library | Kotlin Blog

T.also()

let(), apply(), with(), run()に新たな仲間が。

@kotlin.internal.InlineOnly
@SinceKotlin("1.1")
public inline fun <T> T.also(block: (T) -> Unit): T { block(this); return this }

内容としてはlet()の型1つ版って感じです。let()は引数と戻り値の型が別の定義になっていたのでラムダ式の末尾に戻り値を書かないといけませんでした。also()では引数が戻り値になるのでそうした対応が不要になります。

applyに近いですが、applyはラムダ式のスコープがレシーバとなるため呼び出し元のthisとの衝突が起こります。alsoはラムダ式のスコープを汚さずにapplyに近い処理を書けるようになります。

用途としては次のような感じになると思います。

val binding = HogeBinding.inflate(inflator).also {
  it.root.setOnClickListener(this) // thisのスコープが外側のクラスになる
}

T.takeIf()とT.takeUnless()

グローバルな拡張関数が2つ。ある条件を満たした時|満たさない時自身を返す。

@kotlin.internal.InlineOnly
@SinceKotlin("1.1")
public inline fun <T> T.takeIf(predicate: (T) -> Boolean): T? = if (predicate(this)) this else null
@kotlin.internal.InlineOnly
@SinceKotlin("1.1")
public inline fun <T> T.takeUnless(predicate: (T) -> Boolean): T? = if (!predicate(this)) this else null

用途としてはこんな感じかな〜

val params = request.takeIf{ isValidParameter(it) }.params ?: throw RuntimeException()

わりと便利。

Iterable.groupingBy()

groupBy()はリストをkey selectorでグルーピングしてMap<key, list>に変換する。一方でgroupingBy()はkey selectorでグルーピングした結果を次のようにGrouping<T>で返す。

@SinceKotlin("1.1")
public inline fun <T, K> Iterable<T>.groupingBy(crossinline keySelector: (T) -> K): Grouping<T, K> {
    return object : Grouping<T, K> {
        override fun sourceIterator(): Iterator<T> = this@groupingBy.iterator()
        override fun keyOf(element: T): K = keySelector(element)
    }
}

Grouping<T>はfold関数, reduce関数, eachCount関数を持っていて、key selectorでグルーピングしたリストに対してそれぞれの処理を行える。

listOf(1, 2, 3, 4, 5, 6, 7, 8, 9)
  .groupingBy { it % 2 == 0 }
  .fold(0, { a, b ->
    a + b
  }) // Map<key, list>を返す
  .forEach { t, u ->
    println("$t=$u") // false=25, true=20
  }

使いみちはなんか出現文字の頻度を数えるとからしいけどうーんて感じた。mapとかあると便利そうなんだけどな〜と思った。

minOf() and maxOf()

minOf()とmaxOf()はトップレベル関数として定義されている。引数を比較して最小、最大を返す。引数は2つと3つがある。次のコードはIntの実装だがプリミティブ型毎に実装があるほかT型の実装もある

@SinceKotlin("1.1")
@kotlin.internal.InlineOnly
public inline fun minOf(a: Int, b: Int): Int {
    return Math.min(a, b)
}

T型は次の通り。TはComparableである必要がある。

@SinceKotlin("1.1")
public fun <T: Comparable<T>> maxOf(a: T, b: T): T {
    return if (a >= b) a else b
}

引数の最後にComparator<in T>を受け取るバージョンもある。

@SinceKotlin("1.1")
public fun <T> maxOf(a: T, b: T, comparator: Comparator<in T>): T {
    return if (comparator.compare(a, b) >= 0) a else b
}

Collectionには合ったけどカジュアルに比較するやつがないから追加されたのかな。地味に便利ではありそう。

Map.getValue()

Mapのget関数の戻り値はV?であり、keyがない場合はnullを返す。一方で今回追加されたgetValue関数はVを返す。もしもkeyがない場合はNoSuchElementExceptionをスローする。

@SinceKotlin("1.1")
public fun <K, V> Map<K, V>.getValue(key: K): V = getOrImplicitDefault(key)

get関数の戻り値がnull許容型で利用時にめんどくさいってのがあるのでよさそう。 withDefaultをあわせて使えばNoSuchElementExceptionはなくなるがまぁそれならnull許容型使うのと同じかな。

mapOf(
  1 to "a",
  2 to "b"
)
.withDefault { "c" }
.getValue(100) // "c"

Map.minus() operator

Mapにマイナスオペレータが追加された。

var a = mapOf(
                1 to "a",
                2 to "b"
        )
a += Pair(3, "c") // 1="a", 2="b", 3="c"
a -= 1 // 2="b", 3="c"

なるほどな〜。実装としてはこんな感じ。

@SinceKotlin("1.1")
public operator fun <K, V> Map<out K, V>.minus(key: K): Map<K, V>
        = this.toMutableMap().apply { minusAssign(key) }.optimizeReadOnlyMap()

@SinceKotlin("1.1")
@kotlin.internal.InlineOnly
public inline operator fun <K, V> MutableMap<K, V>.minusAssign(key: K) {
    remove(key)
}

Array-like List instantiation functions

Array型と同様にコンストラクタに要素数を指定して、各インデックスの値の初期化をラムダ式で記述できるようになった。

List(3) { i -> "value$i" } // "value0","value1","value2"
MutableList(3) { i -> "value$i" }

使う機会はパッとは思いつかないけどまぁよさそう。

String to number conversions

StringのtoInt関数などで失敗した場合NumberFormatExceptionをスローするが、新たにtoIntOrNull関数が追加された。

@SinceKotlin("1.1")
public fun String.toIntOrNull(): Int? = toIntOrNull(radix = 10)

このほかtoDoubleOrNull関数やtoFloatOrNull関数などがある。try-catchいちいち書かなくていいので便利そう。

Iterable.onEach()

forEach()は戻り値がUnitだけど、onEachは自身を返す。

@SinceKotlin("1.1")
public inline fun <T, C : Iterable<T>> C.onEach(action: (T) -> Unit): C {
    return apply { for (element in this) action(element) }
}

途中にデバッグ用に出力するとかかな〜〜〜。

return listOf(1, 2, 3)
  .onEach {
    println(it) // 1, 2, 3
  }
  .map(Int::toString) // ["1", "2", "3"]

Map.toMap() and Map.toMutableMap()

Mapにコピー関数であるtoMap関数とtoMutableMap関数が追加された。

@SinceKotlin("1.1")
public fun <K, V> Map<out K, V>.toMap(): Map<K, V> = when (size) {
    0 -> emptyMap()
    1 -> toSingletonMap()
    else -> toMutableMap()
}

内部的にはLinkedHashMapが作られるみたい。

@SinceKotlin("1.1")
public fun <K, V> Map<out K, V>.toMutableMap(): MutableMap<K, V> = LinkedHashMap(this)

Abstract collections

読み取り専用としてAbstractCollection、AbstractList、AbstractSet、AbstractMapが、変更可能な値としてAbstractMutableCollection、AbstractMutableList、AbstractMutableSet、AbstractMutableMapが追加された。抽象クラスを継承して独自のコレクションクラスを実装できるようになった。今までなかったのか〜

雑感

also()とtakeIf()はガンガン使っている。めっちゃ便利。どんどん便利になってよい〜