Objekte in Java duplizieren

74

Ich habe gelernt, dass beim Ändern einer Variablen in Java die Variable, auf der sie basiert, nicht geändert wird

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

Ähnliches habe ich für Objekte angenommen. Betrachten Sie diese Klasse.

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;
    }
}

Nachdem ich diesen Code ausprobiert hatte, war ich verwirrt.

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

Bitte erklären Sie mir, warum sich das Ändern eines der Objekte auf das andere auswirkt. Ich verstehe, dass der Wert von variablem Text für beide Objekte an derselben Stelle im Speicher gespeichert ist.

Warum sind die Werte für Variablen unabhängig, aber für Objekte korreliert?

Wie kann man SomeObject duplizieren, wenn eine einfache Zuweisung nicht ausreicht?

user1581900
quelle
23
Willkommen zu den Referenztypen in Java, docstore.mik.ua/orelly/java-ent/jnut/ch02_10.htm .
Vikdor
5
Willkommen bei Stackoverflow. Dies sind die Grundlagen von Java. Sie sollten zuerst das Java-Konzept durchgehen. Verwenden Sie dieses Tutorial .
Pratikabu
2
Ich denke, das Beispiel ist verwirrend, obwohl die gewählte Lösung richtig ist: Integerist ein Objectund kein primitiver Typ. Wenn Sie schreiben Integer b = a;, bbezieht sich auf dieselbe Instanz wie a. Beim Schreiben b = b+b;bezieht sich b jedoch auf eine neue Instanz, die vom +Operator erstellt wird.
Vince
Es ist offtopic, aber rufen Sie keine überschreibbare Methode in Ihrem Konstruktor stackoverflow.com/questions/3404301/… auf .
Halex
Möglicherweise möchten Sie dieses String-Textattribut auch als privat deklarieren, da Sie Getter- und Setter-Methoden bereitstellen, um damit zu arbeiten. Sie verbergen also die Implementierungsdetails und stellen eine Klasse mit besserer Abstraktion bereit (im Allgemeinen bessere OO-Praxis)
stoldark

Antworten:

134

Jede Variable in Java ist eine Referenz . Also wenn du es tust

SomeClass s2 = s1;

Sie zeigen nur s2auf dasselbe Objekt wie s1auf. Sie weisen SomeClasss2 tatsächlich den Wert der Referenz s1 zu (die auf eine Instanz von verweist ). Wenn Sie Änderungen vornehmen s1, s2werden diese ebenfalls geändert (da sie auf dasselbe Objekt verweisen).

Es gibt eine Ausnahme, primitive Typen : int, double, float, boolean, char, byte, short, long. Sie werden nach Wert gespeichert. Wenn Sie also verwenden =, weisen Sie nur den Wert zu, aber sie können nicht auf dasselbe Objekt verweisen (da es sich nicht um Referenzen handelt). Das bedeutet, dass

int b = a;

setzt nur den Wert von bauf den Wert von a. Wenn Sie sich ändern a, bwird sich nicht ändern.

Letztendlich ist alles eine Zuordnung nach Wert, es ist nur der Wert der Referenz und nicht der Wert des Objekts (mit Ausnahme der oben erwähnten primitiven Typen).

Wenn Sie in Ihrem Fall eine Kopie davon erstellen möchten s1, können Sie dies folgendermaßen tun:

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

Alternativ können Sie einen Kopierkonstruktor hinzufügen SomeClass, der eine Instanz als Argument verwendet und in eine eigene Instanz kopiert.

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

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

Mit diesem können Sie ein Objekt ziemlich einfach kopieren:

SomeClass s2 = new SomeClass(s1);
Brimborium
quelle
13
+1 Referenzen und Grundelemente werden nach Wert kopiert. Es kommt zu einer Verwirrung, bei der Sie nicht zwischen der Referenz und dem referenzierten Objekt unterscheiden. ;)
Peter Lawrey
15
s1.clone()ist manchmal eine Option.
Ryan Amos
3
Sie können s1.clone () verwenden, um ein Objekt nach s2 zu kopieren. Es wird kein Verweis auf s2 übergeben, sondern die Daten von s1 nach s2 kopiert.
Mayur Raiyani
3
@ MayurRaiyani Warum nicht Java-Klon-Methode verwenden
Brimborium
4
Jede Variable in Java speichert einen Wert . Und da Klassen Referenztypen sind, ist der Wert in einer klassentypisierten Variablen eine Referenz . Wenn Sie eine Zuweisung wie in der ersten Anweisung in dieser Antwort oder in der Frage vornehmen, kopieren Sie eine Referenz nach Wert . Sie kopieren nichts als Referenz.
Adam Robinson
40

