77 votes

Dupliquer des objets en Java

J'ai appris que lorsque vous modifiez une variable en Java, cela ne change pas la variable sur laquelle elle était basée

int a = new Integer(5);
int b = a;
b = b + b;
System.out.println(a); // 5 comme prévu
System.out.println(b); // 10 comme prévu

J'ai supposé une chose similaire pour les objets. Considérez cette classe.

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

Après avoir essayé ce code, je suis devenu confus.

SomeObject s1 = new SomeObject("first");
SomeObject s2 = s1;
s2.setText("second");
System.out.println(s1.getText()); // second comme INattendu
System.out.println(s2.getText()); // second comme prévu

Veuillez m'expliquer pourquoi le changement de l'un des objets affecte l'autre. Je comprends que la valeur de la variable texte est stockée au même endroit dans la mémoire pour les deux objets.

Pourquoi les valeurs des variables sont-elles indépendantes mais corrélées pour les objets?

Aussi, comment dupliquer SomeObject, si une simple affectation ne fait pas le travail?

24 votes

Bienvenue dans les types de référence en Java, docstore.mik.ua/orelly/java-ent/jnut/ch02_10.htm.

5 votes

Bienvenue sur stackoverflow. ce sont les bases de java.. vous devriez d'abord étudier le concept de java.. utilisez ce tutoriel..

2 votes

Je pense que l'exemple est confus, même si la solution choisie est correcte : Integer est un Object et non un type primitif. Si vous écrivez Integer b = a;, b fait référence à la même instance que a. Mais en écrivant b = b+b;, b fait référence à une nouvelle instance créée par l'opérateur +.

140voto

brimborium Points 4563

Chaque variable en Java est une référence. Donc, lorsque vous faites

SomeClass s2 = s1;

vous pointez simplement s2 vers le même objet vers lequel s1 pointe. Vous attribuez en réalité la valeur de la référence s1 (qui pointe vers une instance de SomeClass) à s2. Si vous modifiez s1, s2 sera également modifié (car il pointe vers le même objet).

Il y a une exception, les types primitifs : int, double, float, boolean, char, byte, short, long. Ils sont stockés par valeur. Ainsi, lors de l'utilisation de =, vous attribuez uniquement la valeur, mais ils ne peuvent pas pointer vers le même objet (car ce ne sont pas des références). Cela signifie que

int b = a;

définit uniquement la valeur de b sur la valeur de a. Si vous modifiez a, b ne changera pas.

À la fin de la journée, tout est une assignation par valeur, c'est simplement la valeur de la référence et non la valeur de l'objet (avec l'exception des types primitifs mentionnés ci-dessus).

Donc, dans votre cas, si vous voulez faire une copie de s1, vous pouvez le faire comme ceci :

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

Alternativement, vous pouvez ajouter un constructeur de copie à SomeClass qui prend une instance en argument et la copie dans sa propre instance.

class SomeClass {
  private String texte;
  // tous vos champs et méthodes vont ici

  public SomeClass(SomeClass instanceACopier) {
    this.texte = new String(instanceACopier.texte);
  }
}

Avec cela, vous pouvez copier un objet assez facilement :

SomeClass s2 = new SomeClass(s1);

14 votes

+1 les références et les primitives sont copiées par valeur. La confusion vient lorsque vous ne faites pas la distinction entre la référence et l'objet référencé. ;)

0 votes

Merci beaucoup! Cela répond à toutes mes questions.

15 votes

s1.clone() est parfois une option.

44voto

Eng.Fouad Points 44085

La réponse de @brimborium est très bonne (+1 pour lui), mais je veux simplement élaborer davantage en utilisant quelques chiffres. Commençons par l'assignation primitive :

int a = new Integer(5);
int b = a;
b = b + b;
System.out.println(a); // 5 comme prévu
System.out.println(b); // 10 comme prévu

int a = new Integer(5);

1- La première instruction crée un objet Integer de valeur 5. Ensuite, en l'assignant à la variable a, l'objet Integer sera unboxé et stocké dans a en tant que primitif.

Après la création de l'objet Integer, et avant l'assignation :

entrer la description de l'image ici

Après l'assignation :

entrer la description de l'image ici

int b = a;

2- Cela va simplement lire la valeur de a et la stocker dans b.

(L'objet Integer est maintenant éligible pour la collecte de déchets, mais pas nécessairement encore collecté à ce stade)

entrer la description de l'image ici

b = b + b;

3- Cela lit la valeur de b deux fois, les ajoute ensemble, et place la nouvelle valeur dans b.

entrer la description de l'image ici


En revanche :

SomeObject s1 = new SomeObject("first");
SomeObject s2 = s1;
s2.setText("second");
System.out.println(s1.getText()); // second comme INattendu
System.out.println(s2.getText()); // second comme prévu

SomeObject s1 = new SomeObject("first");

1- Crée une nouvelle instance de la classe SomeObject, et l'assigne à la référence s1.

entrer la description de l'image ici

SomeObject s2 = s1;

2- Cela fera en sorte que la référence s2 pointe vers l'objet vers lequel s1 pointe.

entrer la description de l'image ici

s2.setText("second");

3- Lorsque vous utilisez des setters sur une référence, cela modifiera l'objet vers lequel la référence pointe.

entrer la description de l'image ici

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

4- Les deux devraient afficher second, car les deux références s1 et s2 font référence au même objet (comme le montre la figure précédente).

2 votes

Génial, +1 pour votre réponse. Bien que je souhaite suggérer quelques ajouts : 1) les images pourraient être un peu plus petites. 2) Vous devriez ajouter une réponse à sa autre question (comment dupliquer SomeObject), peut-être aussi avec des images.

