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

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

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

# 前書き
この記事は[JavaでKotlinと同じくらい安全なコードを書く（前編）](https://qiita.com/wrongwrong/items/9b1a5c4f76128b43fa3e)の続きです。

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

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

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

# nullを安全に取り扱う
`Kotlin`では、`nullable`な場合型に`?`を付けます。

```kotlin:Kotlin
// fooはnon-null, barはnullable、戻り値はnon-null
fun func(foo: String, bar: String?): String { /* 略 */ }
```

このコードは、前回の記事で紹介した下記のコードと等価です。

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

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

比較すると、`Kotlin`のコードでは特にアノテーションを付ける必要が無く、シンプルなことが分かります。
また、`Kotlin`では指定された`nullability`に違反するコードを書いた場合コンパイルエラーになります。

# メソッド・クラス・変数を基本的に変更できないようにする

## メソッド/クラスを変更できないようにする
クラスを継承するためには`open`で修飾する必要が有り、何もしなかった場合、継承するとコンパイルエラーになります。

```kotlin:Kotlin
class Closed
class ClosedEx : Closed() // コンパイルエラーになる

open class Open
class OpenEx : Open() // コンパイルエラーにならない
```

以下は`Java`でクラスを継承不可にするコードです。
実際のコードでは継承を利用することの方が少ないため、`Kotlin`の方がシンプルかつ安全です。

```java:Java
public final class Temp {}
// コンパイルエラーになる
public class TempEx extends Temp {}
```

`open`クラスに定義した関数に関しては自由にオーバーライドできるため、これを継承不可にする場合は`Java`同様に`final`修飾する必要が有ります。

```kotlin:Kotlin
open class Open {
    final fun closed() {}
    open fun open() {}
}
class OpenEx : Open() {
    override fun closed() { super.closed() } // コンパイルエラーになる
    override fun open() { super.open() } // コンパイルエラーにならない
}
```

```java:Java
public class Temp {
    public final void closed() {}
}
public class TempEx extends Temp {
    @Override
    public void closed() { super.func(); } // コンパイルエラーになる
}
```

こちらは`Kotlin`と`Java`であまり差は有りません。

## 変数を変更できないようにする
`Kotlin`では、変数を`val`もしくは`var`で宣言します。
`val`で宣言した場合は再代入不可、`var`で宣言した場合は再代入可能となります。

```kotlin:Kotlin
val value: Int = 0
var variable: Int = 0

value = 1 // コンパイルエラーになる
variable = 1 // コンパイルエラーにならない
```

一々`final`修飾する必要が無くなっているため、`lombok`を導入していない`Java`に比べるとこちらの方がシンプルです。

また、`Kotlin`では、引数、拡張`for`文の変数、ラムダ式のレシーバーに再代入するとコンパイルエラーになります。
こちらも一々`final`修飾する必要が無くなっており、シンプルかつ安全です。

**引数への再代入不可**

```kotlin:Kotlin
// コンパイルエラーになる
fun f1(arg: Int?) { arg = 0 }
```

```java:Java
// コンパイルエラーになる
public void f1(@Nonnull final Integer arg) { arg = 0; }
```

**拡張for文の変数への再代入不可**

```kotlin:Kotlin
// コンパイルエラーになる
for (i in list) { i = 0 }
```

```java:Java
// コンパイルエラーになる
for (@Nonnull final Integer i : list) { i = 0; }
```

**ラムダ式のレシーバーへの再代入不可**

```kotlin:Kotlin
// コンパイルエラーになる
list.forEach { i -> i = 0 }
```

```java:Java
// コンパイルエラーになる
list.forEach((@Nonnull final Integer i) -> { i = 0; });
```

# Collectionを読み取り専用にする
`Kotlin`では、`mutable`な`Collection`と`read only`な`Collection`が区別されています。

```kotlin:Listの例
val readOnly: List<Int> = listOf(1, 2, 3)
val mutable: MutableList<Int> = mutableListOf(1, 2, 3)

readOnly[0] = 1 // コンパイルエラーになる
mutable[0] = 1 // コンパイルエラーにならない
```

`Java`で同様の安全性を確保する場合、変更操作で実行時エラーが発生することを承知で`Collections.unmodifiable...`でラップするか、以下のようなラッパークラスをそれぞれ定義する必要が有ります。

```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`の内容を生成時以外に変更することは少ないため、`Kotlin`の方がシンプルかつ安全です。

# 一致を安全に判定する
`Kotlin`のコード上では、プリミティブ型とオブジェクト型、及び`nullability`の違いを意識する必要はなく、ただ単に`==`を使うだけで比較が行えます。

```kotlin:Kotlin
// Kotlin上のIntは、non-nullならプリミティブ型、nullableならラッパー型としてJVM上で扱われるが、
// Kotlin上では全く同じように比較できる
fun isEquals(i: Int, j: Int): Boolean { return i == j }
fun isEquals(i: Int, j: Int?): Boolean { return i == j }
fun isEquals(i: Int?, j: Int?): Boolean { return i == j }
```

`Java`の場合、以下のように型ごとに比較方法を意識する必要が有ります。
また、サンプルコードには出していませんが、`null`から`equals`を呼び出すと実行時エラーになるため`null`チェックも適切に行う必要が有ります。
それらを統一的に扱うため、`Objects.equals`に統一する手段も有りますが、`Kotlin`の方がシンプルに書くことができます。

```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); }
```

### 補足: 同一インスタンスかの比較について
`Java`でオブジェクト型を`==`で比較した場合同一インスタンスかの比較になりますが、`Kotlin`では`===`でこの比較が行えます。
否定形は`!==`です。

# 後書き
このシリーズでは`Java`で安全なコードを書く大変さと、`Kotlin`で安全なコードを書く簡単さについて書きました。

`Java`で安全性を突き詰めていくと、ライブラリ・フレームワークの導入やコード・`CI`の複雑化が避けられず、最後には人の目を入れる必要性もあるため、100%徹底することも難しいという課題に当たります。
一方`Kotlin`では、ただ文法に沿って書くだけで、コンパイル時の機械的なチェックによって多くの安全性を確保できます。
ライブラリ・フレームワークを導入したり、コードや`CI`を複雑にすることなく安全性を確保できることは、`Kotlin`を使う大きな利点です。

また、シンプルかつ安全にコードを書けるということは、コード内に全ての意図が分かりやすく表現される = ドキュメント性の高いコードが書かれるということにもつながります。

`Kotlin`の書きやすい・読みやすいという特徴は、ソフトウェア開発における生産性の向上につながります。

`Java`に限らず、安全性が気になっていたり、安全に書くために生産性を犠牲にしている感の有る方は、是非`Kotlin`に触れてみて下さい。

https://kotlinlang.org/