LoginSignup
24
24

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-08-02

Kotlin In Actionはめっちゃいい本です。本当におすすめです。

こちらから購入できます。
https://book.mynavi.jp/ec/products/detail/id=78137

このQiitaはKotilnの基本的な文法とかは分かっている(つもりになっている)人向けです。
自分が勉強になった所だけをメモしています。

Kotlin In Actionは第1部『Kotlinを知る』と第2部『Kotlinを愛でる』からできています。
今回は『第1部 Kotlinを知る』について自分が学びがあった部分を見ていきます。

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

第2章 Kotlinの基本

  • 型推論により式本体を持つ関数は戻り値型を省略できる。(P25)
fun max(a: Int, b: Int) = if(a > b) a else b
  • データのみが存在し、実装コードがないクラスは値オブジェクトと呼ばれる(P30)
    (存在は知っていたけどKotlin In Actionで言及があるとは思わなかった)
class Person(val name: String)
  • プロパティ vs 関数で違いがあるのは可読性のみ、あるクラスの特徴(property)を表現したい場合はそれをプロパティとして宣言する。(P33)
val isSquare: Boolean
  get() {
     ...
  }
  • Kotlinではクラス名とファイル名を一緒にする必要がなく、またパッケージと同じフォルダ構成にする必要はない(P35)

  • 同じフォルダ構成にする必要はないが、殆どの場合Javaのディレクトリ構成に従って、ファイルを配置するのが良い。クラスが小さいときは複数のクラスを同じクラスに含めるのをためらうべきではない。(P35)

  • enumはソフトキーワードと呼ばれ、classの直前に置かれたときだけ特別な意味を持つ(P36)

  • list.withIndex() (P50)

