6

整数の配列「orig」があるとしましょう

私はそれを浅くコピーしたいので、これを行うことはできません:

int[] shallow = orig;

私の教授は、プリミティブの場合、配列の各インデックスをコピーする必要があるという点で、浅いコピーと深いコピーは本質的に同じであると言いました。しかし、配列全体を別の配列と等しく設定すると、同じことが起こりますよね?

オブジェクト配列について同様の質問があります

これが私のイデオロギーでした

Book[] objArr2 = objArr1;

しかし、次のように、各配列インデックスをコピーする必要があると言われました

//for loop
objArr2[i] = objArr1[i];

浅いコピーの場合、配列を別の配列と等しくすることと、各配列インデックスを個別にコピーすることの間に実際に違いはありますか? (深い意味は、まったく新しいオブジェクトを作成する必要があることを理解しています)

4

4 に答える 4

17

私はそれを浅くコピーしたいので、これを行うことはできません:

int[] 浅い = 元;

それは本当に浅いコピーではありません。コピーとは、オリジナルに似ているが、オリジナルのアイテムではない個別のエンティティです。あなたの例では、実際に持っているのは、同じオブジェクトを指している 2 つの参照です。コピーを作成すると、元のオブジェクトとコピーの 2 つのオブジェクトが作成されます。

ここでは、両方が同じオブジェクトを指しているため、変更することはすべてshallow同様に行われます。orig

比較しているオブジェクトがその中に他のオブジェクトへの参照を持っている場合、「浅さ」が作用します。たとえば、整数の配列があり、コピーを作成すると、同じ整数値を含む 2 つの配列が作成されます。

Original Array

[0]
[1]
[2]
[3]

After copying:

[0] <--- Original  [0]
[1]                [1]
[3]                [2]
[4]      Copy ---> [3]

objArr1しかし、オブジェクト ( ととしましょうobjArr2) で構成される配列がある場合はどうなるでしょうか。浅いコピーを行うと、2 つの新しい配列オブジェクトが作成されますが、2 つの配列間の対応する各エントリは同じオブジェクトを指します (オブジェクト自体はコピーされておらず、参照だけがコピーされているため)。

Original Array:

[0:]----> [object 0]
[1:]----> [object 1]
[2:]----> [object 2]
[3:]----> [object 3]

コピー後 (対応する場所が同じインスタンスを指していることに注意してください):

Original -> [0:]----> [object 0] <----[:0] <- Copy
            [1:]----> [object 1] <----[:1]
            [2:]----> [object 2] <----[:2]
            [3:]----> [object 3] <----[:3]

objArr1エントリを置き換えたり、エントリを削除したりして変更しても、同じことobjArr2. ただし、 でオブジェクトを変更すると、それらの場所が同じオブジェクトを指しているため、 にもobjArr1[0]反映されます。したがって、この場合、コンテナー オブジェクト自体は別個のものですが、それらに含まれるものは同じオブジェクトへの参照です。objArr2[0]

ディープ コピーを実行すると、対応する各場所が異なるインスタンスを指す 2 つの新しい配列が作成されます。したがって、基本的には、オブジェクトのコピーをずっと下に作成します。

私の教授は、プリミティブの場合、配列の各インデックスをコピーする必要があるという点で、浅いコピーと深いコピーは本質的に同じであると言いました。

重要な違いは、プリミティブの配列をコピーする場合、値を正確にコピーすることです。新しいプリミティブを取得するたびに。ただし、オブジェクトの配列がある場合、実際にあるのはオブジェクトへの参照の配列です。したがって、コピーを作成するときは、元の配列の参照のコピーを持つ新しい配列を作成するだけです。ただし、これらの参照の新しいコピーは、依然として同じ対応するオブジェクトを指しています。これは、浅いコピーとして知られているものです。配列をディープコピーすると、個々の場所が参照するオブジェクトもコピーされます。したがって、次のように表示されます。

Original -> [0:]----> [object 0] Copy -> [0:]----> [copy of object 0]
            [1:]----> [object 1]         [1:]----> [copy of object 1]
            [2:]----> [object 2]         [2:]----> [copy of object 2]
            [3:]----> [object 3]         [3:]----> [copy of object 3]

しかし、配列全体を別の配列と等しく設定すると、同じことが起こりますよね?

いいえ、違います。ここで行っているのは、既存の配列への新しい参照を作成することだけです。

arr1 -> [0, 1, 2, 3, 4]

では、したとしましょうarr2 = arr1。あなたが持っているものは次のとおりです。

arr1 -> [0, 1, 2, 3, 4] <- arr2

したがって、ここではarr1との両方arr2が同じ配列を指しています。したがって、同じ配列を見ているため、使用して実行した変更は、使用しarr1て配列にアクセスすると反映されます。arr2これは、コピーを作成する場合には発生しません。

