web-dev-qa-db-ja.com

Javaでのオブジェクトの複製

Javaで変数を変更しても、それが基づいている変数は変更されないことを学びました

int a = new Integer(5);
int b = a;
b = b + b;
System.out.println(a); // 5 as expected
System.out.println(b); // 10 as expected

オブジェクトについても同様のことを想定しました。このクラスを検討してください。

public class SomeObject {
    public String text;

    public SomeObject(String text) {
        this.setText(text);
    }

    public String getText() {
        return text;
    }   

    public void setText(String text) {
        this.text = text;
    }
}

このコードを試した後、混乱しました。

SomeObject s1 = new SomeObject("first");
SomeObject s2 = s1;
s2.setText("second");
System.out.println(s1.getText()); // second as UNexpected
System.out.println(s2.getText()); // second as expected

オブジェクトのいずれかを変更すると、他のオブジェクトに影響する理由を説明してください。可変テキストの値は、両方のオブジェクトのメモリ内の同じ場所に保存されることを理解しています。

変数の値が独立しているのにオブジェクトに対して相関しているのはなぜですか?

また、単純な割り当てで役に立たない場合、SomeObjectを複製する方法は?

70
user1581900

Java=のすべての変数は参照です。

SomeClass s2 = s1;

s2が指しているのと同じオブジェクトをs1で指しているだけです。実際には、参照s1(SomeClassのインスタンスを指す)の値をs2に割り当てています。 s1を変更すると、s2も変更されます(同じオブジェクトを指しているため)。

例外があります、プリミティブ型:int, double, float, boolean, char, byte, short, long。それらは値ごとに保存されます。したがって、=を使用する場合、値を割り当てるだけですが、同じオブジェクトを指すことはできません(参照ではないため)。この意味は

int b = a;

bの値をaの値にのみ設定します。 aを変更しても、bは変更されません。

結局のところ、すべては値による割り当てであり、それは参照の値であり、オブジェクトの値ではありません(上記のプリミティブ型を除く)。

そのため、s1のコピーを作成したい場合、次のようにできます。

SomeClass s1 = new SomeClass("first");
SomeClass s2 = new SomeClass(s1.getText());

または、コピーコンストラクターをSomeClassに追加して、引数としてインスタンスを取り、それを独自のインスタンスにコピーすることができます。

class SomeClass {
  private String text;
  // all your fields and methods go here

  public SomeClass(SomeClass copyInstance) {
    this.text = new String(copyInstance.text);
  }
}

これにより、オブジェクトを非常に簡単にコピーできます。

SomeClass s2 = new SomeClass(s1);
128
brimborium

@brimboriumの答えは非常に良い(彼にとって+1)が、数字を使ってさらに詳しく説明したい。最初にプリミティブの割り当てを見てみましょう。

int a = new Integer(5);
int b = a;
b = b + b;
System.out.println(a); // 5 as expected
System.out.println(b); // 10 as expected
int a = new Integer(5);

1-最初のステートメントは、値5の整数オブジェクトを作成します。次に、変数aに割り当てると、整数オブジェクトはボックス化されず、プリミティブとしてaに格納されます。

整数オブジェクトを作成した後、割り当ての前に:

enter image description here

割り当て後:

enter image description here

int b = a;

2-これは、単にaの値を読み取り、bに格納します。

(Integerオブジェクトはガベージコレクションの対象になりましたが、現時点では必ずしもガベージコレクションされているとは限りません)

enter image description here

b = b + b;

3-これは、bの値を2回読み取り、それらを加算して、新しい値をbに配置します。

enter image description here


一方:

SomeObject s1 = new SomeObject("first");
SomeObject s2 = s1;
s2.setText("second");
System.out.println(s1.getText()); // second as UNexpected
System.out.println(s2.getText()); // second as expected
SomeObject s1 = new SomeObject("first");

1- SomeObjectクラスの新しいインスタンスを作成し、それを参照s1に割り当てます。

enter image description here

SomeObject s2 = s1;

2-これにより、参照はs2s1が指しているオブジェクトを指します。

enter image description here

s2.setText("second");

3-参照でセッターを使用すると、参照が指しているオブジェクトが変更されます。

enter image description here

System.out.println(s1.getText());
System.out.println(s2.getText());

4- 2つの参照s1s2が同じオブジェクトを参照しているため(前の図を参照)、両方ともsecondを出力するはずです。

38
Eng.Fouad

これをするとき

SomeObject s1 = new SomeObject("first");
SomeObject s2 = s1;