16voto

davek Points 12514

Lorsque vous faites cela

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

vous avez 2 références au même objet. Ce qui signifie que peu importe la référence d'objet que vous utilisez, les modifications que vous apportez seront visibles lors de l'utilisation de la deuxième référence.

Pensez-y de cette façon : vous avez une seule télévision dans la pièce, mais deux télécommandes : peu importe quelle télécommande vous utilisez, vous continuerez à apporter des modifications au même objet sous-jacent (la télévision).

6 votes

+1, J'aime l'analogie de la télécommande TV. Je suppose que les primitives sont des téléviseurs sans télécommande, où vous devez vous rendre physiquement sur l'ensemble réel et changer la valeur... ou quelque chose comme ça ? :)

10voto

Brian Agnew Points 143181

Lorsque vous écrivez :

SomeObject s1 = new SomeObject("first");

s1 n'est pas un SomeObject. C'est une référence à l'objet SomeObject.

Par conséquent, si vous assignez s2 à s1, vous assignez simplement des références/gestionnaires et l'objet sous-jacent est le même. C'est important en Java. Tout est passé par valeur, mais vous ne transmettez jamais d'objets - seulement des références vers des objets.

Donc lorsque vous assignez s1 = s2, et que vous appelez ensuite une méthode sur s2 qui modifie un objet, l'objet sous-jacent est modifié, et cela est visible lorsque vous référencez l'objet via s1.

C'est un argument en faveur de l'immuabilité dans les objets. En rendant les objets immuables, ils ne changeront pas sous vous et se comporteront donc de manière plus prévisible. Si vous voulez copier un objet, la méthode la plus simple/la plus pratique est d'écrire une méthode copy() qui crée simplement une nouvelle version et copie les champs. Vous pouvez faire des copies intelligentes en utilisant la sérialisation/la réflexion, etc. mais c'est plus complexe, évidemment.

0 votes

+1 pour la mention du concept d'immuabilité. Cela, ainsi que le concept de références, sont des aspects clés de cette discussion.

4voto

sp00m Points 18767

De Top Ten Errors Java Programmers Make:

6 - Confusion entre passage par valeur et passage par référence

Cela peut être un problème frustrant à diagnostiquer, car lorsque vous regardez le code, vous pourriez être sûr que c'est un passage par référence, mais constater qu'en réalité, il est passé par valeur. Java utilise les deux, donc vous devez comprendre quand vous passez par valeur, et quand vous passez par référence.

Lorsque vous passez un type de données primitif, tel qu'un char, int, float, ou double, à une fonction, alors vous passez par valeur. Cela signifie qu'une copie du type de données est dupliquée et passée à la fonction. Si la fonction choisit de modifier cette valeur, elle modifiera seulement la copie. Une fois que la fonction se termine, et le contrôle est retourné à la fonction appelante, la variable "réelle" restera intacte et aucun changement n'aura été sauvegardé. Si vous avez besoin de modifier un type de données primitif, faites-en une valeur de retour pour une fonction, ou emballez-la à l'intérieur d'un objet.

Parce que int est un type primitif, int b = a; est une copie par valeur, ce qui signifie que a et b sont deux objets différents, mais avec la même valeur.

SomeObject s2 = s1; rend s1 et s2 deux références du même objet, donc si vous en modifiez un, l'autre sera aussi modifié.

Une bonne solution est d'implémenter un autre constructeur comme ceci:

public class SomeObject{

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

    // votre code

}

Ensuite, utilisez-le ainsi:

SomeObject s2 = new SomeObject(s1);

0 votes

La référence que vous avez donnée à partir de "Top Ten Errors Java Programmers make" ne convient pas bien dans ce contexte car elle parle de primitives et non de références d'objets.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X