于 2012-10-31T20:34:32.517 に答える
1

ここでの問題は、配列をオブジェクトと考える必要があることです。格納したobjArr1のは、配列の先頭を参照するメモリ アドレスです。したがって、たとえば、objArr1配列がアドレス 0x1234578 に格納されている場合、の値objArr1は事実上 0x1234578 であり、

objArr2 = objArr1;

の値はobjArr20x1234578 に等しくなければならないと言っています。ここで、0x1234578 に格納されている配列の要素 1 を変更すると、それを参照するobjArr1[1]objArr2[1]、値を使用するかは同じになります。どちらかを変更しようとすると、同じことが言えます。どちらかを変更すると、同じ場所を指しているため、もう一方にも反映されます。たとえば、次のコードは true になります

objArr2 = objArr1;
objArr2[0] = 5;
objArr1[0] = 6;
System.out.println(objArr2[0]); //prints "6"

その動作は便利な場合もありますが、コピーにはなりません。

于 2012-10-31T20:33:17.053 に答える
1

プリミティブ用

次の例を検討してください。

public class Example {

    //this class just uses the reference as you suggest
    public static class ArrayEater {

        private final int[] food;

        public ArrayEater(final int[] food) {
            this.food = food; // references same array, does not crate copy
        }

        public void eat() {
            for (int index = 0; index < food.length; index++) {
                final int bite = food[index];
                if (bite == 0) {
                    System.out.println("No food at position " + index);
                } else {
                    System.out.println("Eating " + bite + " from position " + index);
                }
                food[index] = 0;
            }
        }
    }

    //this class makes an actual copy
    public static class ArrayCopyThenEatEater {

        private final int[] food;

        public ArrayCopyThenEatEater(final int[] food) {
            this.food = new int[food.length]; // creates new array
            for (int index = 0; index < food.length; index++) { //copies over the values
                this.food[index] = food[index];
            }
        }

        public void eat() {
            for (int index = 0; index < food.length; index++) {
                final int bite = food[index];
                if (bite == 0) {
                    System.out.println("No food at position " + index);
                } else {
                    System.out.println("Eating " + bite + " from position " + index);
                }
                food[index] = 0;
            }
        }
    }

    public static void main(String[] args) {

        int[] originalArray = {1,3,6,9};
        ArrayEater eater = new ArrayEater(originalArray);
        eater.eat(); 
        eater.eat(); 
        for (int index = 0; index < originalArray.length; index++) {
            System.out.println("Original array has value of " + originalArray[index] + " at position " + index);
        }

        originalArray = new int[]{1,3,6,9};
        ArrayCopyThenEatEater copyEater = new ArrayCopyThenEatEater(originalArray);
        copyEater.eat(); 
        copyEater.eat(); 
        for (int index = 0; index < originalArray.length; index++) {
            System.out.println("Original array has value of " + originalArray[index] + " at position " + index);
        }
    }

}

メイン メソッドを見ると、originalArrayが作成され、2 つの「イーター」に渡されていることがわかります。イーターの1つは、あなたが提案するようにoriginalArrayを参照するだけで、もう1つのイーターは実際のコピーを作成します(あなたの教授が言ったように、このコピーはプリミティブにとって浅くて深いです)。

これを実行すると、次の出力が作成されます。

Eating 1 from position 0
Eating 3 from position 1
Eating 6 from position 2
Eating 9 from position 3
No food at position 0
No food at position 1
No food at position 2
No food at position 3
Original array has value of 0 at position 0  <-- here we see that the eater ate the original!!
Original array has value of 0 at position 1
Original array has value of 0 at position 2
Original array has value of 0 at position 3
Eating 1 from position 0
Eating 3 from position 1
Eating 6 from position 2
Eating 9 from position 3
No food at position 0
No food at position 1
No food at position 2
No food at position 3
Original array has value of 1 at position 0 <-- here we see that the eater did not eat the original!!
Original array has value of 3 at position 1
Original array has value of 6 at position 2
Original array has value of 9 at position 3

上記を見ると、教授が提案したコピーアプローチでは元の配列が変更されないのに対し、あなたのアプローチでは元の配列のコンテンツが上書きされることがわかります。


非プリミティブの場合

ここでは、Food食べられた、または食べられていないクラスがあります。参照コピー、浅いコピー、深いコピーにそれぞれ 1 つずつ、合計 3 つのイーターがあります。

public class Example {

    public static class Food implements Cloneable {
        private boolean eaten = false;
        public void eat() {
            eaten = true;
        }
        public boolean isEaten() {
            return eaten;
        }
        public Food clone() {
            try {
                return (Food) super.clone();
            } catch (CloneNotSupportedException e) {
                return null; //we won't get here
            }
        }
    }

