75

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を複製するにはどうすればよいですか?

4

17 に答える 17

136

Javaのすべての変数は参照です。だからあなたがするとき

SomeClass s2 = s1;

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

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

int b = a;

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

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

したがって、あなたの場合、のコピーを作成したい場合は、次の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);
于 2012-08-22T12:08:27.327 に答える
43

@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のIntegerオブジェクトを作成します。次に、それを変数に割り当てるとa、Integerオブジェクトはボックス化されておらずa、プリミティブとして格納されます。

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

ここに画像の説明を入力してください

割り当て後:

ここに画像の説明を入力してください

int b = a;

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

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

ここに画像の説明を入力してください

b = b + b;

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

ここに画像の説明を入力してください


一方で:

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

ここに画像の説明を入力してください

SomeObject s2 = s1;

2-これにより、s2ポイントしているオブジェクトへの参照ポイントが作成s1されます。

ここに画像の説明を入力してください

s2.setText("second");

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

ここに画像の説明を入力してください

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

4- second2つの参照は同じオブジェクトを参照しているため、s1両方とも印刷する必要s2があります(前の図を参照)。

于 2012-09-16T03:22:26.103 に答える
16

あなたがこれをするとき

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

同じオブジェクトへの参照が2つあります。つまり、どの参照オブジェクトを使用しても、2番目の参照を使用したときに行った変更が表示されます。

次のように考えてください。部屋にテレビが1つありますが、リモコンが2つあります。どちらのリモコンを使用しても、同じ基になるオブジェクト(テレビ)に変更を加えることができます。

于 2012-08-22T12:07:33.810 に答える
10

あなたが書くとき:

SomeObject s1 = new SomeObject("first");

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

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

したがって、を割り当ててから、オブジェクトを変更s1 = s2するメソッドを呼び出すとs2、基になるオブジェクトが変更され、を介してオブジェクトを参照すると表示されますs1

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

于 2012-08-22T12:07:39.093 に答える
4

Javaプログラマーが犯すトップ10のエラーから

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

これは診断するのに苛立たしい問題になる可能性があります。コードを見ると、参照によって渡されていることは確かですが、実際には値によって渡されていることがわかるためです。Javaは両方を使用するため、値を渡す場合と参照を渡す場合を理解する必要があります。

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

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

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

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

public class SomeObject{

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

    // your code

}

次に、次のように使用します。

SomeObject s2 = new SomeObject(s1);
于 2012-08-22T12:12:08.257 に答える
2

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

オペレーターは=ポインター値を割り当てます。オブジェクトはコピーされません。

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

于 2012-08-22T12:09:32.090 に答える
2

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

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

于 2012-08-22T12:08:31.030 に答える
2

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

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

SomeObject s1 = new SomeObject("first");

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

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

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

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

于 2012-08-22T12:10:55.517 に答える
2

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

「また、単純な割り当てではうまくいかない場合、SomeObjectを複製するにはどうすればよいですか?」-検索してみてくださいcloneable(これは、オブジェクトをコピーする1つの方法を提供するJavaインターフェイスです)および' copy constructors'(代替の、そして間違いなく、より良いアプローチ)

于 2012-08-22T12:11:04.860 に答える
2

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

于 2012-08-22T12:12:41.517 に答える
2

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

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
于 2012-08-22T12:15:10.257 に答える
2
int a = new Integer(5) 

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

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

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

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

SomeObject s2 = s1;

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

javaユーザーの場合は、copyコンストラクターに加えて、clone()メソッドを使用してオブジェクトをコピーできます。クローンは次のように使用できます-

SomeObject s3 = s1.clone(); 

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

于 2012-09-16T17:11:17.133 に答える
1

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

于 2012-08-22T12:07:23.910 に答える
1

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

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

于 2012-08-22T12:08:32.457 に答える
1

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

于 2012-08-22T12:08:39.737 に答える
1

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

于 2012-08-22T12:11:27.790 に答える
0
T copyDeep(T input) { 
    return new Gson().fromJson(new Gson().toJson(input), T.class);
}
于 2021-09-16T21:15:12.620 に答える