Object#clone() メソッドからスローされる CloneNotSupportedException はどのようにハンドリングするべきか

（この記事は [地平線に行く](https://yujisoftware.hatenablog.com/entry/CloneNotSupportedException) とのマルチポストです）

Java の `Object#clone()` メソッドは ` throws CloneNotSupportedException` が宣言されています。

```java

protected native Object clone() throws CloneNotSupportedException;
```

しかし、**クラスが `Cloneable` インタフェースを実装していれば `CloneNotSupportedException` はスローされません。**
それにもかかわらず `CloneNotSupportedException` はキャッチ例外のため[^CloneNotSupportedException]、スローするかキャッチする必要があります。

[^CloneNotSupportedException]: 設計ミスだと思います。

```java
                            /* 👇実装している！ */
public class Example implements Cloneable {
    @Override
    public Object clone() {
        // エラー: 例外CloneNotSupportedExceptionは報告されません。
        // スローするには、捕捉または宣言する必要があります
        return super.clone();
    }
}
```

正しく実装していれば何もしなくてもいいハズなのですが…。
このようなとき、どのようにハンドリングすればいいのでしょうか。

そこで、Java API ではこのよう場合にどのように実装されているかを確認してみました。
[Search · CloneNotSupportedException path:/src/java.base/share/classes/java/](https://github.com/openjdk/jdk/search?p=4&q=CloneNotSupportedException+path%3A%2Fsrc%2Fjava.base%2Fshare%2Fclasses%2Fjava%2F&unscoped_q=CloneNotSupportedException+path%3A%2Fsrc%2Fjava.base%2Fshare%2Fclasses%2Fjava%2F)

# InternalError でラップする

圧倒的に多いのが、`CloneNotSupportedException` を `InternalError` でラップするという実装です。
`Cloneable` インタフェースを実装しているのに `CloneNotSupportedException` がスローされたということは、VM 内で何か問題があったということでこのようにしているようです。

```java:src/java.base/share/classes/java/util/ArrayList.java
public Object clone() {
    try {
        ArrayList<?> v = (ArrayList<?>) super.clone();
        v.elementData = Arrays.copyOf(elementData, size);
        v.modCount = 0;
        return v;
    } catch (CloneNotSupportedException e) {
        // this shouldn't happen, since we are Cloneable
        throw new InternalError(e);
    }
}
```

# AssertionError でラップする

`ArrayDeque`, `EnumMap`, `EnumSet` は `CloneNotSupportedException` を `AssertionError` でラップしていました。
プログラムがバグっているということを示すために、`AssertionError` を使用しているようです。直接、この例外を投げるのは珍しい気がします。[^assert]

[^assert]: 本来は、-enableassertions オプション付きで Java を実行しているときに、assert 文の条件が成立していないときにスローされるエラーです。

```java:src/java.base/share/classes/java/util/ArrayDeque.java
public ArrayDeque<E> clone() {
    try {
        @SuppressWarnings("unchecked")
        ArrayDeque<E> result = (ArrayDeque<E>) super.clone();
        result.elements = Arrays.copyOf(elements, elements.length);
        return result;
    } catch (CloneNotSupportedException e) {
        throw new AssertionError();
    }
}
```

# RuntimeException でラップする

発生しないはずだから、とりあえず非キャッチ例外にしてしまえ！ということのようです。
ただ、RuntimeException だと型でエラーの概要が分からない、`catch(Excetption e)` で捕まってしまうという問題があります。

```java:src/java.base/share/classes/java/net/HttpCookie.java
public Object clone() {
    try {
        return super.clone();
    } catch (CloneNotSupportedException e) {
        throw new RuntimeException(e.getMessage());
    }
}
```

# 握りつぶして、null を返す

`java.util.Date` では、キャッチして握りつぶしていました。
ただ、これは発生しないとはいえ `null` が返されてしまうルートができてしまうので、紛らわしいと思います。

```java:src/java.base/share/classes/java/util/Date.java
public Object clone() {
    Date d = null;
    try {
        d = (Date)super.clone();
        if (cdate != null) {
            d.cdate = (BaseCalendar.Date) cdate.clone();
        }
    } catch (CloneNotSupportedException e) {} // Won't happen
    return d;
}
```

# ちなみに

`Cloneable` インタフェースを実装しているクラスの `clone` メソッドで、`throws CloneNotSupportedException` を宣言しているのは見当たりませんでした。

# まとめ

いかがでしたか。[^1]
`Cloneable` インタフェースを実装していれば `CloneNotSupportedException` はスローされないので、どのようにハンドリングしても問題はないです。とはいえ、throws してしまうと呼び元に余計な手間をかけてしまいます。そのため、キャッチして何かしらのハンドリングをしておいた方がいいと思います。

そして、Java API の実装に合わせるのであれば `InternalError` にラップしてスローするのがいいようです。

---

Java Advent Calendar 2019、明日は @e-a-st さんです！
[人生最後に学ぶプログラム言語とは？　(生涯給与３億円クラブに追いつきたい場合) - Qiita](https://qiita.com/e-a-st/items/eaa6815ef8bb264789ce)


[^1]: これ書くとアフィリエイトブログっぽいですね。