Die Antwort von @ brimborium ist sehr gut (+1 für ihn), aber ich möchte nur anhand einiger Zahlen näher darauf eingehen. Nehmen wir zuerst die primitive Aufgabe:

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- Die erste Anweisung erstellt ein Integer-Objekt mit dem Wert 5. aWenn Sie es dann der Variablen zuweisen, wird das Integer-Objekt entpackt und als Grundelement gespeichert a.

Nach dem Erstellen eines Integer-Objekts und vor der Zuweisung:

Geben Sie hier die Bildbeschreibung ein

Nach der Zuordnung:

Geben Sie hier die Bildbeschreibung ein

int b = a;

2- Dies liest nur den Wert von aund speichert ihn dann in b.

(Das Integer-Objekt ist jetzt für die Speicherbereinigung berechtigt, aber zu diesem Zeitpunkt noch nicht unbedingt für die Speicherbereinigung.)

Geben Sie hier die Bildbeschreibung ein

b = b + b;

3- Dies liest den Wert von bzweimal, addiert sie und platziert den neuen Wert in b.

Geben Sie hier die Bildbeschreibung ein


Auf der anderen Seite:

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- Erstellt eine neue Instanz der SomeObjectKlasse und weist sie der Referenz zu s1.

Geben Sie hier die Bildbeschreibung ein

SomeObject s2 = s1;

2- Dadurch werden die Referenzpunkte s2auf das Objekt gesetzt, s1auf das gezeigt wird.

Geben Sie hier die Bildbeschreibung ein

s2.setText("second");

3- Wenn Sie Setter für eine Referenz verwenden, wird das Objekt geändert, auf das die Referenz zeigt.

Geben Sie hier die Bildbeschreibung ein

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

4- Beide sollten drucken second, da die beiden Referenzen s1und s2beziehen sich auf das gleiche Objekt (wie in der vorherigen Abbildung gezeigt).

Eng.Fouad
quelle
1
Cool, +1 für deine Antwort. Obwohl ich einige Ergänzungen vorschlagen möchte: 1) Die Bilder könnten etwas kleiner sein. 2) Sie sollten eine Antwort auf seine andere Frage hinzufügen (wie man sie dupliziert SomeObject), möglicherweise auch mit Bildern.
Brimborium
16

Wenn du das machst

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

Sie haben 2 Verweise auf dasselbe Objekt. Dies bedeutet, dass unabhängig davon, welches Referenzobjekt Sie verwenden, die von Ihnen vorgenommenen Änderungen bei Verwendung der zweiten Referenz sichtbar sind.

Stellen Sie sich das so vor: Sie haben einen Fernseher im Raum, aber zwei Fernbedienungen: Es spielt keine Rolle, welche Fernbedienung Sie verwenden, Sie werden immer noch Änderungen an demselben zugrunde liegenden Objekt (dem Fernseher) vornehmen.

Davek
quelle
5
+1, ich mag die TV-Fernanalogie. Ich denke, Primitive sind Fernseher ohne Fernbedienung, bei denen Sie physisch zum eigentlichen Gerät gehen und den Wert ändern müssen ... oder so? :)
Alex
10

Wenn Sie schreiben:

SomeObject s1 = new SomeObject("first");

s1ist nicht ein SomeObject. Es ist ein Verweis auf das SomeObjectObjekt.

Wenn Sie also s2 s1 zuweisen, weisen Sie einfach Referenzen / Handles zu, und das zugrunde liegende Objekt ist dasselbe. Dies ist in Java wichtig. Alles ist pass-by-value, aber Sie geben niemals Objekte weiter - nur Verweise auf Objekte.

Wenn Sie also s1 = s2eine Methode zuweisen und dann aufrufen s2, mit der ein Objekt geändert wird, wird das zugrunde liegende Objekt geändert. Dies wird angezeigt, wenn Sie auf das Objekt über verweisen s1.

Dies ist ein Argument für die Unveränderlichkeit in Objekten. Indem Sie Objekte unveränderlich machen, ändern sie sich nicht unter Ihnen und verhalten sich daher vorhersehbarer. Wenn Sie ein Objekt kopieren möchten, besteht die einfachste / praktischste Methode darin, eine copy()Methode zu schreiben , die einfach eine neue Version erstellt und über die Felder kopiert. Sie können clevere Kopien mit Serialisierung / Reflexion usw. erstellen, aber das ist natürlich komplexer.

Brian Agnew
quelle
+1 für die Erwähnung des Konzepts der Unveränderlichkeit. Dies ist zusammen mit dem Konzept der Referenzen ein Schlüsselelement in dieser Diskussion.
Marvo
4

