JavaでKotlinと同じくらい安全なコードを書く（前編）

この記事は`Kotlin Advent Calendar`の1日目の記事です。

https://qiita.com/advent-calendar/2021/kotlin

この記事の内容は完全に`Java`ですが、現在`Java`を書いている人が`Kotlin`を導入するきっかけになることを目指して書きますのでご容赦ください。

# 前書き
`Kotlin`の`Java`に対する大きな利点の1つは、コードの安全性を簡単に担保できることです。
一方、安全性はアピールが難しく、「安全だから`Kotlin`を導入したい！」と主張しても「それ、`Java`でもできるのでは？」「どれ位メリット有るの？」と突っ込まれることも有るでしょう。

実際、自分が経験した限り`Java`でも安全なコードを書く（正確には、書ける環境を整える）ことは可能です。
一方、それを実現するには非常に大きな労力が必要で、現実的には安全性を妥協する部分が出てくると思っています。

このシリーズでは、`Kotlin`の導入材料の1つになれることを目指しながら、`Java`で安全なコードを書く大変さと、`Kotlin`で安全なコードを書く簡単さについて書きます。

前編では、`Java`で安全性を高めるための工夫として、以下の4点を書きます。

- `null`を安全に取り扱う
- メソッド・クラス・変数を基本的に変更できないようにする
- `Collection`を読み取り専用にする
- 一致を安全に判定する

後編では、これらが`Kotlin`でどのような表現になるかと、`Java`で実現した場合と比べた簡単さについて書きます。

# nullを安全に取り扱う
素の`Java`では、プリミティブ型等を除いて`nullability`を指定する文法は無く、`IDE`で得られる警告も限定的です。
この状態では、`null`チェックの抜け漏れで実行時エラーになる危険性が有ります。

```java
// 引数や戻り値のnullabilityはJavadoc等に書く以外の形で表現できない
public String func(String foo, String bar) { /* 略 */ }
```

これを安全に扱うための方法は幾つか考えられますが、ここでは`SpotBugs`の静的解析と、`spotbugs-annotations`から導入される`javax.annotation.Nonnull`/`Nullable`アノテーションを組み合わせる例を紹介します。
また、今後のコード例はこれらのアノテーションを付与した状態にします。

補足として、アノテーション等によって`nullability`を示し、解析や実行時のチェックに用いることは、`Lombok`や`JetBrains Annotations`といったライブラリ・フレームワークでも実現できます。
どのライブラリ・フレームワークを用いるかは、プロジェクトで利用している`IDE`や、ライブラリの導入状況に応じて決めることになります。

## 実際にやってみた様子
以下は`spotbugs-gradle-plugin`と`spotbugs-annotations`を導入した上で、引数と戻り値に`Nonnull`/`Nullable`アノテーションを付与した様子です。
これによって、利用方法が間違っている個所が有れば`SpotBugs`の静的解析により警告が得られるようになります（`SpotBugs`の具体的な利用方法については触れません）。

```java
import javax.annotation.Nonnull;
import javax.annotation.Nullable;

@Nonnull
public String func(@Nonnull String foo, @Nullable String bar) { /* 略 */ }
```

ただし、これを行っていても今回紹介した状態ではコンパイルエラーや実行時エラーは発生しません[^compile-err][^lombok]。
この対策としては、静的解析を`CI`等に組み込むこと[^saratto]、アノテーションの付け忘れをコードレビューで見逃さないよう意識することなどが考えられます。

[^compile-err]: 不正が有ればコンパイルエラーにする方法も探しましたが、軽く調べただけでは見つけられませんでした。
[^lombok]: 例えば`Lombok`の場合、アノテーション処理によってコードに`null`チェックが加えられ、引数が`null`ならその場でエラーになるという効果が有ります。
[^saratto]: サラッと言っていますが、特に後付けで導入する場合、これを`CI`に組み込むのは結構大変な作業になると思います。

また、`IDE`によってはこの警告を`GUI`上にリアルタイムで表示できます。
以下は`Nonnull`指定した引数`foo`にわざと`null`を渡すコードを書き、`Intellij IDEA`で警告を表示した様子です。
メッセージの通り、`null`禁止の引数に`null`を渡していることが警告されています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280230/0b7b7d40-419d-0391-e81f-6318839d3bc0.png)

# メソッド・クラス・変数を基本的に変更できないようにする
`Java`では基本的に全てのものが変更（継承・再代入）可能になっています。
一方、変更が可能ということは、それらの操作を誤って行ってしまう危険性が有るということです。

