KotlinのNullSafetyで悶々としていた話


# はじめに

* 当記事は下記の環境にて動作を確認しています。  
 1. Android Studio 3.0 
 1. Kotlin 1.1.51
 1. compileSdkVersion 25,及び26
 
* 当記事は[Kotlin Advent Calendar 2017](https://qiita.com/advent-calendar/2017/kotlin)の14日目の記事です。
* Javaしか書いたことない人でもわかりやすいように書いた…つもりです。
---

# Null Safetyとは
すでに「Null Safety？知ってるよ！」な方は飛ばしてください。

KotlinはJavaと違い基本的にnullは使用できません。
というのも、基本的にオブジェクトがnon nullableだからです。
例えば、下記のコードはエラーになります。

```Kotlin:test.kt
val sampleStr: String  = "Kotlin"
println(sampleStr)
sampleStr = null
```
![image.png](https://qiita-image-store.s3.amazonaws.com/0/196422/d7ede82c-8919-ec75-03aa-2c2884a6fbe2.png)

では、nullを使用したい場合はどうすればいいのでしょうか？
答えは下記のコードの通りです。

```Kotlin:test.kt
var sampleStr: String?  = "Kotlin"
println(sampleStr)
sampleStr = null
println(sampleStr)
```
![image.png](https://qiita-image-store.s3.amazonaws.com/0/196422/7a0d55d3-e35f-a6ea-e1e8-5c0103194712.png)

String?型はString型とはまったく異なるクラスです。  
これは下記のコードによって確かめることができます。

```Kotlin:test.kt
val sampleStr1: String   = "Kotlin"
var sampleStr2: String?  = null
println(sampleStr1 is Any)
println(sampleStr2 is Any)
```

![image.png](https://qiita-image-store.s3.amazonaws.com/0/196422/3e8cab72-3638-4dfd-2eae-e5c09f7dc959.png)

Anyについては、
[JavaプログラマがKotlinでつまづきがちなところ](https://qiita.com/koher/items/d9411a00986f14683a3f#any-%E3%81%A8-any)
が詳しいです。

---

# ホントにNull Safetyなの？
Kotlinではnullableかどうかが型ではっきりとわかるということがわかりました。
では、コンパイルされるときにnullableかどうかがはっきりわかるので、
これでもうNullPointerExceptionとはお別れですね！うーんスッキリ！

…というわけにも行きませんでした。
下記コードをご覧ください。

```kotlin:extension.kt
fun <T : View> View.bindView(@IdRes id: Int): Lazy<T> = lazy {
    findViewById(id) as T
}
```

```kotlin:SampleView.kt
class SampleView : FrameLayout{
    constructor(context: Context?) : super(context)

    constructor(context: Context?,
                attrs: AttributeSet?) : super(context, attrs)

    constructor(context: Context?,
                attrs: AttributeSet?,
                defStyleAttr: Int) : super(context, attrs, defStyleAttr)

    constructor(context: Context?,
                attrs: AttributeSet?,
                defStyleAttr: Int,
                defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes)

    private val profileImageView: ImageView by bindView(R.id.profile_image_view)
    private val titleTextView   : TextView  by bindView(R.id.title_text_view)
    private val userNameTextView: TextView  by bindView(R.id.user_name_text_view)

    // (以下略)
```

なんと、上記コードはコンパイルを通ってしまいます。
何が問題かというと…。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/196422/70c51ab1-6d26-6901-8af3-126171bb73d4.png)

実はfindViewByIdはnullableです。
的確にIDを指定していれば問題ないのですが、間違った値を入れてしまうと…。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/196422/ddb512d3-ac32-e6b0-30a3-c5d58bf4572d.png)

**あ゛あ゛あ゛あ゛あ゛あ゛あ゛あ゛あ゛あ゛あ゛あ゛！(絶叫)**  
ということになります。

# ホントはNull Safetyじゃないの？


~~本記事では、ここからどのようにNullPointerExceptionを起こせるか実験しつつ記載しようと思ったのですが、~~
実は2017年11月10日現在、既にAndroid SDK側で対応されておりました。

[AndroidOのfindViewByIdの仕様変更](https://qiita.com/kubode/items/c7fe9c091fdf5533d36b)

アドベントカレンダー2017、明日は[kamedon39](https://qiita.com/kamedon39)さんです。

---

# 参考URL

[JavaプログラマがKotlinでつまづきがちなところ](https://qiita.com/koher/items/d9411a00986f14683a3f)

[AndroidOのfindViewByIdの仕様変更](https://qiita.com/kubode/items/c7fe9c091fdf5533d36b)