SequenceとList(Array)

TL;DR

Sequence1つの値に対する操作が全て行われた後、必要なら次のが処理される。
Listすべての値に対して1つの操作が行われた後、必要なら次の操作が行われる

Kotlinでのデータの集合

Kotlinにはデータの集合を操作するためのクラスとして、List, Array, Sequenceがあります。
ListやArrayは他の多くに言語にもあるため想像がつくかと思います。では、Sequenceとは何なのでしょうか?
Sequenceを理解するためにList, Arrayとの違いをみていきましょう。

List vs Array

ListArrayはそれぞれの要素に対して操作を行うという観点ではほとんど同じと言えます。大きな違いとしてはArrayJavaの配列型と同等の扱いになります。Javaとの互換性を保つためにArrayがあると推測できます。

以降の文ではArrayは無視してSequenceListについて書いていきます。

Sequence vs List

実際の処理を見ると違いがわかりやすいので、
(1, 2, 3, 4)の値を持ったSequenceとListに対して以下の処理を行う場合の動作をみていきます

  1. それぞれの要素を二乗する : map{v -> v * v}
  2. それぞれの要素に1を足す : map{v -> v + 1}
  3. 値が5の要素を除く : filter{v != 5}
  4. 残った要素から最初に2つを取り出す: take(2)
  5. 1〜4の結果を出力: foreach{ v -> println(v)}

処理過程

操作の過程を以下の表に示します。

f:id:scache:20180419000703p:plain:w300f:id:scache:20180419000707p:plain:w300

Sequenceの4の列がすべて空白になっています(書き忘れじゃないですよ)。
今回の場合、Sequenceでは値4に対してまったく操作が行われません。ここがListとの大きな違いとなります。

Sequenceでは遅延評価が行われ、値1に対する操作(map -> map -> filter -> take)がすべて終わった後に値2,3の操作が行われます。
13のtake(2)が終わった段階でtake(2)の結果が決定するため値4の処理は行われません。

一方、Listの場合は値14に対してmap(v*v)の操作を行った結果のListを作成します。作成されたListに対してさらにmap(v+1)の操作, さらにfilter(), take()と続きます。

f:id:scache:20180419000652p:plain:w300f:id:scache:20180419000656p:plain:w300

まとめ

Sequence1つの値に対する操作が全て行われた後、必要なら次のが処理される。
Listすべての値に対して1つの操作が行われた後、必要なら次の操作が行われる

※ Sequenceは実際に値が必要になるまで処理が行われない(遅延される)などの違いもあります。

参考

kotlin.sequences - Kotlin Programming Language

文字列中の変数の値を表示するプラグインを作った

プラグインの概要

Kotlinの文字列中の変数が定数値の場合に、その値を文字列中に表示するプラグインです。
↓の例では$tagの表示がtagの値であるtag nameになります。

f:id:scache:20180404100244g:plain  

プラグイン説明

インストール方法

以下の画像のように、IntellijやAndroidStudioの設定から
PluginsBrowse Repositories... をクリックし、ConstStringPlaceHolderで検索することでインストールができます。

f:id:scache:20180404230859p:plain:w300
f:id:scache:20180404230836p:plain:w300

使い方

定数値を表示するには、文字列中の変数にカーソルを移動した後にCollapse(右クリック→FoldingCollapse)をします。

f:id:scache:20180404235225g:plain
f:id:scache:20180404234946g:plain

複数の値を一度に変換する場合は、範囲を選択後にCollapse All(右クリック→FoldingCollapse All)をします。

f:id:scache:20180405001433g:plain

課題

v0.0.1時点では、

  • Javaコードでは使用できない
  • ファイルを開いた時点ではすべてExpandされた状態になる

の課題があるので今後は上記を改善していきたいです。

data classで配列を使う時はequalsをオーバーライドしよう [Kotlin]

data classとは

data classは以下のようにクラスにdataをつけて宣言することで、
コンストラクタで宣言されたプロパティを使ったequals/hashCode/toString関数などを自動生成してくれます。

data class Book(
    val title: String,
    val page: Int
)

val book1 = Book("book", 100)
val book2 = Book("book", 100)
book1.toString()  // => Book(title=book, page=100)
book1 == book2    // => true

toString()で返って来る文字列にプロパティの値が入っていたり、
==(equalsが呼ばれる)を使った比較ができたりします。

