Kotlinの型を知る ~後編~ プラットフォーム型とCollections

# はじめに

この記事は[Kotlin Advent Calendar](https://qiita.com/advent-calendar/2017/kotlin) 最終日の記事です。

今年はGoogle I/OでKotlinがAndroidの公式開発言語の1つになり、初めてのカンファレンスであるKotlin Confが開かれるなど、Kotlinの普及がますます進みそうなことを感じた1年になりました。
個人的には[Kotlin In Action](https://www.amazon.co.jp/dp/4839961743/)の翻訳に携わり、Kotlinの普及へ微力ながら貢献出来たのではないかと思います
(翻訳についてまとめた記事もあります：[技術書を翻訳する技術](https://qiita.com/satorufujiwara/items/9485904d7ed464d97c71))。

本記事ではそのKotlin In Actionの著者の1人であるSvetlana Isakova氏のKotlin Confでのセッション「[Kotlin Types : Exposed](https://www.youtube.com/watch?v=Uizh2WlJtnk)」の内容と、Kotlin In Actionの6章の内容を元に「Kotlinの型」についてまとめた内容になります。
アジェンダは以下のとおりです。

- KotlinとJavaの型
- Any, Unit, Nothing, void
- **Null許容型/Null非許容型とプラットフォーム型**
- **Collection型**

「KotlinとJavaの型」「Any, Unit, Nothing, void」については「[Kotlinの型を知る ~前編~ ](https://qiita.com/AAkira/items/16ae2e9c0f6073e0e983)」をご覧ください。
本記事は後半の「Null許容型/Null非許容型とプラットフォーム型」、「Collection型」についてです。

# Null許容型/Null非許容型とプラットフォーム型

ご存知のとおり、Kotlinの型システムでは**Null許容型(Nullable)**と、**Null非許容型(Non-Null)**が区別されています。
これはもちろん「NullPointerException(NPE)」を防ぐためであり、Kotlinを導入する大きなメリットの1つでしょう。
一方で、Kotlinは「Javaとの相互運用性」も使命としている言語であるので、KotlinをJavaと混在させて使うことも多々あります。
まず、KotlinとJavaを混在させた場合のNull許容型/Null非許容型について見ていきます。

## プラットフォーム型が出来た理由

![nullable_annotaition.png](https://qiita-image-store.s3.amazonaws.com/0/36413/59c94ef6-ed4c-e692-a267-bd4940a2a843.png)
(引用元 : [KotlinConf 2017 - Kotlin Types: Exposed by Svetlana Isakova](https://www.youtube.com/watch?v=Uizh2WlJtnk))

Javaに`@Nullable`や`@NonNull`などのアノテーションがついていた場合、Kotlinにおいてはそれらはそれぞれ、Null許容型、Null非許容型として扱われます。この場合は問題ありません。
Javaにこれらのアノテーションがついていない場合にどうするか？を考える必要があります。

KotlinにおいてはアノテーションのないJavaの型は「**プラットフォーム型(Platfrom Type)**」として扱われます。
定義としては「**Null許容についての情報がない型(Unknown Nullability)**」のことです。
この型はJavaの型と全く同じで、この型をNull許容型として扱うか、Null非許容型として扱うかは、実装者の責務になっています。

プラットフォーム型は`String!`のように型の名前の後ろに`!`をつけて表現されますが、

```Kotlin
val s : String! = session.description
```
などと定義することは出来ず、以下のようにエラーメッセージなどに出てくるのみです。

![Screen Shot 2017-12-23 at 4.17.35 PM.png](https://qiita-image-store.s3.amazonaws.com/0/36413/c7bf16b6-da4b-f607-4d25-a34be2270489.png)

なぜわざざわこのような特別な型を用意したのか？
その理由を知るために、仮に「Javaから来た型を全てNull許容型として扱う」ことを考えてみます。
この方法はNullPointerExceptionを防ぐことが出来るので、「一番安全な方法」のように思います。

```Kotlin
val i : Int? = session.description?.length // Int?は扱いにくい
val i : Int = session.description?.length ?: 0 // 使うたびにNullチェック
val i : Int = session.description?.length!! // JavaからのdescriptionがNullでないことが分かっていればこれが楽
```

しかし、実際は上のように使う度にNullチェックするようなことが起こり、多くの場合は非Null表明(`!!`)を多用することになるでしょう。
Javaから来た型をどう扱うかは以下の3つの中から2つだけを選択する必要があって、

- Null安全(NullPointerExceptionが発生しない)
- 便利さ
- Javaとの相互運用性

Kotlinの言語設計者は「Null安全」を諦め、下の2つを選択しました。

- ~~Null安全(NullPointerExceptionが発生しない)~~
- 便利さ
- Javaとの相互運用性

以下のような場合、NullPointerExceptionが発生します。

```Kotlin
//Java
public class Session {
  public String getDescription() {
    return null;
  }
}

//Kotlin
val session = Session()
val description = session.description
println(description.length) // NullPointerException
```

「Null安全」を諦めたとはいえ、NPEを防ぐための工夫はされています。
それについては次節に書きます。

また、Kotlin In Actionには「Javaから来た型を全てNull許容型として扱う」ことの問題点として、ジェネリクスでの例も示されています。

>ジェネリクスを使ったときは、さらに悪いことになります。例えば、Java からもたらされる全ての ArrayList<String> は、Kotlin においては ArrayList<String?>? となり、 参照する際は必ずnullチェックをするかキャストを使うこととなります。これは安全であることのメリットを無力化してしまいます。そのようなチェックを書くことは非常に面倒なので、Kotlin の設計者は実用的な方式を選択し、Java からもたらされる値を正しく扱うのは開発者の責務であるということにしました。
>
>(「Kotlin イン・アクション」P199 6.1.11節)

## NullPointerExceptionを防ぐ方法

Javaから来た型はプラットフォーム型として扱われるため、Null許容についての情報がなく、NPEを完全に防ぐことは出来ません。
しかしながら以下のような方法でNPEの可能性を減らすことが出来ます。

- Javaのコードにアノテーションをつける
- Kotlinで扱うときに型を明示的にする

それぞれの方法を見てみましょう。

### Javaのコードにアノテーションをつける

冒頭でも書いたようにJavaのコードに`@NotNull`というアノテーションがついていれば、KotlinのコンパイラはそれをNull非許容型と解釈します。
このようにKotlinのコンパイラが解釈するアノテーションには以下のようなものがあります。

| 定義 | Null非許容型 | Null許容型 |
|---|---|---|
| JetBrains | `org.jetbrains.annotations.NotNull` | `org.jetbrains.annotations.Nullable` |
| Android | `com.android.annotations.NonNull` </br> `android.support.annotation.NonNull` | `com.android.annotations.Nullable` </br> `android.support.annotation.Nullable` |
| JSR-305 |  | `javax.annotation.CheckForNull` </br> `javax.annotation.Nullable` |
| FindBugs |  | `edu.umd.cs.findbugs.annotations.CheckForNull` </br>  `edu.umd.cs.findbugs.annotations.Nullable` |
| Lombok | `lombok.NonNull` | |

対応しているアノテーションはKotln1.1の場合は以下のソースから確認することが出来ます。

https://github.com/JetBrains/kotlin/blob/1.1.0/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/JvmAnnotationNames.kt

また、JSR-305の`@ParametersAreNonnullByDefault`というアノテーションを仕えば、`@Nullable`のついてない引数全てを`@NonNull`(Null非許容型)として扱うことができます。
こちらについては以下のブログの記事に詳しく書かれています。

[Yukiの枝折 : Android: デフォルトで@NonNull扱いにする](http://yuki312.blogspot.jp/2017/11/nonnull.html)

### Kotlinで扱うときに型を明示的にする

次の方法はKotlinがJavaからの型を扱う場合に、すぐにその型を明示する、という方法です。

```Kotlin
//Java
public class Session {
  public String getDescription() {
    return null;
  }
}

//Kotlin
val session = Session()
val description = session.description
println(description.length) // NullPointerException
```

上の例の場合、`description`の型は明示されていないので、その型はプラットフォーム型である`String!`型となります。
この場合はランタイム時に`description.length`を呼び出すタイミングで`NullPointerException`が発生します。

ではこのコードのKotlin側を次のように書き換えた場合はどうなるでしょう？
違いは2行目の`String`という型を明記した部分だけです。

```Kotlin
val session = Session()
val description : String = session.description
println(description.length) // NullPointerException?
```

この場合もランタイム時にエラーが発生しますが、そのエラーは「`session.description must not be null`」というメッセージのついた`IllegalStateException`になります。
`NullPointerException`ではなく、`IllegalStateException`になる理由はこのコードのバイトコードをJavaへとデコンパイルするとすぐに分かります。

```Java
Session session = new Session();
String var10000 = session.getDescription();
Intrinsics.checkExpressionValueIsNotNull(var10000, "session.description");
String description = var10000;
```

中間の変数(ここでは`var10000`)にJavaからの値を一度代入し、`Intrinsics.checkExpressionValueIsNotNull`というメソッドを使って、そのNullチェックを行っています。
今回の例ではJavaからの値を`description.length`のようにすぐに使っていましたが、そうではなく、関数呼び出しが連続する場合などにおいても、「JavaからKotlinへの境界線でNullチェックを行う」ことで、間違った箇所を特定しやすくしています。

# Collection型

Kotlinには**読み取り専用コレクション(Read-only Collection)**と**ミュータブルコレクション(Mutable Collection)**の2種類のコレクションが用意されています。
Kotlinのコレクションを扱う際に最も重要なのは、

**読取り専用コレクションはイミュータブルではない**

ということです。
イミュータブルコレクションは、以下のリポジトリにプロトタイプが公開されており、

- https://github.com/Kotlin/kotlinx.collections.immutable

>イミュータブルコレクションは、Kotlin 標準ライブラリへの追加が予定されています。
>
>(「Kotlin イン・アクション」P215 6.3.2節)

とのことです。

読み取り専用コレクションはイミュータブルではないことを知るために次の例を見てみましょう。

```Kotlin
val mutableList = mutableListOf(1, 2, 3)
val list : List<Int> = mutableList

println(list) // [1, 2, 3]
list.add(4) // Error!
```
変数`list`は読み取り専用コレクションのため`add`という関数を呼び出すことは出来ません。
しかし、同じインスタンスを共有する変数`mutableList`はミュータブルコレクションなので、以下のように`add`を呼び出せます。

```Kotlin
mutableList.add(4)
println(list) // [1, 2, 3, 4]
```
この時、インスタンスを共有する変数`list`の中身を出力すると、`add(4)`の内容が反映されてしまっています。

読み取り専用コレクション`kotlin.List`も`kotlin.MutableList`も、Javaにおいては`java.util.List`として扱われ、さらに内部的には`java.util.ArrayList`が使われています。
継承関係は以下のようになっています。

![Screen Shot 2017-12-23 at 5.59.09 PM.png](https://qiita-image-store.s3.amazonaws.com/0/36413/a99351a5-8d79-dd76-9693-9de51238abf9.png)
(引用元 : [KotlinConf 2017 - Kotlin Types: Exposed by Svetlana Isakova](https://www.youtube.com/watch?v=Uizh2WlJtnk))

さらにJavaの関数にKotlinのリストを渡す場合など、KotlinとJavaとの間でコレクションのクラスをやり取りする場合は、そのコレクションがJava内で変更されるのかどうか？を考える必要があります。

>コレクションを引数にとって、それを Java へと渡す関数を書く場合、呼び出している Java コードがコレクションを変更するかどうかに応じて、引数の型に正しいコレクショ ンの型を使用することは実装者の責務となります。
>
>(「Kotlin イン・アクション」P218 6.3.3節)

これはNull許容性に関するプラットフォーム型の考え方と似ています。

# まとめ

- KotlinのNull許容は、Null安全と便利さとの間でバランスのとれた妥協をしている
- Kotlinの読み取り専用コレクションはイミュータブルコレクションではない

2017年はKotlinが非常に盛り上がった年でした。来年はさらに普及が進み、Kotlinが当たり前になることでしょう！

今年も一年お疲れさまでした。
Have a Nice Kotlin!

# 関連記事

- [Kotlinの型を知る ~前編~](https://qiita.com/AAkira/items/16ae2e9c0f6073e0e983)