- PR -

Object クラスの clone メソッドが protected なので使いづらい

投稿者投稿内容
未記入
ぬし
会議室デビュー日: 2002/03/28
投稿数: 255
投稿日時: 2003-03-20 17:34
>1.protectedである必然性はない。
>2.Cloneableには問題がある。
>この両者は独立していると思います。
これは同感.

>unibonさんのおっしゃるように、clone()はpublicであるべきだと思います。
>親クラスがクローン不可能なのに、子クラスがクローン可能なのは変です。
これは全く反対.少なくともpublicにすべきではない.

親クラスで不可能なもの(機能が無い)が,子クラスで可能になる
(機能が追加される)というのはごく自然なことのはず.
逆に子クラスのほうが制限されるほうが不自然で,むしろ
可能ならば避けるべきものと言えます.

これはOOPの本なら普通に説明されてると思うし,ほとんど
常識のはず.
#理論を延々と説明するのは手間がかかるのでパス.
#それに上手く説明できる自信もないし.

第一,プログラムが面倒になります.なんせ全部実行時
エラーでしか検出できなくなるわけでしょ?

>私は主にC++を使うのですが、リソースなどコピーできないメンバ変数を
>持つクラスは、コピーコンストラクタと代入演算子をprivateに定義して
C++は有名な欠陥言語なので,OOPの議論においてC++を例として
引用するのは無意味です.C++を使わざるを得ないOSがあるのは
知っていますが,だからと言ってC++の欠陥が変わるものでは
ありません.
うのきち
ベテラン
会議室デビュー日: 2003/02/17
投稿数: 55
投稿日時: 2003-03-20 18:28
引用:

clone の使用が制限されている場合、
clone に代わる実現方法がないことに疑問を持っています。
Object.clone は native であり、フィールドを丸ごと浅いコピーしてくれますが、
これに代わるものを自分で作るとすると、
asip さんが紹介されたコピーコンストラクタのような方法になってしまいます。
しかしインスタンスを new してフィールドをコピーするよりは、
Object.clone で浅いコピーを作るほうが、コストが低く素直なように思います。
#速いから良い、というわけでは必ずしもありませんが。



前に、色々試してみた時、clone()って、遅かったような記憶があります。
あらゆるケースを試したわけではないですが、例えば配列オブジェクトをclone()するのと、newしてから、System.arraycopy()するのでは、後者の方が断然速かったです。
私は、自分で作る時は、clone()は使いません。コピーコンストラクタを使います。
以下が可能なら、まだ使う気にもなるんですが。

public Object dup(Cloneable obj) {
return obj.clone();
}

clone()でないと、出来ない事が存在しないので...
ocean
会議室デビュー日: 2001/10/07
投稿数: 7
投稿日時: 2003-03-20 18:50
>これは全く反対.少なくともpublicにすべきではない.
>第一,プログラムが面倒になります.なんせ全部実行時
>エラーでしか検出できなくなるわけでしょ?

そうですね、ランタイムエラーよりも
コンパイルエラーの方が望ましいのは確かです。
正直、clone()がSetインターフェースを介して呼べないのは、
望みの動作のようにも思えます。ただ、デフォルトの動作が
浅いコピーであり、TreeSetとHashSetの親クラスの動作に
問題がないのなら、publicにしたほうが単純な気がします。

>親クラスで不可能なもの(機能が無い)が,子クラスで可能になる
>(機能が追加される)というのはごく自然なことのはず.
>逆に子クラスのほうが制限されるほうが不自然で,むしろ
>可能ならば避けるべきものと言えます.

普通は同感できるのですが、クローンだと異論があります。
普通クラスを定義するとき、private変数は自己以外から
アクセスされないことを仮定して書くと思います。
もしこのクラスを継承し、Cloneableをインプリメントした場合、浅いコピーなので
super.clone()をよべば親クラスのprivate変数が参照で共有されることになります。

//////////////////////////////////////
// あまりいい例ではありませんが、
// 普通にコーディングしたつもりです。

import java.util.HashSet;

class Country
{
private final HashSet cities = new HashSet();

public void addCity(String name)
{
this.cities.add(name);
}

public void printMe()
{
System.out.println(this.cities);
}
}

class CountryEx extends Country implements Cloneable
{
private final String name;

public CountryEx(String name)
{
this.name = name;
}

public Object clone() throws CloneNotSupportedException
{
return super.clone();
}

public void printMe()
{
System.out.println("[" + this.name + "]");

super.printMe();
}
}

public class MyClass
{
public static void main(String[] args) throws CloneNotSupportedException
{
CountryEx a = new CountryEx("France");
CountryEx b = (CountryEx) a.clone();

a.addCity("Paris");

a.printMe();
b.printMe();
}
}

