Java の Object#Clone() メソッドは throws CloneNotSupportedException が宣言されています。
protected native Object clone() throws CloneNotSupportedException;
しかし、クラスが Cloneable インタフェースを実装していれば CloneNotSupportedException はスローされません。
それにもかかわらず CloneNotSupportedException はキャッチ例外のため1、スローするかキャッチする必要があります。
/* 👇実装している! */ public class Example implements Cloneable { @Override public Object clone() { // エラー: 例外CloneNotSupportedExceptionは報告されません。 // スローするには、捕捉または宣言する必要があります return super.clone(); } }
正しく実装していれば何もしなくてもいいハズなのですが…。 このようなとき、どのようにハンドリングすればいいのでしょうか。
そこで、Java API ではこのよう場合にどのように実装されているかを確認してみました。 Search · CloneNotSupportedException path:/src/java.base/share/classes/java/
InternalError でラップする
圧倒的に多いのが、CloneNotSupportedException を InternalError でラップするという実装です。
Cloneable インタフェースを実装しているのに CloneNotSupportedException がスローされたということは、VM 内で何か問題があったということでこのようにしているようです。
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 を使用しているようです。直接、この例外を投げるのは珍しい気がします。2
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) で捕まってしまうという問題があります。
public Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e.getMessage());
}
}
握りつぶして、null を返す
java.util.Date では、キャッチして握りつぶしていました。
ただ、これは発生しないとはいえ null が返されてしまうルートができてしまうので、紛らわしいと思います。
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 を宣言しているのは見当たりませんでした。
まとめ
いかがでしたか。3
Cloneable インタフェースを実装していれば CloneNotSupportedException はスローされないので、どのようにハンドリングしても問題はないです。とはいえ、throws してしまうと呼び元に余計な手間をかけてしまいます。そのため、キャッチして何かしらのハンドリングをしておいた方がいいと思います。
そして、Java API の実装に合わせるのであれば InternalError にラップしてスローするのがいいようです。