はじめに
同一でないオブジェクトを複製させるために、Cloneableインターフェースを実装してObject#clone
をオーバーライドさせるやり方があります。
ただ、次のコードのように総称型を利用したオブジェクトの複製をしたいときに、直接Object#clone
にアクセスできずにもどかしい思いをしたので、総称型を利用してもオブジェクトの複製ができるように色々と工夫することとしました1。
class GenericsSample {
@SuppressWarnings("unchecked")
static <T> T cloneSample(T obj) {
return (T) obj.clone(); // コンパイルエラー(不可視なcloneメソッド)
}
}
インターフェースの導入
public interface CloneablePublicly<T> {
T clone();
}
Object#clone
の代わりに外部から複製するためのメソッドを呼び出すためのインターフェースを作りました。総称型を利用するオブジェクトのクラスに対し、こいつを実装させます。
class Sample implements Cloneable, CloneablePublicly<Sample> {
int value;
public Sample(int value) {
this.value = value;
}
@Override public Sample clone() {
try {
return (Sample) super.clone();
} catch (CloneNotSupportedException e) {
throw new InternalError(e);
}
}
}
勿論、Cloneableインターフェースも同時に実装させます。これが実装されていないと、CloneNotSupportedException
を吐きます。
オブジェクトの複製
あとは、例えば次のようにメソッドを修正すれば、総称型を利用したオブジェクトの複製が可能となります。
class GenericsSample {
@SuppressWarnings("unchecked")
static <T> T cloneSample(T obj) {
if (obj instanceof Cloneable && obj instanceof CloneablePublicly) {
return ((CloneablePublicly<T>) obj).clone();
}
return null;
}
}
Sample samp1 = new Sample(1);
Sample samp2 = GenericsSample.cloneSample(samp1);
System.out.println(samp1.value); // 1
System.out.println(samp2.value); // 1
System.out.println(samp1 == samp2); // false(同一でないオブジェクトの複製)
おまけ
nullを返したくない場合は、次のようにオプショナルを返します2。これにより、オブジェクトを複製できない場合は何もしないようにすることもできます。
class GenericsSample {
@SuppressWarnings("unchecked")
static <T> Optional<T> cloneSample(T obj) {
return Optional.ofNullable(obj)
.filter(Cloneable.class::isInstance)
.filter(CloneablePublicly.class::isInstance)
.map(o -> ((CloneablePublicly<T>) obj).clone());
}
}
Sample samp1 = new Sample(1);
GenericsSample.cloneSample(samp1).ifPresent(samp2 -> {
System.out.println(samp1.value); // 1
System.out.println(samp2.value); // 1
System.out.println(samp1 == samp2); // false(同一でないオブジェクトの複製)
});
// Sample samp1 = null; のときは、何もしない。
追記
@saka1029 さんのコメントより、インターフェースの継承と境界型パラメータを用いた実装をご紹介いただきました。ありがとうございます!
interface CloneablePublicly<T> extends Cloneable {
T clone();
}
こうすればわざわざ総称型を利用するオブジェクトのクラスに対しCloneableインターフェースを実装せずに済みますね。インターフェースの継承機能をすっかり忘れていました。
class Sample implements CloneablePublicly<Sample> {
... // 同じ
}
class GenericsSample {
static <T extends CloneablePublicly<T>> T cloneSample(T obj) {
return Objects.isNull(obj) ? null : obj.clone();
}
}
境界型パラメータを用いて型安全性を確保すれば、手書きでキャスト検査する必要もなくなります。
オプショナルも、一行で済んでしまいました。
class GenericsSample {
static <T extends CloneablePublicly<T>> Optional<T> cloneSample(T obj) {
return Optional.ofNullable(obj).map(CloneablePublicly::clone);
}
}