同じオブジェクトへの2つの参照があります。つまり、どちらの参照オブジェクトを使用しても、2番目の参照を使用すると、変更内容が表示されます。

次のように考えてください。部屋にはテレビが1台ありますが、リモコンは2台あります。どちらのリモコンを使用するかは関係ありません。

15
davek

あなたが書くとき:

_SomeObject s1 = new SomeObject("first");
_

_s1_はSomeObjectではありません。 SomeObjectオブジェクトへの参照です。

したがって、s2をs1に割り当てる場合、単に参照/ハンドルを割り当てるだけで、基礎となるオブジェクトは同じです。これはJavaでは重要です。すべては値渡しですが、オブジェクトを渡すことはなく、オブジェクトへの参照のみを渡します。

したがって、_s1 = s2_を割り当て、オブジェクトを変更する_s2_のメソッドを呼び出すと、基になるオブジェクトが変更され、_s1_を介してオブジェクトを参照するときに表示されます。

これは、オブジェクトの不変性の1つの引数です。オブジェクトを不変にすることで、オブジェクトはあなたの下で変化せず、より予測しやすい方法で動作します。オブジェクトをコピーする場合、最も簡単で実用的な方法は、新しいバージョンを作成してフィールドをコピーするcopy()メソッドを記述することです。シリアライゼーション/リフレクションなどを使用して巧妙なコピーを作成できますが、明らかにそれはより複雑です。

10
Brian Agnew

From Top Ten Errors Java Programmers Make)

6-値渡しと参照渡しに関する混乱

これは、コードを見ると、参照渡しであると確信しているが、実際には値渡しされていることに気付く可能性があるため、診断するのはイライラする問題です。 Javaは両方を使用するため、値渡しの場合と参照渡しの場合を理解する必要があります。

Char、int、float、doubleなどのプリミティブデータ型を関数に渡すと、値で渡します。つまり、データ型のコピーが複製され、関数に渡されます。関数がその値を変更することを選択した場合、コピーのみが変更されます。関数が終了し、制御が戻り関数に返されると、「実際の」変数は変更されず、変更は保存されません。プリミティブデータ型を変更する必要がある場合は、それを関数の戻り値にするか、オブジェクト内にラップします。

intはプリミティブ型であるため、int b = a;は値によるコピーです。つまり、abは2つの異なるオブジェクトですが、同じ値を持ちます。

SomeObject s2 = s1; make s1およびs2同じオブジェクトの2つの参照。したがって、一方を変更すると、もう一方も変更されます。

良い解決策は、次のような別のコンストラクターを実装することです。

public class SomeObject{

    public SomeObject(SomeObject someObject) {
        setText(someObject.getText());
    }

    // your code

}

次に、そのように使用します:

SomeObject s2 = new SomeObject(s1);
4
sp00m

コードでs1s2同じオブジェクト(newを使用して1つのオブジェクトのみを作成しました)であり、s2が指すようにします次の行の同じオブジェクト。したがって、textを変更すると、s1およびs2を介して値を参照する場合に両方が変更されます。

整数の+演算子はnewオブジェクトを作成し、既存のオブジェクトを変更しません(したがって、5 + 5を追加しても5に新しい値10は与えられません)。

2
Mathias Schwarz

これは、JVMがs1へのポインターを格納するためです。 s2 = s1を呼び出すとき、s2ポインター(つまり、メモリアドレス)はs1の値と同じ値を持つと基本的に言っています。どちらもメモリ内の同じ場所を指しているため、まったく同じものを表しています。

=演算子は、ポインター値を割り当てます。オブジェクトはコピーしません。

オブジェクトのクローン作成自体は複雑な問題です。すべてのオブジェクトには、使用したくなるかもしれないclone()メソッドがありますが、浅いコピーを行います(基本的には、最上位オブジェクトのコピーを作成しますが、その中に含まれるオブジェクトは複製されません)。オブジェクトのコピーを試してみたい場合は、必ずJoshua Blochの Effective Java を読んでください。

2
mprivat

2番目の例から始めましょう。

この最初のステートメントは、新しいオブジェクトを_s1_に割り当てます

_SomeObject s1 = new SomeObject("first");
_

2番目のステートメント(_SomeObject s2 = s1_)で割り当てを行うと、_s2_が同じオブジェクトを指すように指示しています_s1_が現在指しているので、同じオブジェクトへの2つの参照があります。

SomeObjectを複製していないことに注意してください。むしろ、2つの変数が同じオブジェクトを指しているだけです。したがって、_s1_または_s2_を変更する場合、実際には同じオブジェクトを変更します(s2 = new SomeObject("second")のようなことをすると、異なるオブジェクトを指すようになります)。

