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

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)

```kotlin
fun max(a: Int, b: Int) = if(a > b) a else b
```

* データのみが存在し、実装コードがないクラスは値オブジェクトと呼ばれる(P30)  
(存在は知っていたけどKotlin In Actionで言及があるとは思わなかった)  

```kotlin
class Person(val name: String)
```

* **プロパティ vs 関数で違いがあるのは可読性のみ、あるクラスの特徴(property)を表現したい場合はそれをプロパティとして宣言する。**(P33)

```kotlin
val isSquare: Boolean
  get() {
     ...
  }
```

* Kotlinではクラス名とファイル名を一緒にする必要がなく、またパッケージと同じフォルダ構成にする必要はない(P35)

* 同じフォルダ構成にする必要はないが、殆どの場合Javaのディレクトリ構成に従って、ファイルを配置するのが良い。クラスが小さいときは複数のクラスを同じクラスに含めるのをためらうべきではない。(P35)
* enumはソフトキーワードと呼ばれ、classの直前に置かれたときだけ特別な意味を持つ(P36)
* list.withIndex() (P50)

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

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

* **Kotlinでは名前の一部にUtilがつくようなクラスは必要ない、そのかわりにソースファイルのトップレベル、つまりクラスの外側に関数を直接書くこと(トップレベル関数)ができる。**(P65)
* Kotlinの標準ライブラリのすべてを学ぶ必要はなく、IDEのコード補完機能が利用可能な全関数を示してくれる。(P76)
* スプレッド演算子でmainで引数をlistに取り出す(P77)

```kotlin
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)   

```kotlin
"""(.*)/..."""
```

### 第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)

```kotlin
class Person(val name: String){
  companion object : JSONFactory<Person> {
    override ...
  }
}

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

loadFromJSON(Person)
```

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

×

```kotlin
{ println(42) }()
```

○

```kotlin
run { println(42) }
```

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

* ラムダのitについて、乱用するべきではなく、特にネストされたラムダの場合は、明示的に各ラムダの引数を定義したほうが良い。また文脈により判断がつきにくい場合は明示的に宣言することが有用。(P142)
* 変数のキャプチャ(P145)  
Javaだとfinalじゃないとラムダの中からアクセスできなかったのをどうやってアクセスしているか？

```kotlin
fun main(args: Array<String>) {
    var i = 0
    val inc = { i++ }
    inc()
}
```

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

```java
   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)

```kotlin
data class Person(val name: String)
val createPerson = ::Person
val p = createPerson("Alice")

```

* コレクション操作で適切な関数を利用する(P153)  
**filterとsizeを使った場合中間コレクションが生成されてしまうが、countメソッドは要素自体は保持せず数だけ保持するため効率的。**

```kotlin
// ×
    list.filter { it.isNotBlank() }.size
// ○
    list.count { it.isNotBlank() }
```

* flatMapはmapしてflat化したもの(P155)  
(flatMap自体は知っていたんですが、そう言われるとそうかという感じです。)

```kotlin
    val list = listOf("aaa","test")

    println(list.map { it.toList() }.flatten() == list.flatMap { it.toList() })
=> true
```

* 原則として大きなコレクションに対してチェインした操作を行うときは、シーケンスを利用する(P158)  
* generateSequence()は階層構造があるときに便利に使える(P162)

```kotlin
fun File.isInsideHiddenDirectory() = generateSequence(this) { it.parentFile }.any { it.isHidden }
```

* Kotlinでは厳格な関数型があるため、引数としてラムダを取るときは関数型インターフェイス(一つだけメソッドのあるinterface)ではなく、関数型を引数の型として使う必要がある。(P164)
* **Javaにラムダを渡すときにラムダの外にあるものにアクセスしない場合はstaticなフィールドにラムダが作られて、それが利用されるので、何度メソッドが呼ばれてもインスタンスが増えたりしない。objectキーワードを使うかラムダの外にアクセスする場合はnew Runnable(){...という感じでメソッドを呼ぶたびに作られる。** (P165)
* リスナをリスナ内で削除できるようにしたい場合はthisが参照できないので匿名オブジェクトを使う(P168)

×

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

○

```kotlin
        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)

```kotlin
person.company!!.address!!.country
```
* ifチェック vs let(P191)  
letを使うと余分な変数が必要ない。

```kotlin
    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(想定外の挙動をする)

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

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

```koltin
    val list = listOf(
            42L,
            2L
    )
    val x = 2
    println(x in list)
// コンパイルエラー！
```

```kotlin
    val list = listOf(
            42L,
            2L
    )
    val x = 2
    println(x.toLong() in list)
=> true
```

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

```kotlin
    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