道産子エンジニア

毎週好きなこと書きます。

Kotlinの勉強雑メモ

Kotlinスタートブック -新しいAndroidプログラミング

Kotlinスタートブック -新しいAndroidプログラミング

やろうやろう詐欺をし続けて、結構時間が経ったので本腰を入れていく。kotlinだけでなくAndroidのライブラリ全般で開発がめちゃくちゃ早いので、一年前の本なのに読んでみたはいいけどかなりの部分がもう古い情報になっているので注意。読めば「なるほど〜」となるが手を動かすのが重要。サンプルアプリを作るところまでがこの本のゴールなので作ったはいいが、Dagger周りで仕様変更が大きすぎて公開するにはもう少し直さないといけない。


※2017/08/03追記 シンプルにDagger2.11を使って実現したので、サンプルを公開した。サブコンポーネントはややこしいので使ってない。

github.com


基本的には冗長なnullチェックがなくなるってインパクトが強いけど、正確には nullをしっかり区別して、簡潔に書ける ところが素晴らしいなと感じる。 普通に書くだけでnullを意識しないとコンパイルできないのが強力だよなぁ。

  • new;(セミコロン)は不要
  • コンストラクタの引数にvarvalをつけるとgettersetterが生える
  • require関数で初期値違反を検出する
  • tailrecは末尾再帰(tail recursive)
  • 演算子オーバーロードが便利
  • 拡張関数最高
  • kotlinc コマンドでREPL起動
  • 原則としてvalを使い、再代入不可にする
  • ${変数名}で値参照(同じリテラルなら中かっこ省略可能)
  • 拡張関数の思いつき:toggle(Visibilityの反転)
  • +=とか-+とかで要素を追加、削除できるのよい
  • listOfで不変長リスト,mutableListOfで可変長リスト
  • toPairオブジェクトの作成が可能 -> val pair = ("key" to value)
  • MapにはPariオブジェクトを渡す必要がある
  • StringはtoRegexで正規表現に変換可能
  • 基底クラスはAny。クラスとして定義したものは全てAnyのサブクラス
  • open修飾子で継承可能にできる
  • ===で等価かどうか判別
  • a.plus(b)a plus bのようにする(中値呼び出し)にするにはinfix修飾子をメソッドにつける
  • rangeの代わりに[1..10]で使っている..step関数のinfix
  • 内部クラスにするにはinner修飾子をつける(アクセスが外側のオブジェクト経由でないとできなくなる)
  • 可変長引数にするにはvarargで指定し、一つに関数に一つのみ
  • 返り値のない関数はUnit
  • ::で関数オブジェクトを取得(そしてそれを変数に代入やメソッドに渡せる! Function)
  • 関数オブジェクトを直接生成するのがラムダ式->。中かっこ必須{}
val square: (Int) -> Int = { i: Int ->
  i * i
}
//型推論による省略式
val square = { i: Int ->
  i * i
}
//引数が一つのときのみ`it`が使える
val square: (Int) -> Int = {
  it * it
}

リスト系

val range: IntRange = 12..15

inで存在確認

>>> 5 in range
true
>>> 5 !in range
false

Listオブジェクトへの変換

(1..5).toList()

反転

//(1..5).reversed().toList()
(5 downTo 1).toList()

等間隔

(0..100 step 25).toList() //[0,25,50,75,100]


オブジェクトと型クラス

object式

値と関数を持つオブジェクト

val bucket = object {
  val capacity: Int = 5
  val quantity: Int = 0
  fun fill() {
    quantity = capacity
  }
  fun drainAway() {
    quantity = 0
  }
  fun printQuantity() {
    pringln(quantity)
  }
}

※単体では型がないため名前参照ができない

interface

型を与えることができる。名前参照が可能になる

interface Bucket {
  fun fill()
  fun drainAway()
  fun pourTo(that: Bucket)

  fun getCapacity(): Int
  fun getQuantity(): Int
  fun setQuantity(quantity: Int)
}
fun createBucket(capacity: Int): Bucket = object : Bucket {
  var _quantity: Int = 0
  override fun fill() {
    setQuantity(getCapacity)
  }

  override fun drainAway() {
    setQuantity(0)
  }

  //バケツに注ぐ
  override fun pourTo(that: Bucket) {
    val thatVacuity = that.getCapacity() - that.getQuantity()
    if(getQuantity() <= thatVacuity) {
      that.setQuantity(that.getQuantity() + getQuantity())
      drainAway()
    } else {
      that.fill()
      setQuantity(getQuantity() - thatVacuity)
    }
  }

  override fun getCapacity(): Int = capacity
  override fun getQuantity(): Int = _quantity
  override fun setQuantity(quantity: Int) {
    _quantity = quantity
  }
}

SAM interface

Single Abstract Method interfaceはラムダに変換できる

  • Runnable : run()
  • View.OnClickListener : onClick(View v)

などを次のように書ける。

view.setOnClickListener { v ->
  //do something
}

クラス

バッキングフィールド