こうすると、bには追加していないけれど
[France]
Paris
[France]
Paris
となります。

そうすると、
1.親クラスがCloneableでない場合は super.clone() を呼ぶべきではない(子クラスの責任)
2.Cloneableでないクラスを定義するときは、継承クラスがCloneableをインプリメント
することに備えた実装をする必要がある(親クラスの責任)
のどちらかが必要になると思います。

1.はナンセンスです。
そもそも、HashSet extends AbstractSet implements Cloneable と
JDK1.4.1で定義されていますが、その中で super.clone() が呼ばれています。
とすると、「AbstractSetはCloneableではないが、clone()を呼んでも安全だ」
という実装の詳細を把握していなくてはならないことになります。

なので、2.ということになるんですが、
私にはCloneableでないクラスで例外を送出するように
clone()を実装するぐらいしか思いつきません。そうすると、
Cloneableの存在する理由が無くなってしまいます。

以上の点で、Cloneableはわからない点が多いと思います。
NotCloneableなら、まだ理解できますが・・・




[ メッセージ編集済み 編集者: ocean 編集日時 2003-03-20 18:56 ]
asip
ベテラン
会議室デビュー日: 2001/12/27
投稿数: 77
投稿日時: 2003-03-20 19:46
"shallow copy"ならわざわざclone()を使う必要はないのでは?

コード:
import java.util.*;

public class MyClass implements Cloneable {

    private Set set = new HashSet(); // 今のところ HashSet だが将来は TreeSet にもしたい。

    public Object clone() {
        try {
            MyClass o = (MyClass) super.clone();
            o.set = set; 
            return o;
        } catch (CloneNotSupportedException ex) {
            throw new InternalError();
        }
    }
    
    public static void main(String[] args) {
        MyClass a = new MyClass();
        MyClass b = (MyClass) a.clone();
    }
}



で期待する結果が得られるはず。
未記入
ぬし
会議室デビュー日: 2002/03/28
投稿数: 255
投稿日時: 2003-03-20 21:04
>1.親クラスがCloneableでない場合は super.clone() を呼ぶべきではない(子クラスの責任)
>2.Cloneableでないクラスを定義するときは、継承クラスがCloneableをインプリメント
>することに備えた実装をする必要がある(親クラスの責任)
>のどちらかが必要になると思います。
多分2が正しいのでしょう.
「子クラスを作ることを許し,且つ,子クラスがclone()をサポートする
ことを許す場合は,親クラスではそのことを想定して実装する必要がある.」

継承できるように設計されていない限り,一般には継承は不可能,というのが
原則だと思います.継承は万能じゃないんですよ.

ただ,推論の方向性自体は正しいと思います.

>以上の点で、Cloneableはわからない点が多いと思います。
>NotCloneableなら、まだ理解できますが・・・
で,正にこういうふうな結論に至るので,clone()はイマイチ設計が良くないと
言われたりするわけです.もし,もっと綺麗な設計が思いついたら,....

まあ,Javaについてはもはや手遅れですけどね.
次は軽く10〜20年後でしょう.全く異なる新言語が登場する時ですから.
unibon
ぬし
会議室デビュー日: 2002/08/22
投稿数: 1532
お住まい・勤務地: 美人谷        良回答(20pt)
投稿日時: 2003-03-26 09:23
unibon です。こんにちわ。

引用:

うのきちさんの書き込み (2003-03-20 18:28) より:
前に、色々試してみた時、clone()って、遅かったような記憶があります。
あらゆるケースを試したわけではないですが、例えば配列オブジェクトをclone()するのと、newしてから、System.arraycopy()するのでは、後者の方が断然速かったです。



つぎのコードで試してみましたが、速度差はあまり感じませんでした。
コード:
public class CloneTester5 implements Cloneable {


private static final boolean USE_CLONE = true; // true の場合は、配列を clone メソッドで複製する。

private int[] array = new int[100000]; // 400KB の配列。

public CloneTester5() {
// ゼロのままだとどこかの段階で最適化される可能性もあるので、特徴のある値で埋める。
for (int i = 0; i < array.length; i++) {
array[i] = i;
}
}

public Object clone() throws CloneNotSupportedException {
CloneTester5 ct = (CloneTester5) super.clone();
if (USE_CLONE) {
ct.array = (int[]) array.clone();
} else {
ct.array = new int[array.length];
System.arraycopy(array, 0, ct.array, 0, array.length);
}
return ct;
}

public static void main(String[] args) throws CloneNotSupportedException {
CloneTester5 src = new CloneTester5();
CloneTester5[] dst = new CloneTester5[200]; // 200 個の複製の格納用。
long a = System.currentTimeMillis();
for (int i = 0; i < dst.length; i++) {
dst[i] = (CloneTester5) src.clone();
}
long b = System.currentTimeMillis();
System.out.println("time = " + (b - a) + "[ms]");
}

}


