Java Language Piège: penser les variables comme des objets


Exemple

Aucune variable Java ne représente un objet.

String foo;   // NOT AN OBJECT

Aucun tableau Java ne contient non plus d'objets.

String bar[] = new String[100];  // No member is an object.

Si vous pensez à tort que les variables sont des objets, le comportement réel du langage Java vous surprendra.

  • Pour les variables Java qui ont un type primitif (tel que int ou float ), la variable contient une copie de la valeur. Toutes les copies d'une valeur primitive sont indiscernables. c'est-à-dire qu'il n'y a qu'une seule valeur int pour le numéro un. Les valeurs primitives ne sont pas des objets et ne se comportent pas comme des objets.

  • Pour les variables Java qui ont un type de référence (soit un type de classe, soit un type de tableau), la variable contient une référence. Toutes les copies d'une référence sont indiscernables. Les références peuvent pointer sur des objets ou être null ce qui signifie qu'elles ne pointent vers aucun objet. Cependant, ils ne sont pas des objets et ils ne se comportent pas comme des objets.

Les variables ne sont pas des objets dans les deux cas et elles ne contiennent aucun objet dans les deux cas. Ils peuvent contenir des références à des objets , mais cela dit quelque chose de différent.

Exemple classe

Les exemples suivants utilisent cette classe, qui représente un point dans un espace 2D.

public final class MutableLocation {
   public int x;
   public int y;

   public MutableLocation(int x, int y) {
       this.x = x;
       this.y = y;
   }

   public boolean equals(Object other) {
       if (!(other instanceof MutableLocation) {
           return false;
       }
       MutableLocation that = (MutableLocation) other;
       return this.x == that.x && this.y == that.y;
   }
}

Une instance de cette classe est un objet qui a deux champs x et y qui ont le type int .

Nous pouvons avoir plusieurs instances de la classe MutableLocation . Certains représenteront les mêmes emplacements dans un espace 2D; c'est-à-dire que les valeurs respectives de x et y correspondent. D'autres représenteront des endroits différents.

Plusieurs variables peuvent pointer vers le même objet

 MutableLocation here = new MutableLocation(1, 2);
 MutableLocation there = here;
 MutableLocation elsewhere = new MutableLocation(1, 2);

Dans ce qui précède, nous avons déclaré trois variables here , there et elsewhere pouvant contenir des références aux objets MutableLocation .

Si vous pensez (à tort) que ces variables sont des objets, vous risquez de ne pas les interpréter comme suit:

  1. Copiez l'emplacement "[1, 2]" here
  2. Copiez l'emplacement « [1, 2] » pour there
  3. Copier l'emplacement "[1, 2]" vers elsewhere

À partir de cela, vous pouvez en déduire que nous avons trois objets indépendants dans les trois variables. En fait, il n'y a que deux objets créés par ce qui précède. Les variables here et there se réfèrent effectivement au même objet.

Nous pouvons le démontrer. En supposant les déclarations de variable comme ci-dessus:

System.out.println("BEFORE: here.x is " + here.x + ", there.x is " + there.x +
                   "elsewhere.x is " + elsewhere.x);
here.x = 42;
System.out.println("AFTER: here.x is " + here.x + ", there.x is " + there.x +
                   "elsewhere.x is " + elsewhere.x);

Cela va afficher les éléments suivants:

BEFORE: here.x is 1, there.x is 1, elsewhere.x is 1
AFTER: here.x is 42, there.x is 42, elsewhere.x is 1

Nous avons assigné une nouvelle valeur à here.x et cela a changé la valeur que nous voyons there.x Ils font référence au même objet. Mais la valeur que nous voyons via elsewhere.x n'a pas changé, donc elsewhere doit faire référence à un objet différent.

Si une variable était un objet, l'affectation here.x = 42 ne changerait pas there.x . there.x

L'opérateur d'égalité ne teste PAS que deux objets sont égaux

L'application de l'opérateur d'égalité ( == ) pour référencer les valeurs teste si les valeurs font référence au même objet. Il ne teste pas si deux objets (différents) sont "égaux" au sens intuitif.

 MutableLocation here = new MutableLocation(1, 2);
 MutableLocation there = here;
 MutableLocation elsewhere = new MutableLocation(1, 2);

 if (here == there) {
     System.out.println("here is there");
 }
 if (here == elsewhere) {
     System.out.println("here is elsewhere");
 }

Ceci imprimera "ici est là", mais il n'imprimera pas "ici est ailleurs". (Les références here et elsewhere concernent deux objets distincts.)

En revanche, si nous appelons la méthode equals(Object) que nous avons implémentée ci-dessus, nous allons tester si deux instances de MutableLocation ont un emplacement égal.

 if (here.equals(there)) {
     System.out.println("here equals there");
 }
 if (here.equals(elsewhere)) {
     System.out.println("here equals elsewhere");
 }

Cela imprimera les deux messages. En particulier, here.equals(elsewhere) renvoie true car les critères sémantiques que nous avons choisis pour l'égalité de deux objets MutableLocation ont été satisfaits.

Les appels de méthode ne transmettent PAS d'objets du tout

Les appels de méthode Java utilisent la valeur par défaut 1 pour transmettre les arguments et renvoyer un résultat.

Lorsque vous transmettez une valeur de référence à une méthode, vous transmettez en réalité une référence à un objet par valeur , ce qui signifie qu'il crée une copie de la référence d'objet.

Tant que les deux références d'objet pointent toujours vers le même objet, vous pouvez modifier cet objet à partir de l'une ou l'autre référence, ce qui est source de confusion pour certaines.

Cependant, vous ne passez pas d'objet par référence 2 . La distinction est que si la copie de référence d'objet est modifiée pour pointer vers un autre objet, la référence d'objet d'origine pointe toujours vers l'objet d'origine.

void f(MutableLocation foo) {  
    foo = new MutableLocation(3, 4);   // Point local foo at a different object.
}

void g() {
    MutableLocation foo = MutableLocation(1, 2);
    f(foo);
    System.out.println("foo.x is " + foo.x); // Prints "foo.x is 1".
}

Vous ne transmettez pas non plus une copie de l'objet.

void f(MutableLocation foo) {  
    foo.x = 42;
}

void g() {
    MutableLocation foo = new MutableLocation(0, 0);
    f(foo);
    System.out.println("foo.x is " + foo.x); // Prints "foo.x is 42"
}

1 - Dans les langages comme Python et Ruby, le terme "pass by sharing" est préféré pour "passer par valeur" d'un objet / référence.

2 - Le terme "passer par référence" ou "appeler par référence" a une signification très spécifique dans la terminologie des langages de programmation. En effet, cela signifie que vous passez l'adresse d'une variable ou d'un élément de tableau , de sorte que lorsque la méthode appelée assigne une nouvelle valeur à l'argument formel, elle modifie la valeur de la variable d'origine. Java ne supporte pas cela. Pour une description plus complète des différents mécanismes de transmission des paramètres, reportez-vous à https://en.wikipedia.org/wiki/Evaluation_strategy .