Object#clone() メソッドからスローされる CloneNotSupportedException はどのようにハンドリングするべきか
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
にラップしてスローするのがいいようです。