カスタムゲッター

プロパティにカスタムゲッターを設定することで

class Person {
  var name: String = "" //プロパティ
  var age: Int = 0 //プロパティ
  val nameLength: Int //バッキングフィールドを持たないプロパティ
    get() = this.name.length //カスタムゲッター
}

以上のようにすると

val hanako = Person()
hanako.name = "はなこ"
println(hanako.nameLength) // =3

となる。

カスタムセッター

プロパティへ

class Person {
    var name: String = ""
        set(value) { //カスタムセッター
            println("${value}がセットされました")
            field = value //fieldがbucking
        }
    var age: Int = 0
    val nameLen: Int
        get() = this.name.length
}

とすることで、値がセットされた時に処理を行える。

val test = Person()
test.name = "test" //testがセットされましたを出力
println(test.nameLen) // =4

コンストラクタ

constructorキーワードでコンストラクタに引数を与えれる。それらをプロパティにセットする。

class Rational constructor(n: Int, d: Int) {
  val numerator: Int = n
  val denominator: Int = d
}

普通はconstructorキーワードを省略して、引数に直接valvarをつけると同じ意味になる。

class Rational (val numerator: Int, val denominator: Int)

デフォルト値も設定可能。 デフォルト値を設定することでメンバ変数の初期化を書かなくてよい。

class Rational (val numerator: Int, val denominator: Int = 1)

イニシャライザも追加可能。

class Rational (val numerator: Int, val denominator: Int = 1) {
  init {
    require(denominator != 0)
  }
}

requireは条件を満たさない時に例外を投げる関数


ジェネリクス

基本

class Container<T>(var value: T)

型制約

//単数
class Container<T: Hoge>(var value: T)
//複数
class Container<T> where T : Hoge, T : Fuga

Null safety

いろんな書き方や制約のつけ方があるので、導入前にチームで話した方がいい。

最近lintも増えつつある。

github.com

チームでコード規約を作っていくのもいいと思う。

github.com

nullを代入

val s: String = null //コンパイルエラー
val s: String? = Null

nullの可能性がある変数の参照

s.toUpperCase()//コンパイルエラー
s?.toUpperCase()//nullでなければ実行される

引数がNotNullでないといけないメソッドなどはletを使う

fun square(i: Int): Int = i * i

val a: Int? = 5
val aSquare = a?.let(::square)//引数が一つなので関数オブジェクトを渡せる

!!は使うな

val hoge: String? = null
val fuga: String = requireNotNull(hoge, {"ぬるぽ"}) //NPE

エルビス演算子?:

nullの時はデフォルトを使う的な実装をするときに使う

val foo: String? = "hello"
(foo ?: "default").toUpperCase() //>>>HELLO
val hoge: String? = null
(hoge ?: "default").toUpperCase() //>>>DEFAULT

安全キャスト

ClassCastExceptionを防ぐ

val str: Any = "ぺろーん"
str as String //ok
str as Int // ClassCastException
str as? Int //return null

その他

演算子オーバーロード

+.plus()と等価のようにメソッドに演算子を対応づけられる。
対応させるにはoperator修飾子をつける

class MyInt(val value: Int) {
  operator fun times(that: MyInt): MyInt = MyInt(value * that.value)
}

val product = MyInt(3) * MyInt(5)
product.value //=15

分解宣言

pairfirstsecondを別々に宣言してアクセスできるようにする方法

val (name, age) = Pair("Yuichi", 27)
println(name) //Yuichi
println(age) //27

データクラス

クラス宣言にdataをつけるとAnyクラスが持つメソッドの適切な実装が行われる。 これにより、toStringでデータを表示したり、equalsでの中身が同じかどうかの比較、 copyでJavaのcloneと同じように、ディープコピーが可能。

class User(val id: Long, val name: String)
User(1, "you") == User(1, "you") //false
---
data class User(val id: Long, val name: String)
User(1,"you") == User(1, "you") //true

シングルトンにするには

classではなくobjectを使うと実現可能

object Me {
  fun hello() {
    println("Hello world")
  }
}
  • クラス内に作るときはcompanion object修飾子をつける
  • コンパニオンオブジェクトは1クラスに1つ

enum

enum class SomeType(val num: Int) {
  S(100) {
    override fun message(): String = "small"
  },
  M(300) {
    override fun message(): String = "medium"  
  },
  L(500) {
    override fun message(): String = "large"
  };
  abstruct fun message(): String
}
SomeType.S.num //100
SomeType.M.message() //medium
val types: Array<SomeType> = SomeType.values()
types.toList() //[S, M, L]
SomeType.valueOf("S") //S
SomeType.S.name //S
SomeType.values().map { type -> type.ordinal } //[0, 1, 2]

以上。

これから助走読本を読みつつ、公式が用意している try! kotlinのkoansも解く。

try.kotlinlang.org

余談だけどkoansって中国の公案のことで、いわゆる禅問答のことだ。

公案 - Wikipedia

悟りを開いていこう。