0
0

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.

総称型を利用したオブジェクトの複製

Last updated at Posted at 2020-09-30

はじめに

同一でないオブジェクトを複製させるために、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 さんのコメントより、インターフェースの継承と境界型パラメータを用いた実装をご紹介いただきました。ありがとうございます!

Cloneableインターフェースの継承
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);
    }
}
  1. 色々と調べたのですが、良さそうな資料が見つからなかったので自作しました。ベストプラクティスな方法やご指摘などございましたら、恐れ入りますがご教示いただけますと幸いです。

  2. 「Effective Java 第3版」によればOptionalを頻繁に用いるとコストがかかるとのことなので、パフォーマンスが気になる場合は控えたほうが良いかもしれません。

0
0
3

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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?