14
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

JavaAdvent Calendar 2019

Day 10

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

Last updated at Posted at 2019-12-09

(この記事は 地平線に行く とのマルチポストです)

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 でラップする

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

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, EnumSetCloneNotSupportedExceptionAssertionError でラップしていました。
プログラムがバグっているということを示すために、AssertionError を使用しているようです。直接、この例外を投げるのは珍しい気がします。2

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) で捕まってしまうという問題があります。

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 が返されてしまうルートができてしまうので、紛らわしいと思います。

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 を宣言しているのは見当たりませんでした。

まとめ

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

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


Java Advent Calendar 2019、明日は @e-a-st さんです!
人生最後に学ぶプログラム言語とは? (生涯給与3億円クラブに追いつきたい場合) - Qiita

  1. 設計ミスだと思います。

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

  3. これ書くとアフィリエイトブログっぽいですね。

14
3
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?