このサンプルプログラムは論理的に 80MB(=200*100000*4バイト)のメモリを使いますが、
物理メモリが 512MB 程度の Windows 2000 のコンピュータで、
JDK 1.4.1 で -Xmx256m を指定して動かしてもさほど差がなかったです。
ただ arraycopy のほうが1%ほど速いような気もしないではなかったですが、
誤差の範囲かもしれません。
ループ回数などを増やすとガーベッジコレクションの影響が大きくなることもあり、
あまりうまく測定できていないのですが。

引用:

うのきちさんの書き込み (2003-03-20 18:28) より:
私は、自分で作る時は、clone()は使いません。コピーコンストラクタを使います。
以下が可能なら、まだ使う気にもなるんですが。

public Object dup(Cloneable obj) {
return obj.clone();
}

clone()でないと、出来ない事が存在しないので...


以下、かなり自信がないのですが、
これを拝見すると、そもそも Object.clone は abstract でも良いような気もします。
(ポリモーフィズムのためには Object クラスに、
まったく clone の定義がないと困りますが、
でも native の実装がある必要がないような気がします。)

#以下、あとで追加。

でもそうしたら
Object o = new Object();
ってできなくなりますね。
やっぱり abstract にはできないですね。でも Object.clone の動作として、
フィールドの浅いコピーがなくても良い、
すなわち、プリミティブなフィールドは 0 や false になり、
オブジェクト用のフィールドは null になる、
というのはありかもしれませんね。
でも、もしそういう仕様だとしたら、こんどは、
「なぜついでに浅いコピーしてくれないんだ。
手間がかかるわけでもなしデフォルトで浅いコピーはやってほしい。
特に配列のときなどは大変じゃないか。」
という不満が出るに決まっているので、今のような動作になるんでしょうね。

#あと、編集すると BB コードが崩れたので、BB コードの部分(CODE タグ)を編集しました。


[ メッセージ編集済み 編集者: unibon 編集日時 2003-03-26 15:22 ]
未記入
ぬし
会議室デビュー日: 2002/03/28
投稿数: 255
投稿日時: 2003-03-26 20:43
>つぎのコードで試してみましたが、速度差はあまり感じませんでした。

というより,
>ループ回数などを増やすとガーベッジコレクションの影響が大きくなることもあり、
>あまりうまく測定できていないのですが。
が正解だと思います.

クローンだろうがnewだろうが大量のインスタンスを次々と生成する処理
自体が本質的に遅いので,最適化を考えるなら無駄なインスタンス生成を
減らすようにアルゴリズムの変更を検討すべきです.インスタンス生成と
GCに比べれば,cloneとarraycopyの差など誤差みたいなものなんでしょうね.
下手すると,内部的にarraycopyと同じ処理に最適化してる可能性さえあります.
ocean
ベテラン
会議室デビュー日: 2003/07/06
投稿数: 65
投稿日時: 2003-07-11 20:28
すでに凍ったスレッドだと思いますが、認識が改まったので追記させていただきます。

C#では、次のように簡潔に実現できるようです。

コード:

using System;

class Base
{
}

class Derived : Base, ICloneable
{
public virtual object Clone()
{
return new Derived();
}
}

class App
{
public static void Main()
{
Base b1 = new Derived();

Base b2 = (Base) ((ICloneable)b1).Clone();
}
}



Javaでこうできないのは、Cloneableがタグにすぎず、実際にはclone()を持っていないためです。なぜJavaがそのような設計にしたかは存じませんが・・・

以前の私の返答は、C++の影響でしょうか、Objectがclone()を持つことを前提にしていたところがありました。でも実際にC#の設計を見てみると、悪夢を統べるものさんの

引用:

「Objectクラスのインスタンスはcloneできない」ってのが正しいかと


が正しかったのかなと思えました。

まだ、
class A
class B : ICloneable, A
がAの設計変更でICloneableを実装したとき、B::Clone()を変更しなくちゃいけないのでは?とか、疑問が残ってはいますが。。。




[ メッセージ編集済み 編集者: ocean 編集日時 2003-07-11 20:33 ]

[ メッセージ編集済み 編集者: ocean 編集日時 2003-07-11 20:33 ]

スキルアップ/キャリアアップ(JOB@IT)