    public static class ReferenceEater {

        private final Food[] food;

        public ReferenceEater(final Food[] food) {
            this.food = food; // references same array, does not crate copy
        }

        public void eat() {
            for (int index = 0; index < food.length; index++) {
                final Food bite = food[index];
                if (bite.isEaten()) {
                    System.out.println("No food at position " + index);
                } else {
                    System.out.println("Eating from position " + index);
                    bite.eat();
                }
            }
        }
    }

    public static class ShallowEater {

        private final Food[] food;

        public ShallowEater(final Food[] food) {
            this.food = new Food[food.length]; // creates new array
            for (int index = 0; index < food.length; index++) {
                this.food[index] = food[index]; //shallow copy still references same elements!
            }
        }

        public void eat() {
            for (int index = 0; index < food.length; index++) {
                final Food bite = food[index];
                if (bite.isEaten()) {
                    System.out.println("No food at position " + index);
                } else {
                    System.out.println("Eating from position " + index);
                    bite.eat();
                }
            }
        }
    }

    public static class DeepEater {

        private final Food[] food;

        public DeepEater(final Food[] food) {
            this.food = new Food[food.length]; // creates new array
            for (int index = 0; index < food.length; index++) {
                this.food[index] = food[index].clone(); //deep copy also copies the elements!
            }
        }

        public void eat() {
            for (int index = 0; index < food.length; index++) {
                final Food bite = food[index];
                if (bite.isEaten()) {
                    System.out.println("No food at position " + index);
                } else {
                    System.out.println("Eating from position " + index);
                    bite.eat();
                }
            }
        }
    }

    public static void main(String[] args) {

        Food[] originalArray = {new Food(), new Food(), new Food()};
        ReferenceEater referenceEater = new ReferenceEater(originalArray);
        referenceEater.eat(); 
        referenceEater.eat(); 
        for (int index = 0; index < originalArray.length; index++) {
            System.out.println("Food at position " + index + " has been eaten?  " + originalArray[index].isEaten());
        }

        originalArray = new Food[]{new Food(), new Food(), new Food()};
        ShallowEater shallowEater = new ShallowEater(originalArray);
        shallowEater.eat(); 
        shallowEater.eat(); 
        for (int index = 0; index < originalArray.length; index++) {
            System.out.println("Food at position " + index + " has been eaten?  " + originalArray[index].isEaten());
        }

        originalArray = new Food[]{new Food(), new Food(), new Food()};
        DeepEater deepEater = new DeepEater(originalArray);
        deepEater.eat(); 
        deepEater.eat(); 
        for (int index = 0; index < originalArray.length; index++) {
            System.out.println("Food at position " + index + " has been eaten?  " + originalArray[index].isEaten());
        }
    }

}

ディープ コピーでは、配列の個々の要素もコピーされることに注意してください。

次に、出力を見てください。

Eating from position 0
Eating from position 1
Eating from position 2
No food at position 0
No food at position 1
No food at position 2
Food at position 0 has been eaten?  true
Food at position 1 has been eaten?  true
Food at position 2 has been eaten?  true
Eating from position 0
Eating from position 1
Eating from position 2
No food at position 0
No food at position 1
No food at position 2
Food at position 0 has been eaten?  true
Food at position 1 has been eaten?  true
Food at position 2 has been eaten?  true
Eating from position 0
Eating from position 1
Eating from position 2
No food at position 0
No food at position 1
No food at position 2
Food at position 0 has been eaten?  false
Food at position 1 has been eaten?  false
Food at position 2 has been eaten?  false

ここで、プリミティブの場合と同様に、参照コピーによって元のコピーが再び食べられることがわかります。しかし、上記のプリミティブのディープ/シャロー コピーと同じように得られるシャロー コピーを見ると、今回はオリジナルで食べ物がすべて食べられています (プリミティブとは異なります!)。これは、新しい配列を作成しましたが、同じ Food インスタンスへの参照を渡したためです。最後に、ディープ コピーを使用すると、元の配列の食品アイテムが食べられていないことがわかります。これは、食べる人がそれらのアイテムのクローンを食べたためです。

于 2012-10-31T21:14:18.750 に答える
0

あなたの例は配列を使用しているので、例としてそれらを使用することに固執します。配列は、実際には (型に基づいて) 「分割された」メモリ ブロックにすぎません。int[] a などによって保持される値は、その配列の開始メモリ アドレスです。int[] a = someOtherArray を実行すると、格納されている値ではなく、別のメモリ位置のアドレスが割り当てられます。したがって、要素ごとに移動し、各場所に格納されている値を割り当てる必要があります。

于 2012-10-31T20:28:15.273 に答える