はじめに
初投稿です。
今年の4月からWeb系のバックエンドエンジニアとして働き始めた1年目です。
アウトプットの機会を増やしたく、記事を投稿してみることにしました。
Markdown記法にも慣れていけたら良いなと思っています。誤りがあれば指摘していただけると助かります。
今回は、Java学習中に疑問に思った「CloneNotSupportedException」というエラーを扱おうと思います。
背景
私はclone()について、下記のような認識でいました。
- 自作のクラスにclone()を実装する際には、必ずCloneableインターフェースをimplementsする必要がある。また、Cloneableインターフェースはマーカーインターフェースであり、実際には何も宣言されていない。
- Objectクラスにはclone()メソッドがあり、全てのクラスは暗黙的にObjectクラスを継承している
疑問
上記の認識で、下記の2点を疑問に思いました。
- そもそもCloneableインターフェースはマーカーインターフェースであるため、clone()メソッドの実装を強制しているわけではないので、エラーとは関係ないはず
- 仮にCloneableインターフェース内でclone()が宣言されていたとしても、Objectクラスを継承しているのだからエラーを吐くのはおかしい(そもそも実装していないことによるエラーならコンパイルエラーになるはず)
要するに何がエラーを引き起こしてるのかを疑問に思いました。
ドキュメントを見る
そこで、Object#clone()のドキュメントを読んだところ、どうやらObject#clone()メソッドで、implements Cloneableを記述していない場合にエラーが出るよう実装されているようです。
つまり「背景」に記述した私の1つ目の認識は正確には間違っていて、自作クラスのclone()内部で、スーパークラス(正確にはObjectクラスの)clone()を利用していると、implements Cloneableの記載が必須になるようです。言語仕様によるエラーではなく、clone()がそのような実装になっているようです。
※ちなみにObject#clone()の実装をソースファイルを見て確認しようと思いましたが、native修飾子がついており、他の言語で実装されているようです。
実際に検証してみた
Mainクラスは下記です。
public class Main {
public static void main(String[] args) {
Hero hero = new Hero("太郎");
Hero hero3 = null;
try {
hero2 = hero.clone();
} catch (CloneNotSupportedException e) {
System.out.println(e.getMessage());
System.out.println("クローンの作成に失敗");
}
System.out.println(hero3);
}
}
Object#clone()を利用したHeroクラス
次に、Heroクラスです。
このHeroクラスのclone()内部では、親クラス、つまりObjectクラスのsuper()を利用しています。
public class Hero implements Cloneable {
private String name;
public Hero(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
@Override
public Hero clone() throws CloneNotSupportedException {
Hero hero = (Hero)super.clone();
System.out.println("Heroのクローンを作成");
return hero;
}
}
Heroクラスを利用した場合、implements Cloneableを削除してもコンパイルは通りますが、実行時にCloneNotSupportedExceptionになります。
Object#clone()を利用しないHeroクラス
public class Hero {
private String name;
public Hero(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
@Override
public Hero clone() {
return new Hero(this.name);
}
}
上記のHeroクラスは、clone()では新たなHeroインスタンスを作成し、それをリターンしているだけです。したがってimplements Cloneableは書かれていませんが無事にクローンが生成されます。MainクラスでCloneNotSupportedExceptionをハンドルする必要もありません。
まとめ
- Object#cloneではimplements Cloneableの記述がなければCloneNotSupportedExceptionエラーになる
- そのため、Object#clone()を利用していなければ、clone()を実装していたとしても実行時エラーにはならない。
終わりに
読んでくださった方、ありがとうございました。
今後も小さな発見でも時間があるときに投稿しようと思うので、見ていただけると嬉しいです。