data class内で配列を宣言した場合

↓のようにプロパティに配列型を宣言した場合にも、同様にequals()などの関数が自動で生成されます。

data class Book(
    val title: String,
    val author: Array<String>
)
    
val book1 = Book("book", arrayOf("Alice","Bob"))
val book2 = Book("book", arrayOf("Alice","Bob"))
book1.toString() // Book(title=book, author=[Alice, Bob])
book1 == book2   // false

しかし、先ほどとは違いbook1 == book2の結果がfalseになってしまいます。

なぜ配列を使うと比較がうまくできないのか?

配列を使ったdata classをJavaデコンパイルすると、
Array<String>String[]に、data classのequals内の配列の比較はarray1.equals(array2)と同等のコードになります。

Javaの配列型のequals()メソッドは、要素の比較は行わず配列型のインスタンスが同じかどうかを判定するため、配列内の値が等しいだけではtrueになりません。

対処法

equals()メソッドをオーバーライドしてArrays#equlas()を使うことでこの問題を解決することが可能です。

IntellijやAndroidStudioでKotlinPluginを入れている場合(最近のバージョンでは初めから入っています)は、Inspectionが表示され以下のようなequals()/hashCode()を自動生成することが可能になっています。 f:id:scache:20180321002505p:plain

override fun equals(other: Any?): Boolean {
    if (this === other) return true
    if (javaClass != other?.javaClass) return false
    other as Book
    if (title != other.title) return false
    if (!Arrays.equals(author, other.author)) return false
    return true
}
override fun hashCode(): Int {
    var result = title.hashCode()
    result = 31 * result + Arrays.hashCode(author)
    return result
}

Gradle Kotlin DSLを書くための参考になるサイトやページのメモ

Gradle Kotlin DSL

メリット

  • 1回ビルドできる状態にもっていけばあとはコードの補完が効く
  • Groovyを知らなくてもKotlinで書くことができる

デメリット

  • gradle(Groovy版)からKotlinDSLに直すのがつらい
  • あまり使っている人を見ないので思わぬバグを踏む可能性が高い

公式サイト

github.com

Android関係

DroidKaigi2018にいってきた

セッションについて

去年のDroidKaigiでは、MVP, MVVMを使った実装方法、RxJava, Kotlinなどをどう使うか、のようなすぐに役に立つ系の話が多かったイメージがあります。
しかし今年は、なぜそのような設計にしたのか、あるライブラリや技術がどのように実装されているか、という一歩踏み込んだ話が増えた気がします。このような話はすぐには役に立たないかもしれないですが、アプリ開発に限らず良い影響が出ると個人的には思っています。

また、去年はサンプルコードがKotlinの場合は断りを入れている人が多かったですが、今年は当然のようにKotlinやRxJavaが使われていて、技術の移り変わりを感じました。

特によかったセッション

見たセッションはほとんど良かったが、特に良かったもの

  • 詳解 ViewGroupのレイアウト内部実装 by Hiroyuki Seto
  • ConstraintLayout, now and future
    ConstraintLayout完全に理解した(理解したとは言ってない)
    英語は何を言っているかわからなかったが言いたいことは分かった。今年は英語をがんばる

  • AndroidとCPU
    CPU? ARMとか、Snapdragonが強いんでしょ、程度の知識しかなかったがCPUの種類や特徴について知ることができて、想像以上に良いセッションだった。

  • multi-module-no-susume by Shinnosuke Kugimiya
    すごく良く考えて設計しているなと感じたし、設計に銀の弾丸はないなと改めて思った。作るものに適した設計をできる力をつけていきたい。
    次に個人で作るアプリはマルチモジュールやっていく

アフターパーティとか

同級生や前職でお世話になった人、去年のDroidKaigi以来の人と話せて良かった。
去年も評判が良かったコーヒーを飲みたかったが、普段からコーヒーをあまり飲まないので今年も結局飲まずに終わってしまったのが残念。
企業ブースは時間がなくて半分ぐらいしか回れず。

また来年もDroidKaigiがあるのなら発表したいと思う。
DroidKaigi、最高だった。

Kotlinで型パラメータの型情報を取得する

フレームワークやライブラリの仕様で、Classインスタンスが必要になる場合があります。
例: AndroidIntentを作る場合、jsonを特定の型にパースする場合など

JavaとKotlinではこのような場合にどのような違いがあるのか調べました。