Von den zehn häufigsten Fehlern, die Java-Programmierer machen :

6 - Verwirrung über das Übergeben von Werten und Übergeben von Referenzen

Dies kann ein frustrierendes Problem bei der Diagnose sein, denn wenn Sie sich den Code ansehen, können Sie sicher sein, dass er als Referenz übergeben wird, aber feststellen, dass er tatsächlich als Wert übergeben wird. Java verwendet beide, daher müssen Sie verstehen, wann Sie einen Wert übergeben und wann Sie einen Verweis übergeben.

Wenn Sie einen primitiven Datentyp wie char, int, float oder double an eine Funktion übergeben, übergeben Sie den Wert. Das bedeutet, dass eine Kopie des Datentyps dupliziert und an die Funktion übergeben wird. Wenn die Funktion diesen Wert ändert, wird nur die Kopie geändert. Sobald die Funktion beendet ist und die Steuerung an die Rückgabefunktion zurückgegeben wird, bleibt die "echte" Variable unberührt und es wurden keine Änderungen gespeichert. Wenn Sie einen primitiven Datentyp ändern müssen, machen Sie ihn zu einem Rückgabewert für eine Funktion oder schließen Sie ihn in ein Objekt ein.

Da intes sich um einen primitiven Typ handelt, int b = a;handelt es sich um eine Kopie nach Wert, was bedeutet, dass aund bzwei verschiedene Objekte sind, jedoch mit demselben Wert.

SomeObject s2 = s1;make s1und s2zwei Referenzen desselben Objekts. Wenn Sie also eines ändern, wird auch das andere geändert.

Eine gute Lösung besteht darin, einen anderen Konstruktor wie folgt zu implementieren:

public class SomeObject{

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

    // your code

}

Dann benutze es so:

SomeObject s2 = new SomeObject(s1);
sp00m
quelle
Die Referenz, die Sie in "Top Ten Errors Java Programmers make" angegeben haben, passt nicht in diesen Kontext, da es sich um Grundelemente und nicht um Objektreferenzen handelt.
Drona
2

In Ihrem Code s1und s2sind das gleiche Objekt (Sie haben nur ein Objekt mit erstellt new) und Sie lassen s2in der nächsten Zeile auf das gleiche Objekt zeigen. Wenn Sie dies ändern text, ändert sich daher beides, wenn Sie den Wert durch s1und referenzieren s2.

Der +Operator für Ganzzahlen erstellt ein neues Objekt und ändert das vorhandene Objekt nicht (das Hinzufügen von 5 + 5 ergibt also nicht 5 den neuen Wert 10 ...).

Mathias Schwarz
quelle
2

Das liegt daran, dass die JVM einen Zeiger auf speichert s1. Wenn Sie anrufen s2 = s1, sagen Sie im Grunde, dass der s2Zeiger (dh die Speicheradresse) den gleichen Wert hat wie der für s1. Da beide auf dieselbe Stelle im Gedächtnis verweisen, repräsentieren sie genau dasselbe.

Der =Operator weist Zeigerwerte zu. Das Objekt wird nicht kopiert.

Das Klonen von Objekten ist an sich schon eine komplizierte Angelegenheit. Jedes Objekt verfügt über eine clone () -Methode, die Sie möglicherweise verwenden möchten, die jedoch eine flache Kopie erstellt (was im Grunde bedeutet, dass eine Kopie des Objekts der obersten Ebene erstellt wird, aber alle darin enthaltenen Objekte nicht geklont werden). Wenn Sie mit Objektkopien herumspielen möchten, lesen Sie unbedingt Joshua Blochs Effective Java .

mprivat
quelle
2

Beginnen wir mit Ihrem zweiten Beispiel:

Diese erste Anweisung weist ein neues Objekt zu s1

SomeObject s1 = new SomeObject("first");

Wenn Sie die Zuweisung in der zweiten Anweisung ( SomeObject s2 = s1) vornehmen s2, sagen Sie, dass Sie auf dasselbe Objekt s1zeigen sollen, auf das Sie gerade zeigen, sodass Sie zwei Verweise auf dasselbe Objekt haben.

Beachten Sie, dass Sie das nicht dupliziert haben SomeObject, sondern dass zwei Variablen nur auf dasselbe Objekt zeigen. Wenn Sie also etwas ändern s1oder ändern s2, ändern Sie tatsächlich dasselbe Objekt (beachten Sie, wenn Sie so etwas getan haben s2 = new SomeObject("second"), würden sie jetzt auf verschiedene Objekte zeigen).

In Ihrem ersten Beispiel aund bsind primitive Werte, so modifiziert man die andere nicht beeinflussen.

