LoginSignup
8
10

More than 5 years have passed since last update.

Kotlinを使っているエンジニアがKotlin In Action 『第2部 Kotlinを愛でる』を読んだ時の知見メモ

Posted at

https://qiita.com/takahirom/items/3f04599536f99f8146b8
の続きです。

特に面白かったところを太字にします。

演算子オーバーロードとその他の変換の規約

  • +演算子を使えるようにするには、plusという名前の特別なメソッドを定義すれば良い、その手法を規約(convention)という。(特別なメソッドを定義することで何かの機能が使えるようになる。)
  • なぜインターフェースではなく、規約という特別なものにしたかというと、これであれば拡張関数を生やすことで、既存のJavaクラスを拡張することができるため。
  • operator修飾子をつけることで、たまたま同じメソッド名を使ってしまったわけではないということを明確にしている。(P228)
  • plus(+)とplusAssign(+=)は一般的に同時に実装するべきではない。なぜならイミュータブルなクラスであれば+だけだし、BuilderなどのミュータブルなクラスであればplusAssignだけとなるため(P232)
  • varで宣言されている場合はplusAssignがなくてもplusを自動的に使ってくれる。valだとこれはエラーになる(P233)
    class Score(val value: Int) {
      operator fun plus(score: Score): Score {
        return Score(score.value + value)
      }
    }

    var score = Score(1)
    score += Score(3)
  • 単体の+などの演算子はunaryPlus()が使われる(P234)
  • ++はinc()(P232)
  • operator component1()とかも規約(P245)
  • Mapから分解宣言で取り出せるのはMapに拡張関数でcomponent1とか生やしているから(P247)
  • 関連する属性が動的に変化する場合にexpando objectということがある(mapなどで管理する)(P257)
  • DelegatedPropertyで文字列で指定しなくてもmapから値が取り出せる(P258)
    class Person(val properties: Map<String,String>) {
        val name: String by properties
    }
    println(Person(mapOf("name" to "test")).name)

以下のような拡張関数が生えているので、それが使われる。

@kotlin.internal.InlineOnly
public inline operator fun <V, V1 : V> Map<in String, @Exact V>.getValue(thisRef: Any?, property: KProperty<*>): V1 =
    @Suppress("UNCHECKED_CAST") (getOrImplicitDefault(property.name) as V1)

ここ個人的に面白すぎて、自分でもPreferenceで同じようなの書いてみたりしました。これでPreferenceに書き込んだりできます(Android感強くてごめんなさい)

  operator fun SharedPreferences.getValue(thisRef: Any?, property: KProperty<*>): String? {
    return getString(property.name, null)
  }

  operator fun SharedPreferences.setValue(thisRef: Any?, property: KProperty<*>, value: String?) {
    edit().putString(property.name, value).apply()
  }

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    var test: String? by PreferenceManager.getDefaultSharedPreferences(this)
    println(test)
    test = "test"
    println(test)

高階関数 : 引数と戻り値としてのラムダ

  • 関数型でも引数に名前がつけられる(P266)
    fun funcNamedArg(callback: (code: Int, content: String) -> Unit) {
       callback(200, "Hello, World!")
    }


funcNamedArg { code, content ->  }
  • IntelliJ IDEAはデバッガでのラムダコードへのスマートステッピングをサポートしているので、デバッグできる。たぶんこれはFilterの中とかでどう動くか分かる(P268)

    (ちょっとこの機能がどういうのか探している間に、Kotlin Sequence Debuggerという関係ないけど良さそうなプラグインを見つけた)
    https://plugins.jetbrains.com/plugin/10301-kotlin-sequence-debugger
    image.png

  • 関数型の引数を持っていてそれがnullableなときはcallback?.invoke()とすることで安全呼び出しして楽できる (P271)

  • ラムダを引数に取る関数以外では、常にinline関数を使えばパフォーマンスが改善するというわけではない。JVMはすでに強力なインライン展開サポートを提供しているため。(P283)

  • inline化するかどうかにはコードサイズに注意を払う必要がある。inline関数は呼び出し側にすべてコピーしていくのでバイトコードのサイズが割に合わないことがある。(P283)

  • ラムダがインライン展開されている場合のみ外側の関数からのリターンが使える。(P286)


* ラムダにlabelをつけてローカルリターンできる

fun main(args: Array<String>) {
    fetch label@{ return@label }
}

fun fetch(function: () -> Unit) {
    function()
}
  • returnの時にリターンする場所のルールはfunキーワードを使って宣言された最も近い関数からリターンする。簡単。

アノテーションとリフレクション

  • Kotlinのアノテーションはデフォルトで実行時まで保持されるので、その場合は保持に関して明示的な指定をする必要がない。(P343)
  • KotlinのリフレクションAPIはKotlinのクラスだけに限定されず、任意のJVM言語で書かれたクラスを参照できる。(P347)
  • 実行時にオブジェクトのクラスを取得するにはjavaClass.kotlinで取得する。(P348)
  • リフレクションでKFunctionなどを取得した時にcallを使うと引数の型のチェックがされないので、invokeを使うのが良い。(P350)
  • KFunctionNインターフェースはコンパイラにより生成される型。これによりサイズを減らして引数の個数に対する制限を回避している。(P350)

DSLの作成

  • 洗練されたAPIの意味

    • コードで何が起きているかが読み手によって明確になっていること。これは、良い名前と良いコンセプトを選ぶことで達成できます。どの言語でも重要なことです
    • お決まりの記述が最小限で、不必要な構文がなく、コードがきれいに見えること。

まとめ

Kotlin In Actionは本当にいい本です。

8
10
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
8
10