Java (Eclipse) で Null 安全なプログラム

# 0. はじめに

Java で毎回毎回 null チェックするの、何とかならないかなぁと思っていたら、Eclipse に Null Analysis という機能があることに気付いた。（IntelliJ の方が先に実装してるっぽいけど）

色々調べたので、Null Analysys の使い方(2018年10月時点)をまとめておく。
ちなみに使っている Eclipse バージョンは 2018-09 (4.9.0)。基本的に Juno (4.2) 以降は Null Analysis が出来るが、いろいろバグがあったりするので最新版の方が良い。

# 1. Null Analysis とは

「いちいち null チェックしたくないよね」を Javaらしくアノテーションを付けて静的解決する手段。詳しくは、 [Eclipse Junoでnull解析を行う](https://qiita.com/yuichielectric/items/8dd08da5058a64335090 "") 参照。

なお、ちゃんと Null Analysis の恩恵を受けるには、Java 8 以降が必要。
Java 8 で実装された JSR 308 の型アノテーションが無いと、あまり恩恵が受けられない。

# 2. どのアノテーションを使うべきか

アノテーションには、Eclipse のもの、IntelliJ IDEA のもの等、似て非なるものが色々あって選択が難しい。

自分の場合、あまり特定 IDE の色を入れたくないので、選択肢としては、
- JSR 305 の ```@javax.annotation.Nonnull```系
- Checker Framework の ```@org.checkerframework.checker.nullness.qual.NonNull``` 系
のどちらかになる。各種アノテーションについては、[Nonnullって、何ですか？](https://takeda-san.hatenablog.com/entry/2018/05/30/235154) が詳しい。

さらにJava 9 以降のモジュールシステムを考慮すると JSR 305 の```@javax.annotation.Nonnull``` は使えない。簡単に言うと、```javax.annotation.*``` が Java 標準モジュールとして入っているところに 外部モジュールの ```javax.annotation.Nonnull``` を使おうとすると、パッケージ名前空間が衝突してしまうため。詳しくは [Making JSR 305 Work On Java 9](https://blog.codefx.org/java/jsr-305-java-9/) を参照。

というわけで使うアノテーションは Checker Framework のアノテーション一択になる。

が、Eclipse 上で使うにはひとつ問題がある。
Eclipse では Null Analysis 対象とするパッケージを ```@NonNullByDefault``` アノテーションで判定するのだけど、Checker Framework はそもそもデフォルトが全部 NonNull 扱いなので、それっぽいアノテーションを持っていない(```@DefaultQualifier```はなんか違う。。引数必要だし）。

ここは少し妥協して ```@NonNullByDefault``` は Eclipse のアノテーションを使うようにしよう。後で切り替えるにしても、それほど苦労しないはず。

# 3. Null Analysis の準備
アノーテションを使う時は Maven Central から取ってきましょう。```build.gradle``` 例は以下の通り。

```gradle
// https://mvnrepository.com/artifact/org.checkerframework/checker-qual
implementation group: 'org.checkerframework', name: 'checker-qual', version: '2.5.6'
// https://mvnrepository.com/artifact/org.eclipse.jdt/org.eclipse.jdt.annotation
implementation group: 'org.eclipse.jdt', name: 'org.eclipse.jdt.annotation', version: '2.2.100'

```

Eclipse 標準では Null Analysis はオフなので、コンパイラ設定も必要。やり方は、[Nonnullって、何ですか？](https://takeda-san.hatenablog.com/entry/2018/05/30/235154) の下の方に書いてある。


# 4.コードの書き方

## 4-1. 自作パッケージに ```@NonNullByDefault``` を付ける
基本的に自作パッケージは全部デフォルト NonNull にする。
各自作パッケージの ```package-info.java``` にアノテーションを付けるだけ。

```
@org.eclipse.jdt.annotation.NonNullByDefault
package common.validator;
```

こうすると、以下のようなコードが

```java
public class Foo {
    String a;
    public String bar(String b) {
        String bb = b + b;
        return bb;
    }
}
```

以下のように、全体的に ```@NonNull``` が付いている感じになる。

```java
public class Foo {
    @NonNull String a;  // フィールドは @NonNull

    @NonNull  // 返り値も @NonNull
    public String bar(@NonNull String b) { // 仮引数も @NonNull
        @NonNull String bb = bb + bb;  // ローカル変数も @NonNull
        return bb;
    }
}
```

## 4-2. ```@NonNull``` と ```@Nullable```

```@NonNull``` が付いている箇所には null が入らないことがコンパイル時（Eclipseだとコード記述時リアルタイム）に保証される。
ちなみに、null を入れたい箇所には ```@Nullable``` を付ける。

```@NonNull```が付いている変数に null を入れようとするとコンパイルエラーになるし、

```java
@NonNull String a = null; //コンパイルエラー
```

null じゃなくても、```@Nullable```な変数を```@NonNull```な変数に代入しようとすると、コンパイルエラーになる。

```java
@Nullable String a = "a";
@NonNull String s = a; //コンパイルエラー
```

メソッド呼び出しも同様。上記例クラスのメソッドに ```@Nullable``` な変数を入れようとするとコンパイルエラーになる。

```java
Foo foo = new Foo();
@Nullable String nullableStr = "a";
String s = foo.bar(nullableStr); //ここでコンパイルエラー
```

なお、当然ながら、```@Nullable``` な変数に ```@NonNull``` な変数を代入することは可能。

```java
@NonNull String nonNullStr = "a";
@Nullable String nullableStr = nonNullStr; //OK
```


## 4-3. ```@Nullable``` 変数を ```@NonNull``` にするには

外部とのやり取りでDTOパターンを使ったりすると、どうしても ```@Nullable``` な値が入ってくるので、何らかの手段で ```@NonNull``` に変換する必要がある。
外部ライブラリなども ```@NonNull``` を付けたりしないので、戻り値が ```@Nullable``` 扱いになったりするし。

変換は ```@NonNull``` な変数に代入することで行うが、普通に代入するとコンパイルエラーになるので、コンパイラに ```@Nullable``` だけど null じゃないよ、ということを伝える必要がある。

### 伝え方1 : Java 7 導入の Objects で伝える

```Objects#requireNonNull()``` を通すと、コンパイラが検査済みと判断してくれる。null が来ないことが自明な場合はこの伝え方が簡単。もし null が来ると ```NullPointerException``` になる。

```java
@Nullable String nullableStr = 〜〜;
Objects.requireNonNull(nullableStr); // Null のときは NullPointerException
@NonNull String nonNullStr = nullableStr; //OK
```

Null Analysis を使うと、ちょくちょくこのコードを使うので、Java Editor Templateに入れておくと便利。
自分の場合は、以下の定義を入れている。 ```reqn``` と書いて ```Ctrl+Space``` で、良い感じにコードが入る。

```java
Name                 : ReqNotNull
Context              : Java Statement
Automatically insert : true
Pattern:
${:import (java.util.Objects)}Objects.requireNonNull(${localVar})
```


### 伝え方2 : if文で伝える
null のときに ```NullPointerException``` を出したくない場合はこの方法。

```java
@Nullable String nullableStr = 〜〜;
if (nullableStr != null) { // if 文で伝える
    @NonNull String nonNullStr = nullableStr; //OK
}
```

### その他
他にも伝え方があれば教えて欲しい。


## 4-4. 配列/Listのようなコンテナ型について

#### 配列の場合
配列の場合は少し考慮が必要。（```@NonNullByDefault``` パッケージ配下で）普通にフィールド宣言すると、配列変数自体は ```@NonNull``` になるが、配列の中に入るオブジェクトは ```@Nullable``` になる。

```java
public class Foo {
    String[] args;  // args は NonNull だが、args[0] は Nullable
}
```

中身も ```@NonNull``` にしたい場合は、アノテーションを付ける。

```java
public class Foo {
    @NonNull String[] args;  // args も args[0] も NonNull
}
```

可変長引数を定義するときも同じ。

```java
public class Foo {
    // アノテーションなし
    public void bar(String...args) { 
        // args は NonNull だが、args[0] は Nullable
    }

    // アノテーションあり
    public void baz(@NonNull String...args) {
        // args も args[0] も NonNull
    }
}
```

#### List の場合

List の場合は、配列と違ってコンテナも中身も ```@NonNull``` になる。

```java
public class Foo {
    List<String> args;  // args も args.get(0) も NonNull
}
```

# 5. 不明点
よく分かっていない点のメモ

- JSR 305 アノテーションを使っていたときは ```Object#toString()``` 等の標準メソッドが Nullable で面倒だったのが、Checker Framework + Eclipse アノテーションにすると、NonNull 扱いになっていた。何故だ？

- JSR 305 アノテーションを使っていたときはデフォルト NonNull なのはメソッドの仮引数だけだったが、Checker Framework + Eclipse アノテーションにするとフィールドも戻り値も NonNull になっていた。Eclipse がアノテーション名を見て動作を変えている？


# 6. おわりに

何か知見が増えたら書き足す予定。