Javaの場合

以下のようにAndroidIntentを作る際にActivityClassを渡す場合

public <T> void startActivity(Context context, Class<T> clazz) {
    context.startActivity(new Intent(context, clazz));
}

startActivity(this, MainActivity.class)

上のコードは問題なく動きますがジェネリクスで型が分かっているのに引数でClassを渡しているのは冗長な気がします。 型パラメータからClassを取得しようとするとどうなるのでしょうか

public <T> void startActivity(Context context) {
    context.startActivity(new Intent(context, T.class));
}

これはコンパイルエラーになります。これ以外の構文でもTからClassを取得することはできません。
よってJavaでは型パラメータからClassを取得することは基本的にはできず、引数としてClassを渡さなければいけないのです。

Kotlinの場合

Kotlinの場合ではどうでしょうか?

Kotlinでは多少制約はありますが型パラメータのメソッド呼び出しやフィールドアクセスが可能です。
上のJavaコードをKotlinで書くと以下のようになります。

inline fun <reified T> startActivity(context: Context) {
    context.startActivity(Intent(context, T::class.java))
}

startActivity<MainActivity>(this)

ポイントは

  • inline関数にする必要がある
  • 型パラメータにreifiedをつける
  • 型引数に Nothingや、List<String>のように型パラメータ付きのクラス(Arrayは除く)は使えない。

また、obj is Tのように型チェックも可能です。これもJavaではできないです。

最後に

今回はreifiedの機能や使い方について調べてみました。
reifiedを使うには制約がありますが、それは何故なのかも調べてみたいと思います。

Kotlinで関数の引数などの条件をチェックする関数

kotlinの標準ライブラリに条件式を満たさなければ例外を出すという動作を行う関数が定義されています。
関数内で引数の条件をチェックする場合などに使うと便利なので紹介したいと思います

IllegalArgumentExceptionを発生させる

引数をチェックするための関数は以下の4つがあります

  • fun require(value: Boolean)
  • fun require(value: Boolean, lazyMessage: () -> Any)
  • fun requireNotNull(value: T?): T
  • fun requireNotNull(value: T?, lazyMessage: () -> Any): T

使用例

fun require(value: Boolean)

fun List<String>.second() {
    require(size >= 2)
    this[1]
}

listOf("a").second()
// => java.lang.IllegalArgumentException: Failed requirement.

fun require(value: Boolean, lazyMessage: () -> Any)
こちらは、例外を投げる時のメッセージを指定することが可能です

fun List<String>.second() {
    require(size >= 2){
        if(size==0) "リストの要素が1つも無いよ"
        else "リストの要素が1つしかないよ"
    }
    this[1]
}

listOf("a").second()
// => java.lang.IllegalArgumentException: リストの要素が1つしかないよ

fun requireNotNull(value: T?): T

fun f(v: Any?): String {
    requireNotNull(v)
    return v.toString()
}

f(null)
// => java.lang.IllegalArgumentException: Required value was null.

f("not null")
// => "not null"

fun requireNotNull(value: T?, lazyMessage: () -> Any): T

fun f(v: Any?): String {
    requireNotNull(v){ "nullじゃない値がほしい" }
    return v.toString()
}

f(null)
// => java.lang.IllegalArgumentException: nullじゃない値がほしい

IllegalStateExceptionを発生させる

なんらかの処理を行う前に関係する状態をチェックするための関数が定義されています

  • fun check(value: Boolean)
  • fun check(value: Boolean, lazyMessage: () -> Any)
  • fun checkNotNull(value: T?): T
  • fun checkNotNull(value: T?, lazyMessage: () -> Any): T

使用例

関数の使い方はrequireとほとんど同じなので1つだけ例をあげておきます

var someState: String? = null
fun end(): String {
    val state = checkNotNull(someState) { "Stateがnullになっている" }
    check(state == "start") { "この処理を実行するにはStateがstartの必要があります" }
    // ...
    return state
}

someState="end"
end()
// => java.lang.IllegalStateException: この処理を実行するにはStateがstartの必要があります

まとめ

requireやcheck関数を使用することで特定の状況でのみ処理が実行されることを保証することができるため、バグを生みにくくコードが読みやすくなることが期待できます。
requireとcheck関数の処理や呼び出し方法はほとんど変わらないため、場面に応じて使い分けると良いでしょう。