Question Java est-il "référence par référence" ou "valeur de transmission"?


J'ai toujours pensé que Java était passer-par-référence.

Cependant, j'ai vu quelques articles de blog (par exemple, ce blog) qui prétendent que ce n'est pas le cas.

Je ne pense pas comprendre la distinction qu'ils font.

Quelle est l'explication?


5482


origine


Réponses:


Java est toujours passe-en-valeur. Malheureusement, ils ont décidé d'appeler la localisation d'un objet une "référence". Quand nous passons la valeur d'un objet, nous passons le référence à lui. C'est déroutant pour les débutants.

Ça va comme ça:

public static void main(String[] args) {
    Dog aDog = new Dog("Max");
    // we pass the object to foo
    foo(aDog);
    // aDog variable is still pointing to the "Max" dog when foo(...) returns
    aDog.getName().equals("Max"); // true
    aDog.getName().equals("Fifi"); // false 
}

public static void foo(Dog d) {
    d.getName().equals("Max"); // true
    // change d inside of foo() to point to a new Dog instance "Fifi"
    d = new Dog("Fifi");
    d.getName().equals("Fifi"); // true
}

Dans l'exemple ci-dessus aDog.getName() reviendra toujours "Max". La valeur aDog dans main n'est pas changé dans la fonction foo avec le Dog  "Fifi" comme la référence d'objet est passée par valeur. S'il a été adopté par référence, alors le aDog.getName() dans main retournerais "Fifi" après l'appel à foo.

Également:

public static void main(String[] args) {
    Dog aDog = new Dog("Max");
    foo(aDog);
    // when foo(...) returns, the name of the dog has been changed to "Fifi"
    aDog.getName().equals("Fifi"); // true
}

public static void foo(Dog d) {
    d.getName().equals("Max"); // true
    // this changes the name of d to be "Fifi"
    d.setName("Fifi");
}

Dans l'exemple ci-dessus, Fifi est le nom du chien après l'appel à foo(aDog) parce que le nom de l'objet a été défini à l'intérieur de foo(...). Toutes les opérations qui foo effectue sur d sont tels que, à toutes fins pratiques, ils sont effectués sur aDog lui-même (sauf quand d est changé pour pointer vers un autre Dog exemple comme d = new Dog("Boxer")).


4734



Je viens de remarquer que vous avez référencé mon article.

La spécification Java dit que tout ce qui est en Java est la valeur de transmission. Il n'y a pas de "passage par référence" en Java.

La clé pour comprendre cela est que quelque chose comme

Dog myDog;

est ne pas un chien; c'est en fait un aiguille à un chien.

Qu'est-ce que cela signifie, c'est quand vous avez

Dog myDog = new Dog("Rover");
foo(myDog);

vous passez essentiellement la adresse de la création Dog objet à la foo méthode.

(Je dis essentiellement parce que les pointeurs Java ne sont pas des adresses directes, mais il est plus facile de les penser de cette façon)

Supposons que le Dog L'objet réside à l'adresse mémoire 42. Cela signifie que nous passons 42 à la méthode.

si la méthode a été définie comme

public void foo(Dog someDog) {
    someDog.setName("Max");     // AAA
    someDog = new Dog("Fifi");  // BBB
    someDog.setName("Rowlf");   // CCC
}