最初の例では、abはプリミティブ値であるため、一方を変更しても他方には影響しません。

Javaの内部では、すべてのオブジェクトは値渡しを使用して機能します。オブジェクトの場合、渡している「値」はメモリ内のオブジェクトの場所です(したがって、参照による受け渡しと同様の効果があるようです)。プリミティブの動作は異なり、値のコピーを渡すだけです。

2
Jeff Storey
int a = new Integer(5) 

上記の場合、新しい整数が作成されます。ここで、Integerは非プリミティブ型で、その中の値は(intに)変換され、int 'a'に割り当てられます。

SomeObject s1 = new SomeObject("first");  
SomeObject s2 = s1;

この場合、s1とs2は両方とも参照型です。これらは、プリミティブ型のような値を含むように作成されているのではなく、何らかのオブジェクトの参照を含んでいます。理解するために、参照は、参照されているオブジェクトを見つけることができる場所を示すリンクと考えることができます。

ここで、参照s1は、最初の値(実際にはSomeObjectのインスタンスでコンピューターのメモリに格納されている)の値を見つけることができます。つまり、s1はクラス「SomeObject」のオブジェクトのアドレスです。次の割り当てによって-

SomeObject s2 = s1;

s1に保存されている値をs2にコピーするだけで、s1に文字列「first」のアドレスが含まれていることがわかります。この同化の後、s1とs2の両方が同じオブジェクトを参照するため、println()は同じ出力を生成します。

Javaユーザーである場合、cloneコンストラクターメソッドを使用してオブジェクトをコピーできます。クローンは次の方法で使用できます-

SomeObject s3 = s1.clone(); 

Clone()の詳細については、これは便利なリンクです http://en.wikipedia.org/wiki/Clone_%28Java_method%29

2
HassanF

上記の答えは、あなたが見ている振る舞いを説明しています。

「また、単純な割り当てでは役に立たない場合、SomeObjectを複製する方法は?」 -cloneable(オブジェクトをコピーする1つの方法を提供するJavaインターフェイス)と 'copy constructors '(別の、そしておそらく、より良いアプローチ)

2
matt freake

参照へのオブジェクトの割り当ては、オブジェクトを複製しません。参照はポインターのようなものです。オブジェクトを指し、操作が呼び出されると、ポインターが指すオブジェクトで実行されます。この例では、s1とs2は同じオブジェクトを指しており、setterは同じオブジェクトの状態を変更し、その変更は参照全体に表示されます。

2
Drona

クラスを変更して、同じ参照を使用する代わりに新しい参照を作成します。

public class SomeObject{

    public String text;

    public SomeObject(String text){
        this.setText(text);
    }

    public String getText(){
        return text;
    }   

    public void setText(String text){
        this.text = new String(text);
    }
}

次のようなものを使用できます(理想的な解決策を持っているふりはしません):

public class SomeObject{

    private String text;

    public SomeObject(String text){
        this.text = text;
    }

    public SomeObject(SomeObject object) {
        this.text = new String(object.getText());
    }

    public String getText(){
        return text;
    }   

    public void setText(String text){
        this.text = text;
    }
}

使用法:

SomeObject s1 = new SomeObject("first");
SomeObject s2 = new SomeObject(s1);
s2.setText("second");
System.out.println(s1.getText()); // first
System.out.println(s2.getText()); // second
2
Dragon

二行目 (SomeObject s2 = s1;)2番目の変数を最初の変数に単純に割り当てます。これにより、2番目の変数が最初の変数と同じオブジェクトインスタンスを指します。

1
01es

s1s2はオブジェクトへの参照としてのみ機能するためです。 s2 = s1を割り当てるときは、参照のみを割り当てます。つまり、両方がメモリ内の同じオブジェクト(現在のテキストが「最初」のオブジェクト)を指します。

S1またはs2でセッターを実行すると、両方が同じオブジェクトを変更します。

1
Raul Rene

オブジェクトを変数に割り当てると、実際にそのオブジェクトへの参照が割り当てられます。したがって、両方の変数s1およびs2は同じオブジェクトを参照しています。

1
Averroes

オブジェクトは参照によって参照されたため。したがって、s2 = s1と記述すると、参照のみがコピーされます。 Cのように、メモリアドレスでのみ処理します。また、メモリのアドレスをコピーし、このアドレスの背後にある値を変更すると、両方のポインター(参照)がメモリ内のこの時点で1つの値を変更します。

1
BigAl