for ((index, element) in list.withIndex()) {

第3章 関数の定義と呼び出し

  • Kotlinでは名前の一部にUtilがつくようなクラスは必要ない、そのかわりにソースファイルのトップレベル、つまりクラスの外側に関数を直接書くこと(トップレベル関数)ができる。(P65)
  • Kotlinの標準ライブラリのすべてを学ぶ必要はなく、IDEのコード補完機能が利用可能な全関数を示してくれる。(P76)
  • スプレッド演算子でmainで引数をlistに取り出す(P77)
fun main(args: Array<String>) {
  val list = listOf("args:", *args)
...
  • KotlinのStringの拡張関数でパスを解析する(P82)
val directory = path.substringBeforeLast("/")
val fullName = path.substringAfterLast("/")
val fileName = fullName.substringBeforeLast(".")
val extension ...
  • 正規表現のエスケープはトリプルクオート文字列を使うとエスケープが楽(P82)
"""(.*)/..."""

第4章 クラス、オブジェクト、インターフェイス

  • Kotlinではoverride修飾子を付けないとオーバーライドできない。この制限によって予期せず同じ名前のメソッドをオーバーライドしてしまう事態を回避することができる(P91)
  • openをつけないと継承できないのは"壊れやすい基底クラス"と呼ばれる問題に対応して、『Effective Java』の「継承のために設計及び文書化する。でなければ継承を禁止する」という考え方に沿って考えられている。(P93)
  • openクラスじゃないとスマートキャストが使われやすい (P94)
    valでかつカスタムアクセサを持たないことがスマートキャストを使う条件になるが、openだとオーバーライドできて、カスタムアクセサを作られる可能性があるため
  • Javaでは外部のコードが同じパッケージ名でクラスを定義すればカプセル化を破れたのをinternalであれば本物のカプセル化を実現できる。(P96)
  • sealedインターフェースを宣言することができないのは、Javaからこのインターフェースを宣言することを防げないから。(classならprivateコンストラクタが使えるため) (P102)
  • プロパティのバッキングフィールドはバッキングフィールドへの明示的な参照があるかデフォルトアクセサの実装の場合にコンパイラによって作られる。(P113)
  • Kotlinではstaticメソッドがない、殆どの場合はトップレベル関数を使うことが推奨される。しかしトップレベル関数ではクラスのprivateにアクセスできないので、インスタンス生成のような場合にコンパニオンオブジェクトを使う。(P126)
  • コンパニオンオブジェクトをJSONFactoryなど継承させて利用できる(P130)
class Person(val name: String){
  companion object : JSONFactory<Person> {
    override ...
  }
}

fun loadFromJSON<T>(factory: JSONFactory<T>): T{
...
}

loadFromJSON(Person)
  • コードブロック内に処理を入れる場合はラムダを直接呼び出すと読みにくいのでrunを使う(P139)

×

{ println(42) }()

run { println(42) }

第5章 ラムダを使ったプログラミング

  • ラムダのitについて、乱用するべきではなく、特にネストされたラムダの場合は、明示的に各ラムダの引数を定義したほうが良い。また文脈により判断がつきにくい場合は明示的に宣言することが有用。(P142)
  • 変数のキャプチャ(P145)
    Javaだとfinalじゃないとラムダの中からアクセスできなかったのをどうやってアクセスしているか?
fun main(args: Array<String>) {
    var i = 0
    val inc = { i++ }
    inc()
}

IntRefみたいなクラスで包んでそれをfinalで宣言する(デコンパイル結果)

   public static final void main(@NotNull String[] args) {
      Intrinsics.checkParameterIsNotNull(args, "args");
      final IntRef i = new IntRef();
      i.element = 0;
      Function0 inc = (Function0)(new Function0() {
         // $FF: synthetic method
         // $FF: bridge method
         public Object invoke() {
            return this.invoke();
         }

         public final int invoke() {
            int var1 = i.element++;
            return var1;
         }
      });
      inc.invoke();
   }
  • コンストラクタ参照(P147)
data class Person(val name: String)
val createPerson = ::Person
val p = createPerson("Alice")

  • コレクション操作で適切な関数を利用する(P153)
    filterとsizeを使った場合中間コレクションが生成されてしまうが、countメソッドは要素自体は保持せず数だけ保持するため効率的。
// ×
    list.filter { it.isNotBlank() }.size
// ○
    list.count { it.isNotBlank() }
  • flatMapはmapしてflat化したもの(P155)
    (flatMap自体は知っていたんですが、そう言われるとそうかという感じです。)
    val list = listOf("aaa","test")

    println(list.map { it.toList() }.flatten() == list.flatMap { it.toList() })
=> true
  • 原則として大きなコレクションに対してチェインした操作を行うときは、シーケンスを利用する(P158)
  • generateSequence()は階層構造があるときに便利に使える(P162)
fun File.isInsideHiddenDirectory() = generateSequence(this) { it.parentFile }.any { it.isHidden }
  • Kotlinでは厳格な関数型があるため、引数としてラムダを取るときは関数型インターフェイス(一つだけメソッドのあるinterface)ではなく、関数型を引数の型として使う必要がある。(P164)
  • Javaにラムダを渡すときにラムダの外にあるものにアクセスしない場合はstaticなフィールドにラムダが作られて、それが利用されるので、何度メソッドが呼ばれてもインスタンスが増えたりしない。objectキーワードを使うかラムダの外にアクセスする場合はnew Runnable(){...という感じでメソッドを呼ぶたびに作られる。 (P165)
  • リスナをリスナ内で削除できるようにしたい場合はthisが参照できないので匿名オブジェクトを使う(P168)

×

        viewTreeObserver.addOnGlobalLayoutListener{
            // thisがこれを取り囲んでいるクラスになるのでリスナを参照できない
            viewTreeObserver.removeOnGlobalLayoutListener(this)
        }

        viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
            override fun onGlobalLayout() {
                viewTreeObserver.removeOnGlobalLayoutListener(this)
            }
        })
  • buildStringを使って文字列を作る(P173)
    buildString {
        for (it in 'A'..'Z') {
            append(it)
        }
    }

第6章 Kotlinの型システム

  • 非null表明: !! "最も単純で最も怠惰なツールです" (P186)
  • 1行に複数の非null表明をしてはいけない。それをするとスタックトレースは行番号を示してどれがnullか分からない。(P188)
person.company!!.address!!.country
  • ifチェック vs let(P191)
    letを使うと余分な変数が必要ない。
    val person: Person? = getPerson()
    if(person != null) sendEmailTo(person.email)

    getPerson()?.let{
        sendEmailTo(it.email)
    }
  • プラットフォーム型が存在する理由はもし全部nullableにすると、例えばジェネリクスのときにもう最悪な感じになるArrayList<String?>?。実用的な方式を選択し、Javaからの値を正しく扱うのは開発者の責務とした。 (P199)
  • 基本的にKotlinのIntはプリミティブ型のintにコンパイルされるが、コレクションに入れるときだけ制約のためにjava.lang.Integerのインスタンスとして保持される。(P202)
  • プリミティブのnull許容型はそれに対応するラッパークラス(java.lang.Integerなど)へとコンパイルされる(P203)
  • プリミティブ型の大きなコレクションを効率的に保持するにはTrove4Jなどのサードパーティ製のライブラリを使用する。(P204)
  • KotlinではIntとLongなどが自動的に変換されない場合がある、そのおかげでJavaだと起こる予想外の事態が起こらない(P205)

Java(想定外の挙動をする)

        ArrayList<Long> list = new ArrayList<>();
        list.add(42L);
        list.add(2L);
        System.out.println(list.contains(2));
=> false

Kotlin
Kotlinはコンパイルエラーになって守ってくれる。

    val list = listOf(
            42L,
            2L
    )
    val x = 2
    println(x in list)
// コンパイルエラー!
    val list = listOf(
            42L,
            2L
    )
    val x = 2
    println(x.toLong() in list)
=> true

しかも引数として数値を渡したりする場合は自動的に変換してくれる

    val list = listOf(
            42L,
            2L
    )
    println(2 in list)
=> true
  • UnitとVoid違い、Unitを型引数に指定した場合returnを書かなくて良くなる。Voidの場合はreturn nullがいる。(P208)
  • Nothing型は例外を投げる場合だけでなく、無限ループする場合も使われる(P209)
  • Kotlinでは変更可能なコレクションと変更不可のコレクションがあるが、Javaに渡す場合はこれが変更されてしまうことを抑止できない(P217)
  • KotlinのNull非許容のリストであっても、Javaに渡す場合はNullを入れられてしまう(P218)
  • 配列型の型引数は常にオブジェクト型なのでIntを入れようとするとjava.lang.Integer型になる。プリミティブ型の配列を作る必要がある場合はIntArrayなどを使う必要がある。(P222)

まとめ

Kotlin In Actionは本当にいい本です。
今回の知見は自分の経験に基づいたものになるので、たぶん各個人が読むとまた別の知見があると思われるので、読んでいったほうが良いと思います。

続きはこちら
https://qiita.com/takahirom/items/64a8ebb3630c04e71d9b

24
24
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
24
24