このため、メソッド・クラス・変数はそれぞれ`final`で修飾して変更できないようにしておく方が安全です。
なお、基本的に`final`にするとしても、結局は手動で修飾するしかない[^defaultfinal]ため、こちらも最後はコードレビューでのチェックを徹底する必要が有ります。

[^defaultfinal]: 全て`final`が基本になるようなコンパイラオプションが無いかと探しましたが、[`Java8`のドキュメント](https://docs.oracle.com/javase/jp/8/docs/technotes/tools/windows/javac.html#BHCGAJDC)を読んでも見つけられませんでした。

ここでは、メソッド・クラス・変数を変更できないようにする方法について紹介します。

## メソッド/クラスを変更できないようにする
メソッドを`final`修飾した場合は継承先クラスでの`override`が、クラスを`final`修飾した場合はクラスそのものの継承が出来なくなります。
これによって意図せぬ継承を防ぐことができ、安全になります。

```java:メソッドをfinal修飾した場合
public class Temp {
    public final void func() {}
}
// コンパイルエラーになる
public class TempEx extends Temp {
    @Override
    public void func() { super.func(); }
}
```

```java:クラスをfinal修飾した場合
public final class Temp {}
// コンパイルエラーになる
public class TempEx extends Temp {}
```

## 変数を変更できないようにする
変数を再代入可能な状態で宣言していると、誤って書き換えてしまう危険性が有ります。
よって、一時変数やフィールドはできるだけ`final`修飾しておいた方が安全です[^construct-field]。

[^construct-field]: フィールドを`final`にする場合、コンストラクタ等で初期化する形にする必要が有ります。

```java
// funcの戻り値をfinalで設定
@Nonnull final String foo = func(bar);
```

`Java`では、他にも引数、拡張`for`文の変数、ラムダ式のレシーバーも再代入可能です。
これらも`final`修飾することで、より安全に取り扱うことができます。

```java:引数のfinal化
// コンパイルエラーにならない
public void f1(@Nonnull Integer arg) { arg = 0; }
// コンパイルエラーになる
public void f2(@Nonnull final Integer arg) { arg = 0; }
```

```java:拡張for文の変数のfinal化
// コンパイルエラーにならない
for (@Nonnull Integer i : list) { i = 0; }
// コンパイルエラーになる
for (@Nonnull final Integer i : list) { i = 0; }
```

```java:ラムダ式のレシーバーのfinal化
// コンパイルエラーにならない
list.forEach(i -> { i = 0; });
// コンパイルエラーになる
list.forEach((@Nonnull final Integer i) -> { i = 0; });
```

### 補足: lombokのvalについて
`Java10`より、`var`で変数を宣言することで型推論が出来るようになりました。
`lombok.val`は、`final var`と同等の変数宣言になります。

乱用すると可読性を損なうため注意が必要ですが、これを用いると少なくともローカル変数は一々`final`修飾しなくて済むため、安全なコードを書きやすくなります。

```java
import javax.annotation.Nonnull;
import lombok.val;

void func(@Nonnull final String str) {
    val foo = str; // final varで宣言したのと同等
    foo = ""; // コンパイルエラーになる
}
```

# Collectionを読み取り専用にする
`Java`の`List`や`Map`は基本的に`mutable`で、値の追加・削除・置き換えといった処理が自由に行えます。
一方、変更を自由に行える状況では、意図しない書き換えでバグが発生する危険性が有ります。

このため、`Collection`は読み取り専用にする方が安全です。

ここでは、`Collections.unmodifiable...`を用いる方法と、`Collection`をラップするクラスを定義する方法を紹介します。

## Collections.unmodifiable...を用いる方法
`java.util.Collections`には、値の変更処理を呼び出した際に`UnsupportedOperationException`を発生させるラッパークラスと、そのインスタンスを生成するファクトリメソッドが定義されています。
例えば`List`を`unmodifiable`にする場合以下のように記述します。

```java
import javax.annotation.Nonnull;
import java.util.Collections;
import java.util.List;

@Nonnull List<Integer> unmodifiableList = Collections.unmodifiableList(list);
```

この方法では`List`などのインターフェースはそのままになるため、外部ライブラリなどで扱いやすいという利点が有ります。
一方、値の変更処理を不用意に呼び出すと実行時エラーになる危険性は残っています。

仮に型レベルで変更を防ぎたい場合、後述する`Collection`をラップするクラスを定義する方法が考えられます。

## Collectionをラップするクラスを定義する方法
こちらは`add`や`remove`といった変更処理を省いたラッパークラスを定義する方法です。
例えば以下のように定義する方法が考えられます。

ライブラリなどに値を渡したり、内容を変更する処理も考慮して、`mutable`な値は`getUnsafeValue`で取得できるようにしています。

```java:Collectionのラッパークラス
import javax.annotation.Nonnull;
import java.util.Collection;
import java.util.Iterator;

/**
 * 変更できないCollection
 */
public abstract class ImCollection<E> implements Iterable<E> {
    @Nonnull private final Collection<E> rawValue;

    protected ImCollection(@Nonnull Collection<E> rawValue) {this.rawValue = rawValue;}

    @Override
    @Nonnull
    public final Iterator<E> iterator() { return rawValue.iterator(); }

    /**
     * @return 変更するためのCollection
     */
    // 継承先で具体的な型を指定するためにabstractで定義している
    @Nonnull
    public abstract Collection<E> getUnsafeValue();

    public int size() { return rawValue.size(); }
    public boolean isEmpty() { return rawValue.isEmpty(); }
    public boolean contains(E e) { return rawValue.contains(e); }
    // 以降Collectionに定義された変更しない操作
}
```

```java:Listのラッパークラス
import javax.annotation.Nonnull;
import java.util.List;

/**
 * 変更できないList
 */
public final class ReadOnlyList<E> extends ImCollection<E> {
    @Nonnull private final List<E> value;

    public ReadOnlyList(@Nonnull List<E> value) {
        super(value);
        this.value = value;
    }

    @Override
    @Nonnull
    public List<E> getUnsafeValue() {return value;}

    public E get(int index) { return value.get(index); }
    public int indexOf(E e) { return value.indexOf(e); }
    public int lastIndexOf(E e) { return value.lastIndexOf(e); }
    // 以降Listに定義された変更しない操作
}
```

実用上は以下のような処理全てを実装する必要が有り、この方法は多少手間がかかりますが、`Collection`の操作だけを考えるなら最も安全な方法です。

- `Collection`インターフェースに定義された処理全て
- `Stream`の`Collector`
- `JSON`等へのシリアライズ/デシリアライズ時の変換処理

# 一致を安全に判定する
`Java`で一致を判定する場合、`primitive`型とオブジェクト型で異なる方法を用いる必要が有ります[^equals]。
また、オブジェクト型の一致判定で両方とも`nullable`だった場合、少なくとも片方が`null`でないことをチェックする必要が有ります。

[^equals]: 正確には、オブジェクト型同士を`==`で比較した場合、同一インスタンスか否かの判定結果が返ってきますが、実用上同一インスタンスかの判定は滅多に行わないため、ここでは言及していません。

```java
// primitive型同士
boolean isEquals(int i, int j) { return i == j; }
// primitive型とオブジェクト型
boolean isEquals(int i, @Nonnull Integer j) { return i == j; }
// オブジェクト型同士
boolean isEquals(@Nonnull Integer i, @Nonnull Integer j) { return i.equals(j); }
```

この比較方法の違いと`null`チェックの必要性はバグに繋がる危険性が有ります。
また`Integer`等のラッパー型であれば、特定の値の範囲では`==`で比較しても正常な結果が返ってくる場合が有るなど、深いバグになりやすいという点で更に高い危険性が有ります。

比較方法の違いや`null`チェックを吸収して安全に比較する方法としては、`java.util.Objects.equals`が提供されています。
安全性を重視するのであれば、比較は必ずこちらを用いる形とするのが良いでしょう。

```java
import java.util.Objects;

// a, bの型やnullabilityに関わらず判定できる
final bool result = Objects.equals(a, b);
```

一々`Objects.equals`と書くのが長いようであれば、以下のようなショートハンドを定義し、`static import`して利用することも考えられます。

```java
final class Util {
    public static boolean eq(@Nullable Object a, @Nullable Object b) { return Objects.equals(a, b); }
}
```

# 終わりに
この記事では`Java`で安全性を高めるための工夫について書きました。

書き上げた上で、自分は「例え新規プロジェクトでもこれ全部やるのは難しそうだな」と感じましたがいかがでしょうか？

`Kotlin`では、これらの安全性を簡単に実現できます。
明日公開の後編ではこの安全性と`Java`で実現した場合と比べた簡単さについて書きますので、お読み頂ければ幸いです。

**追記: 後編を公開しました**

https://qiita.com/wrongwrong/items/edc8bc30387391323c6e