Unter der Haube von Java arbeiten alle Objekte mit Pass-by-Wert. Bei Objekten übergeben Sie den übergebenen "Wert" als Speicherort des Objekts im Speicher (es scheint also einen ähnlichen Effekt der Referenzübergabe zu haben). Grundelemente verhalten sich anders und übergeben nur eine Kopie des Werts.

Jeff Storey
quelle
2

Die obigen Antworten erklären das Verhalten, das Sie sehen.

Als Antwort auf "Wie kann man SomeObject duplizieren, wenn eine einfache Zuweisung den Job nicht erledigt?" - Versuchen Sie, nach cloneable(es ist eine Java-Oberfläche, die eine Möglichkeit zum Kopieren von Objekten bietet) und ' copy constructors' (ein alternativer und wohl besserer Ansatz) zu suchen.

matt freake
quelle
2

Die Objektzuweisung zu einer Referenz klont Ihr Objekt nicht. Referenzen sind wie Zeiger. Sie zeigen auf ein Objekt, und wenn Operationen aufgerufen werden, erfolgt dies für das Objekt, auf das der Zeiger zeigt. In Ihrem Beispiel zeigen s1 und s2 auf dasselbe Objekt, und Setter ändern den Status desselben Objekts, und die Änderungen sind in allen Referenzen sichtbar.

Drona
quelle
2

Ändern Sie Ihre Klasse, um eine neue Referenz zu erstellen, anstatt dieselbe zu verwenden:

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);
    }
}

Sie können so etwas verwenden (ich gebe nicht vor, eine ideale Lösung zu haben):

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;
    }
}

Verwendung:

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
Drachen
quelle
2
int a = new Integer(5) 

Im obigen Fall wird eine neue Ganzzahl erstellt. Hier ist Integer ein nicht primitiver Typ, und der darin enthaltene Wert wird konvertiert (in ein int) und einem int 'a' zugewiesen.

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

In diesem Fall sind sowohl s1 als auch s2 Referenztypen. Sie sind nicht so erstellt, dass sie einen Wert wie primitive Typen enthalten, sondern enthalten die Referenz eines Objekts. Zum besseren Verständnis können wir Referenz als einen Link betrachten, der angibt, wo ich das Objekt finde, auf das verwiesen wird.

Hier sagt uns die Referenz s1, wo wir den Wert von "first" finden können (der in einer Instanz von SomeObject tatsächlich im Speicher des Computers gespeichert ist). Mit anderen Worten, s1 ist die Adresse des Objekts der Klasse "SomeObject". Durch die folgende Zuordnung -

SomeObject s2 = s1;

Wir kopieren nur den in s1 gespeicherten Wert nach s2 und wissen jetzt, dass s1 die Adresse der Zeichenfolge "first" enthält. Nach dieser Zuordnung erzeugen beide println () dieselbe Ausgabe, da sowohl s1 als auch s2 dasselbe Objekt auffrischen.

Zusammen mit dem Kopierkonstruktor können Sie Objekte mit der Methode clone () kopieren, wenn Sie ein Java-Benutzer sind. Der Klon kann auf folgende Weise verwendet werden:

SomeObject s3 = s1.clone(); 

Weitere Informationen zu clone () finden Sie unter http://en.wikipedia.org/wiki/Clone_%28Java_method%29

HassanF
quelle
1

Das liegt daran s1und s2funktioniert nur als Referenz auf Ihre Objekte. Bei der Zuweisung s2 = s1weisen Sie nur die Referenz zu, was bedeutet, dass beide auf dasselbe Objekt im Speicher verweisen (das Objekt mit dem aktuellen Text "first").

Wenn Sie jetzt einen Setter ausführen, entweder auf s1 oder s2, ändern beide dasselbe Objekt.

Raul Rene
quelle
1

Wenn Sie einer Variablen ein Objekt zuweisen und ein Objekt zuweisen, weisen Sie diesem Objekt tatsächlich den Verweis zu. Also verweisen beide Variablen s1und s2auf dasselbe Objekt.

Averroes
quelle
1

Da wurden Objekte durch eine Referenz referenziert. Wenn Sie also s2 = s1 schreiben, wird nur die Referenz kopiert. Es ist wie in C, wo Sie nur mit Speicheradressen umgehen. Wenn Sie die Adresse eines Speichers kopieren und den Wert hinter dieser Adresse ändern, ändern beide Zeiger (Referenzen) den einen Wert an dieser Stelle im Speicher.

BigAl
quelle
1

Die zweite Zeile ( SomeObject s2 = s1;) weist der ersten einfach die zweite Variable zu. Dies führt dazu, dass die zweite Variable auf dieselbe Objektinstanz wie die erste zeigt.

01es
quelle