regardons ce qui se passe.

  • le paramètre someDog est réglé sur la valeur 42
  • à la ligne "AAA"
    • someDog est suivi au Dog il indique (le Dog objet à l'adresse 42)
    • cette Dog (celui à l'adresse 42) est demandé de changer son nom à Max
  • à la ligne "BBB"
    • un nouveau Dog est créé. Disons qu'il est à l'adresse 74
    • nous assignons le paramètre someDog à 74
  • à la ligne "CCC"
    • someDog est suivi à la Dog il indique (le Dog objet à l'adresse 74)
    • cette Dog (celui à l'adresse 74) est demandé de changer son nom à Rowlf
  • alors, nous revenons

Maintenant, réfléchissons à ce qui se passe en dehors de la méthode:

Fait myDog changement?

Il y a la clé.

Gardant à l'esprit que myDog est un aiguilleet pas un réel Dog, La réponse est non. myDog a toujours la valeur 42; il pointe toujours vers l'original Dog(mais notez qu'en raison de la ligne "AAA", son nom est maintenant "Max" - toujours le même chien; myDogLa valeur n'a pas changé.)

C'est parfaitement valable suivre une adresse et changer ce qui est à la fin de celui-ci; cela ne change pas la variable, cependant.

Java fonctionne exactement comme C. Vous pouvez affecter un pointeur, passer le pointeur à une méthode, suivre le pointeur de la méthode et modifier les données pointées. Cependant, vous ne pouvez pas changer l'endroit où ce pointeur pointe.

En C ++, Ada, Pascal et d'autres langages qui supportent la référence par renvoi, vous pouvez réellement changer la variable qui a été transmise.

Si Java avait une sémantique pass-by-reference, le foo méthode que nous avons défini ci-dessus aurait changé où myDog pointait quand il a assigné someDog sur la ligne BBB.

Considérez les paramètres de référence comme des alias pour la variable transmise. Lorsque cet alias est affecté, la variable qui a été transmise l'est également.


2637



Java passe toujours les arguments par valeur NON par référence.


Laissez-moi expliquer cela à travers un Exemple:

public class Main{
     public static void main(String[] args){
          Foo f = new Foo("f");
          changeReference(f); // It won't change the reference!
          modifyReference(f); // It will modify the object that the reference variable "f" refers to!
     }
     public static void changeReference(Foo a){
          Foo b = new Foo("b");
          a = b;
     }
     public static void modifyReference(Foo c){
          c.setAttribute("c");
     }
}

Je vais expliquer cela en étapes:

  1. Déclaration d'une référence nommée f de type Foo et l'assigner à un nouvel objet de type Foo avec un attribut "f".

    Foo f = new Foo("f");
    

    enter image description here

  2. Du côté de la méthode, une référence de type Foo avec un nom a est déclaré et il est initialement affecté à null.

    public static void changeReference(Foo a)
    

    enter image description here

  3. Comme vous appelez la méthode changeReference, le référence a sera affecté à l'objet qui est passé en argument.

    changeReference(f);
    

    enter image description here

  4. Déclaration d'une référence nommée b de type Foo et l'assigner à un nouvel objet de type Foo avec un attribut "b".

    Foo b = new Foo("b");
    

    enter image description here

  5. a = b réattribue la référence a NE PAS f à l'objet dont son attribut est "b".

    enter image description here


  6. Comme vous appelez modifyReference(Foo c) méthode, une référence c est créé et affecté à l'objet avec attribut "f".

    enter image description here

  7. c.setAttribute("c"); va changer l'attribut de l'objet qui référence c pointe vers lui, et c'est le même objet que la référence f pointe vers lui.

    enter image description here

J'espère que vous comprenez maintenant comment passer des objets comme arguments fonctionne en Java :)


1388



Cela vous donnera un aperçu de la façon dont Java fonctionne vraiment au point que dans votre prochaine discussion sur Java en passant par référence ou en passant en valeur vous sourirez seulement :-)

Première étape, effacez de votre esprit ce mot qui commence par 'p' "_ _ _ _ _ _ _", surtout si vous venez d'autres langages de programmation. Java et 'p' ne peuvent pas être écrits dans le même livre, forum, ou même txt.

La deuxième étape se souvient que lorsque vous passez un objet dans une méthode, vous passez la référence de l'objet et non l'objet lui-même.

  • Étudiant: Maître, cela signifie-t-il que Java est passe-par-référence?
  • Maîtriser: Sauterelle, No.

Pensez maintenant à ce que la référence / variable d'un objet fait / est:

  1. Une variable contient les bits qui indiquent à la machine virtuelle Java comment accéder à l'objet Object référencé en mémoire (Heap).
  2. Lors de la transmission d'arguments à une méthode vous NE passez PAS la variable de référence, mais une copie des bits dans la variable de référence. Quelque chose comme ça: 3bad086a. 3bad086a représente un moyen d'accéder à l'objet passé.
  3. Donc vous passez juste 3bad086a que c'est la valeur de la référence.
  4. Vous passez la valeur de la référence et non la référence elle-même (et non l'objet).
  5. Cette valeur est effectivement COPIÉE et donnée à la méthode.

Dans ce qui suit (n'essayez pas de compiler / exécuter ceci ...):

1. Person person;
2. person = new Person("Tom");
3. changeName(person);
4.
5. //I didn't use Person person below as an argument to be nice
6. static void changeName(Person anotherReferenceToTheSamePersonObject) {
7.     anotherReferenceToTheSamePersonObject.setName("Jerry");
8. }

Ce qui se produit?

  • La variable la personne est créé à la ligne 1 et il est nul au début.
  • Un nouvel objet Personne est créé à la ligne 2, stocké en mémoire et la variable la personne est donné la référence à l'objet Personne. C'est, son adresse. Disons 3bad086a.
  • La variable la personne en maintenant l'adresse de l'objet est passée à la fonction dans la ligne # 3.
  • Dans la ligne 4, vous pouvez écouter le son du silence
  • Vérifiez le commentaire sur la ligne 5
  • Une variable locale de méthode -anotherReferenceToTheSamePersonObject- est créé puis vient la magie dans la ligne # 6:
    • La variable / référence la personne est copié bit par bit et transmis à anotherReferenceToTheSamePersonObject à l'intérieur de la fonction.
    • Aucune nouvelle instance de Personne n'est créée.
    • Tous les deux "la personne" et "anotherReferenceToTheSamePersonObject"maintenez la même valeur de 3bad086a.
    • N'essayez pas ceci mais la personne == anotherReferenceToTheSamePersonObject serait vrai.
    • Les deux variables ont des COPIES IDENTIQUES de la référence et elles se réfèrent toutes les deux au même objet Personne, au même objet sur le tas et NON à une copie.

Une image vaut mieux que mille mots:

Pass by Value

Notez que les flèches anotherReferenceToTheSamePersonObject sont dirigées vers l'objet et non vers la personne variable!

Si vous ne l'avez pas compris, faites-moi confiance et souvenez-vous qu'il vaut mieux dire que Java est passé en valeur. Bien, passer par la valeur de référence. Eh bien, c'est encore mieux pass-by-copy-de-la-valeur-variable! ;)

Maintenant, n'hésitez pas à me détester, mais notez que compte tenu de cette il n'y a pas de différence entre passer des types de données primitifs et des objets quand on parle d'arguments de méthode.

Vous passez toujours une copie des bits de la valeur de la référence!

  • S'il s'agit d'un type de données primitif, ces bits contiendront la valeur du type de données primitif lui-même.
  • S'il s'agit d'un objet, les bits contiendront la valeur de l'adresse qui indique à la machine virtuelle Java comment accéder à l'objet.

Java est une valeur de transfert parce qu'à l'intérieur d'une méthode, vous pouvez modifier l'objet référencé autant que vous voulez, mais peu importe comment vous essayez, vous ne serez jamais capable de modifier la variable passée qui gardera le référencement (pas p _ _ _ _ _ _ _) le même objet, peu importe quoi!


La fonction changeName ci-dessus ne sera jamais capable de modifier le contenu réel (les valeurs de bit) de la référence passée. En d'autres termes, changeName ne peut pas faire référence à un autre objet.


Bien sûr, vous pouvez couper court et juste dire que Java est valeur-passe!


651



Java est toujours passer par la valeur, sans exception, déjà.

Alors comment se fait-il que n'importe qui puisse être du tout perplexe et croire que Java est passé par référence, ou pense-t-il avoir un exemple de Java faisant office de passage par référence? Le point clé est que Java jamais fournit un accès direct aux valeurs de objets eux-mêmes, dans tout conditions. Le seul accès aux objets est à travers un référence à cet objet. Parce que les objets Java sont toujours accessible via une référence, plutôt que directement, il est courant de parler de champs et de variables et arguments de méthode comme étant objets, quand ils sont pédantiquement seulement références à des objets. La confusion provient de ce changement (stricto sensu, incorrect) de la nomenclature.

Donc, lors de l'appel d'une méthode

  • Pour les arguments primitifs (int, long, etc.), la valeur de passage est la valeur réelle de la primitive (par exemple, 3).
  • Pour les objets, la valeur de passage est la valeur de la référence à l'objet.

Donc si vous avez doSomething(foo) et public void doSomething(Foo foo) { .. } les deux Foos ont copié les références ce point aux mêmes objets.

Naturellement, en passant par la valeur, une référence à un objet ressemble beaucoup (et est impossible à distinguer dans la pratique) de passer un objet par référence.


561



Java transmet les références par valeur.

Vous ne pouvez donc pas modifier la référence qui est transmise.


278



J'ai l'impression que discuter de «passer par référence vs passer par valeur» n'est pas super utile.

Si vous dites, "Java est pass-by-quoi (référence / valeur)", dans les deux cas, vous ne fournissez pas une réponse complète. Voici quelques informations supplémentaires qui, espérons-le, aideront à comprendre ce qui se passe en mémoire.

Crash course sur pile / heap avant d'arriver à l'implémentation Java: Les valeurs vont et viennent de la pile d'une manière ordonnée, comme une pile d'assiettes à la cafétéria. La mémoire dans le tas (également appelée mémoire dynamique) est désordonnée et désorganisée. La JVM ne trouve que l'espace où elle le peut et le libère car les variables qui l'utilisent ne sont plus nécessaires.

D'accord. Tout d'abord, les primitives locales vont sur la pile. Donc ce code:

int x = 3;
float y = 101.1f;
boolean amIAwesome = true;

résultats dans ceci:

primitives on the stack

Lorsque vous déclarez et instanciez un objet. L'objet réel va sur le tas. Qu'est-ce qui se passe sur la pile? L'adresse de l'objet sur le tas. Les programmeurs C ++ appellent cela un pointeur, mais certains développeurs Java sont contre le mot "pointeur". Peu importe. Sachez simplement que l'adresse de l'objet va sur la pile.

Ainsi:

int problems = 99;
String name = "Jay-Z";

a b*7ch aint one!

Un tableau est un objet, donc il va aussi sur le tas. Et qu'en est-il des objets dans le tableau? Ils obtiennent leur propre espace de tas, et l'adresse de chaque objet va à l'intérieur du tableau.

JButton[] marxBros = new JButton[3];
marxBros[0] = new JButton("Groucho");
marxBros[1] = new JButton("Zeppo");
marxBros[2] = new JButton("Harpo");

marx brothers

Alors, qu'est-ce qui se passe quand vous appelez une méthode? Si vous passez dans un objet, ce que vous passez est l'adresse de l'objet. Certains pourraient dire la "valeur" de l'adresse, et certains disent que c'est juste une référence à l'objet. C'est la genèse de la guerre sainte entre les partisans de la «référence» et de la «valeur». Ce que vous appelez n'est pas aussi important que vous comprenez que ce qui est transmis est l'adresse de l'objet.

private static void shout(String name){
    System.out.println("There goes " + name + "!");
}

public static void main(String[] args){
    String hisName = "John J. Jingleheimerschmitz";
    String myName = hisName;
    shout(myName);
}

Une chaîne est créée et un espace est attribué au tas, et l'adresse de la chaîne est stockée dans la pile et reçoit l'identifiant hisName, puisque l'adresse de la deuxième chaîne est la même que la première, aucune nouvelle chaîne n'est créée et aucun nouvel espace de tas n'est alloué, mais un nouvel identifiant est créé sur la pile. Ensuite, nous appelons shout(): un nouveau cadre de pile est créé et un nouvel identifiant, name est créé et affecté l'adresse de la chaîne déjà existante.

la da di da da da da

Alors, valeur, référence? Vous dites "pomme de terre".


198



Juste pour montrer le contraste, comparez le suivant C ++ et Java extraits:

En C ++: Note: Mauvais code - fuites de mémoire!  Mais cela démontre le point.

void cppMethod(int val, int &ref, Dog obj, Dog &objRef, Dog *objPtr, Dog *&objPtrRef)
{
    val = 7; // Modifies the copy
    ref = 7; // Modifies the original variable
    obj.SetName("obj"); // Modifies the copy of Dog passed
    objRef.SetName("objRef"); // Modifies the original Dog passed
    objPtr->SetName("objPtr"); // Modifies the original Dog pointed to 
                               // by the copy of the pointer passed.
    objPtr = new Dog("newObjPtr");  // Modifies the copy of the pointer, 
                                   // leaving the original object alone.
    objPtrRef->SetName("objRefPtr"); // Modifies the original Dog pointed to 
                                    // by the original pointer passed. 
    objPtrRef = new Dog("newObjPtrRef"); // Modifies the original pointer passed
}

int main()
{
    int a = 0;
    int b = 0;
    Dog d0 = Dog("d0");
    Dog d1 = Dog("d1");
    Dog *d2 = new Dog("d2");
    Dog *d3 = new Dog("d3");
    cppMethod(a, b, d0, d1, d2, d3);
    // a is still set to 0
    // b is now set to 7
    // d0 still have name "d0"
    // d1 now has name "objRef"
    // d2 now has name "objPtr"
    // d3 now has name "newObjPtrRef"
}

En Java,

public static void javaMethod(int val, Dog objPtr)
{
   val = 7; // Modifies the copy
   objPtr.SetName("objPtr") // Modifies the original Dog pointed to 
                            // by the copy of the pointer passed.
   objPtr = new Dog("newObjPtr");  // Modifies the copy of the pointer, 
                                  // leaving the original object alone.
}

public static void main()
{
    int a = 0;
    Dog d0 = new Dog("d0");
    javaMethod(a, d0);
    // a is still set to 0
    // d0 now has name "objPtr"
}

Java a uniquement les deux types de passage: par valeur pour les types prédéfinis et par valeur du pointeur pour les types d'objet.


161



Java transmet les références aux objets par valeur.


145