Java est-il «pass-by-reference» ou «pass-by-value»?


6584

J'ai toujours pensé que Java était passe-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?


706
Je pense qu'une grande partie de la confusion sur cette question tient au fait que différentes personnes ont des définitions différentes du terme "référence". Les personnes issues d'un arrière-plan C ++ supposent que «référence» doit signifier ce que cela signifie en C ++, les personnes issues d'un arrière-plan C supposent que «référence» doit être identique à «pointeur» dans leur langage, etc. Qu'il soit correct de dire que Java passe par référence dépend vraiment de ce que l'on entend par "référence".
Gravity

119
J'essaie d'utiliser systématiquement la terminologie qui se trouve dans l' article sur la stratégie d'évaluation . Il convient de noter que, même si l'article souligne que les termes varient considérablement selon la communauté, il souligne que la sémantique call-by-valueet call-by-referencediffère de manière très cruciale . (Personnellement, je préfère utiliser call-by-object-sharingces jours-ci call-by-value[-of-the-reference], car cela décrit la sémantique à un niveau élevé et ne crée pas de conflit avec call-by-value, ce qui est l'implémentation sous-jacente.)

59
@Gravity: Pouvez-vous aller mettre votre commentaire sur un énorme panneau d'affichage ou quelque chose? C'est toute la question en bref. Et cela montre que tout cela est de la sémantique. Si nous ne sommes pas d'accord sur la définition de base d'une référence, alors nous ne serons pas d'accord sur la réponse à cette question :)
MadConan

30
Je pense que la confusion est «passer par référence» contre «sémantique de référence». Java est pass-by-value avec une sémantique de référence.
pulvérisation

11
@Gravity, bien que vous ayez absolument raison de dire que les gens venant de C ++ auront instinctivement un ensemble d'intuitions différent concernant le terme "référence", je pense personnellement que le corps est plus enfoui dans le "par". «Passer par» est déroutant en ce qu'il est absolument distinct de «Passer un» en Java. En C ++, cependant, ce n'est pas familier. En C ++, vous pouvez dire "passer une référence", et il est entendu qu'il passera le swap(x,y)test .

Réponses:


5846

Java est toujours pass-by-value . Malheureusement, lorsque nous transmettons la valeur d'un objet, nous lui transmettons la référence . C'est déroutant pour les débutants.

Ça va comme ça:

public static void main(String[] args) {
    Dog aDog = new Dog("Max");
    Dog oldDog = aDog;

    // 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
    aDog == oldDog; // true
}

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 aDog.getName()- dessus sera toujours de retour "Max". La valeur à l' aDogintérieur mainn'est pas modifiée dans la fonction fooavec Dog "Fifi"car la référence d'objet est passée par valeur. Si elle était adoptée par référence, le aDog.getName()dans mainretournerait "Fifi"après l'appel à foo.

Également:

public static void main(String[] args) {
    Dog aDog = new Dog("Max");
    Dog oldDog = aDog;

    foo(aDog);
    // when foo(...) returns, the name of the dog has been changed to "Fifi"
    aDog.getName().equals("Fifi"); // true
    // but it is still the same dog:
    aDog == oldDog; // 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, Fifiest le nom du chien après l'appel à foo(aDog)car le nom de l'objet a été défini à l'intérieur de foo(...). Toutes les opérations qui foos'exécutent dsont telles que, à toutes fins pratiques, elles sont exécutées aDog, mais il n'est pas possible de modifier la valeur de la variable aDogelle-même.


263
N'est-ce pas confondre légèrement le problème avec les détails internes? Il n'y a pas de différence conceptuelle entre «passer une référence» et «passer la valeur d'une référence», en supposant que vous voulez dire «la valeur du pointeur interne vers l'objet».
izb

383
Mais il y a une différence subtile. Regardez le premier exemple. S'il était purement transmis par référence, aDog.name serait "Fifi". Ce n'est pas le cas - la référence que vous obtenez est une référence de valeur qui, si elle est remplacée, sera restaurée à la sortie de la fonction.
erlando

300
@ Lorenzo: Non, en Java, tout est transmis par valeur. Les primitives sont transmises par valeur et les références d'objet sont transmises par valeur. Les objets eux-mêmes ne sont jamais passés à une méthode, mais les objets sont toujours dans le tas et seule une référence à l'objet est passée à la méthode.
Esko Luontola

295
Ma tentative d'un bon moyen de visualiser le passage d'objet: Imaginez un ballon. Appeler un fxn, c'est comme attacher une deuxième chaîne au ballon et remettre la ligne au fxn. parameter = new Balloon () coupe cette chaîne et crée un nouveau ballon (mais cela n'a aucun effet sur le ballon d'origine). parameter.pop () le fera toujours apparaître car il suit la chaîne dans la même bulle d'origine. Java est passé par valeur, mais la valeur passée n'est pas profonde, elle est au plus haut niveau, c'est-à-dire une primitive ou un pointeur. Ne confondez pas cela avec une valeur de passage profonde où l'objet est entièrement cloné et transmis.
dhackner

150
Ce qui est déroutant, c'est que les références aux objets sont en fait des pointeurs. Au début, SUN les appelait des pointeurs. Ensuite, le marketing a informé que «pointeur» était un mauvais mot. Mais vous voyez toujours la nomenclature "correcte" dans NullPointerException.
contrat du professeur Falken a été rompu le

3036

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

La spécification Java dit que tout en Java est pass-by-value. Il n'y a pas de "passe-par-référence" en Java.

La clé pour comprendre cela est que quelque chose comme

Dog myDog;

n'est pas un chien; c'est en fait un pointeur vers un chien.

Ce que cela signifie, c'est quand vous avez

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

vous passez essentiellement l' adresse de l' Dogobjet créé à la foomé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 l' Dogobjet réside à l'adresse mémoire 42. Cela signifie que nous passons 42 à la méthode.

si la méthode était 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 someDogest réglé sur la valeur 42
  • à la ligne "AAA"
    • someDogest suivi jusqu'à ce Dogqu'il pointe vers (l' Dogobjet à l'adresse 42)
    • que Dog(celui à l'adresse 42) est invité à changer son nom en Max
  • à la ligne "BBB"
    • un nouveau Dogest créé. Disons qu'il est à l'adresse 74
    • on affecte le paramètre someDogà 74
  • à la ligne "CCC"
    • someDog est suivi jusqu'à ce Dogqu'il pointe vers (l' Dogobjet à l'adresse 74)
    • que Dog(celui à l'adresse 74) est prié de changer son nom en Rowlf
  • puis on revient

Voyons maintenant ce qui se passe en dehors de la méthode:

A myDogchangé?

Voilà la clé.

Gardant à l'esprit qu'il myDogs'agit d'un pointeur et non d'un réel Dog, la réponse est NON. myDoga 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 de n'a pas changé.)

Il est parfaitement valable de suivre une adresse et de modifier ce qui se trouve à la fin de celle-ci; cela ne change cependant pas la variable.

Java fonctionne exactement comme C. Vous pouvez affecter un pointeur, passer le pointeur à une méthode, suivre le pointeur dans la méthode et modifier les données qui ont été pointées. Toutefois, vous ne pouvez pas modifier l'emplacement du pointeur.

En C ++, Ada, Pascal et d'autres langages qui prennent en charge le passage par référence, vous pouvez réellement changer la variable qui a été passée.

Si Java avait une sémantique de passage par référence, la foométhode que nous avons définie ci-dessus aurait changé où myDogpointait lors de son affectation someDogsur 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 transmise l'est également.


164
C'est pourquoi le refrain commun "Java n'a pas de pointeurs" est si trompeur.
Beska

135
Vous vous trompez, à mon humble avis. "En gardant à l'esprit que myDog est un pointeur, et non un chien réel, la réponse est NON. MyDog a toujours la valeur 42; il pointe toujours vers le chien d'origine." myDog a la valeur 42 mais son argument de nom contient désormais "Max", plutôt que "Rover" sur la ligne // AAA.
Özgür

180
Pensez-y de cette façon. Quelqu'un a l'adresse d'Ann Arbor, MI (ma ville natale, GO BLUE!) Sur une feuille de papier appelée "annArborLocation". Vous le copiez sur un morceau de papier appelé "myDestination". Vous pouvez conduire jusqu'à "myDestination" et planter un arbre. Vous pouvez avoir changé quelque chose à propos de la ville à cet endroit, mais cela ne change pas le LAT / LON qui a été écrit sur l'un ou l'autre papier. Vous pouvez changer le LAT / LON sur "myDestination" mais cela ne change pas "annArborLocation". Est ce que ça aide?
Scott Stanchfield

43
@Scott Stanchfield: J'ai lu votre article il y a environ un an et cela m'a vraiment aidé à clarifier les choses. Merci! Puis-je humblement suggérer un petit ajout: vous devez mentionner qu'il existe en fait un terme spécifique qui décrit cette forme "d'appel par valeur où la valeur est une référence" qui a été inventée par Barbara Liskov pour décrire la stratégie d'évaluation de son langage CLU dans 1974, afin d'éviter toute confusion telle que celle abordée par votre article: appel par partage (parfois appelé appel par partage d'objets ou simplement appel par objet ), qui décrit à peu près parfaitement la sémantique.
Jörg W Mittag

29
@Gevorg - Les langages C / C ++ ne possèdent pas le concept de "pointeur". Il existe d'autres langages qui utilisent des pointeurs mais n'autorisent pas les mêmes types de manipulation de pointeurs que C / C ++ autorisent. Java a des pointeurs; ils sont juste protégés contre les méfaits.
Scott Stanchfield

1741

Java transmet toujours les arguments par valeur , PAS par référence.


Permettez-moi d'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 l'expliquer par étapes:

  1. Déclarer une référence nommée fde type Fooet lui affecter un nouvel objet de type Fooavec un attribut "f".

    Foo f = new Foo("f");

    entrez la description de l'image ici

  2. Du côté méthode, une référence de type Fooavec un nom aest déclarée et elle est initialement affectée null.

    public static void changeReference(Foo a)

    entrez la description de l'image ici

  3. Lorsque vous appelez la méthode changeReference, la référence se averra attribuer l'objet qui est passé en argument.

    changeReference(f);

    entrez la description de l'image ici

  4. Déclarer une référence nommée bde type Fooet lui affecter un nouvel objet de type Fooavec un attribut "b".

    Foo b = new Foo("b");

    entrez la description de l'image ici

  5. a = bfait une nouvelle affectation à la référence a, non f , de l'objet dont son attribut est "b".

    entrez la description de l'image ici

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

    entrez la description de l'image ici

  7. c.setAttribute("c");changera l'attribut de l'objet qui fait référence cà lui, et c'est le même objet qui fait référence fà lui.

    entrez la description de l'image ici

J'espère que vous comprenez maintenant comment fonctionne le passage d'objets comme arguments en Java :)


82
+1 Des trucs sympas. bons diagrammes. J'ai également trouvé une belle page succincte ici adp-gmbh.ch/php/pass_by_reference.html OK j'avoue qu'elle est écrite en PHP, mais c'est le principe de compréhension de la différence qui je pense est important (et comment manipuler cette différence pour vos besoins).
DaveM

5
@ Eng.Fouad C'est une belle explication mais si apointe vers le même objet que f(et n'obtient jamais sa propre copie de l'objet fpointe vers), toutes les modifications apportées à l'objet effectuées à l'aide de adevraient également être modifiées f(car elles fonctionnent toutes les deux avec le même objet ), il faut donc à un moment donné aobtenir sa propre copie de l'objet fpointé vers.
0x6C38

14
@MrD lorsque 'a' pointe vers le même objet 'f' pointe également vers, alors tout changement apporté à cet objet via 'a' est également observable via 'f', MAIS IL N'A PAS CHANGÉ 'f'. 'f' pointe toujours vers le même objet. Vous pouvez totalement changer l'objet, mais vous ne pouvez jamais changer ce vers quoi "f" pointe. C'est la question fondamentale que certaines personnes ne peuvent tout simplement pas comprendre.
Mike Braun

@MikeBraun ... quoi? Maintenant, vous m'avez confondu: S. Ce que vous venez d'écrire n'est-il pas contraire à ce que montrent 6. et 7.?
Evil Washing Machine

6
C'est la meilleure explication que j'ai trouvée. Au fait, qu'en est-il de la situation de base? Par exemple, l'argument nécessite un type int. Transmet-il toujours la copie d'une variable int à l'argument?
allenwang

733

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

Première étape, veuillez effacer 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 rappelle que lorsque vous passez un objet dans une méthode, vous passez la référence d'objet et non l'objet lui-même.

  • Étudiant : Maître, cela signifie-t-il que Java est un pass-by-reference?
  • Maître : sauterelle, non.

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

  1. Une variable contient les bits qui indiquent à la machine virtuelle Java comment accéder à l'objet référencé en mémoire (tas).
  2. Lorsque vous passez des arguments à une méthode, vous NE PAS transmettre la variable de référence, mais une copie des bits de 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 réellement COPIÉE et donnée à la méthode .

Dans ce qui suit (veuillez ne pas essayer 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 personne est créée à la ligne 1 et elle est nulle au début.
  • Un nouvel objet Personne est créé sur la ligne n ° 2, stocké en mémoire, et la variable personne reçoit la référence à l'objet Personne. Autrement dit, son adresse. Disons 3bad086a.
  • La personne variable qui détient l'adresse de l'objet est transmise à la fonction à 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éée, puis vient la magie de la ligne # 6:
    • La variable / personne de référence est copiée bit par bit et transmise à anotherReferenceToTheSamePersonObject à l'intérieur de la fonction.
    • Aucune nouvelle instance de Personne n'est créée.
    • " Person " et " anotherReferenceToTheSamePersonObject " contiennent tous deux la même valeur de 3bad086a.
    • N'essayez pas ceci mais la personne == anotherReferenceToTheSamePersonObject serait vraie.
    • Les deux variables ont des COPIES IDENTIQUES de la référence et elles font toutes deux référence au même objet Personne, le MÊME objet sur le tas et PAS À UNE COPIE.

Une image vaut mieux que mille mots:

Passer par valeur

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

Si vous ne l'avez pas obtenu, alors faites-moi confiance et rappelez-vous qu'il vaut mieux dire que Java est un passage par valeur . Eh bien, passez par la valeur de référence . Eh bien, c'est encore mieux la copie de la valeur de la variable pass-by-copy! ;)

Maintenant, n'hésitez pas à me détester mais notez que, compte tenu de cela, il n'y a pas de différence entre le passage de types de données primitifs et les objets lorsque vous parlez 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 pass-by-value car à l'intérieur d'une méthode, vous pouvez modifier l'objet référencé autant que vous le souhaitez, mais peu importe vos efforts, vous ne pourrez jamais modifier la variable transmise qui continuera à référencer (pas p _ _ _ _ _ _ _) le même objet quoi qu'il arrive!


La fonction changeName ci-dessus ne pourra jamais modifier le contenu réel (les valeurs binaires) de la référence passée. En d'autres termes, changeName ne peut pas forcer Person à se référer à un autre objet.


Bien sûr, vous pouvez le raccourcir et dire simplement que Java est une valeur de passage!


5
Voulez-vous dire des pointeurs? .. Si je comprends bien, dans public void foo(Car car){ ... }, carest local à fooet qu'il contient l'emplacement de tas de l'objet? Donc, si je modifie carla valeur de car = new Car(), cela pointera vers un objet différent sur le tas? et si je change la valeur de carla propriété de car.Color = "Red", l'objet dans le tas pointé par carsera modifié. De même, c'est la même chose en C #? Répondez, s'il vous plaît! Merci!
dpp

11
@domanokz Vous me tuez, s'il vous plaît, ne dites plus ce mot! ;) Notez que j'aurais pu répondre à cette question sans dire «référence» également. C'est un problème de terminologie et les «p» aggravent les choses. Scot et moi avons malheureusement des opinions différentes à ce sujet. Je pense que vous avez compris comment cela fonctionne en Java, maintenant vous pouvez l'appeler passer par valeur, par partage d'objet, par copie de la valeur de variable, ou n'hésitez pas à trouver autre chose! Peu m'importe tant que vous savez comment cela fonctionne et ce qu'il y a dans une variable de type objet: juste une adresse de boîte postale! ;)
Marsellus Wallace

9
On dirait que vous venez de passer une référence ? Je vais défendre le fait que Java est toujours un langage de copie par référence copié. Le fait qu'il s'agisse d'une référence copiée ne change pas la terminologie. Les deux RÉFÉRENCES pointent toujours vers le même objet. C'est l'argument d'un puriste ...
John Strickler

3
Alors passez à la ligne théorique n ° 9, System.out.println(person.getName());qu'est-ce qui va apparaître? "Tom" ou "Jerry"? C'est la dernière chose qui m'aidera à surmonter cette confusion.
TheBrenny

1
"La valeur de référence " est ce qui m'a finalement expliqué.
Alexander Shubert

689

Java est toujours transmis par valeur, sans aucune exception, jamais .

Alors, comment se fait-il que quiconque puisse être confus du tout et croire que Java est passe par référence, ou pense-t-il avoir un exemple de Java agissant comme passe-par-référence? Le point clé est que Java ne fournit en aucun cas un accès direct aux valeurs des objets eux - mêmes . Le seul accès aux objets se fait par une référence à cet objet. Étant donné que les objets Java sont toujours accessibles via une référence, plutôt que directement, il est courant de parler des champs et des variables et des arguments de méthode comme étant des objets , quand ils ne sont pédantiquement que des références à des objets .La confusion provient de ce changement (à proprement parler, incorrect) de nomenclature.

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

  • Pour les arguments primitifs ( int, long, etc.), le passage par valeur 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 en avez doSomething(foo)et que public void doSomething(Foo foo) { .. }les deux Foos ont copié des références qui pointent vers les mêmes objets.

Naturellement, passer par valeur une référence à un objet ressemble beaucoup (et ne se distingue pas en pratique à) passer un objet par référence.


7
Puisque les valeurs des primitives sont immuables (comme String), la différence entre les deux cas n'est pas vraiment pertinente.
Paŭlo Ebermann

4
Exactement. Pour tout ce que vous pouvez dire via un comportement JVM observable, les primitives peuvent être transmises par référence et peuvent vivre sur le tas. Ils ne le font pas, mais ce n'est en fait pas observable en aucune façon.
Gravity

6
les primitives sont immuables? est-ce nouveau dans Java 7?
user85421

4
Les pointeurs sont immuables, les primitives en général sont mutables. La chaîne n'est pas non plus une primitive, c'est un objet. De plus, la structure sous-jacente de la chaîne est un tableau mutable. La seule chose immuable à ce sujet est la longueur, c'est-à-dire la nature inhérente des tableaux.
kingfrito_5005

3
Ceci est une autre réponse soulignant la nature largement sémantique de l'argument. La définition de référence fournie dans cette réponse ferait de Java un «pass-by-reference». L'auteur admet essentiellement cela dans le dernier paragraphe en déclarant qu'il est "impossible de le distinguer dans la pratique" du "passage par référence". Je doute qu'OP demande à cause d'un désir de comprendre l'implémentation Java mais plutôt de comprendre comment utiliser correctement Java. S'il était impossible de les distinguer dans la pratique, il ne servirait à rien de s'en soucier et même y penser serait une perte de temps.
Loduwijk

331

Java transmet les références par valeur.

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


27
Mais la chose qui ne cesse de se répéter "vous ne pouvez pas changer la valeur des objets passés en arguments" est clairement fausse. Vous ne pourrez peut-être pas les faire référence à un autre objet, mais vous pouvez modifier leur contenu en appelant leurs méthodes. OMI, cela signifie que vous perdez tous les avantages des références et que vous ne gagnez aucune garantie supplémentaire.
Timmmm

34
Je n'ai jamais dit "vous ne pouvez pas changer la valeur des objets passés en arguments". Je dirai "Vous ne pouvez pas changer la valeur de la référence d'objet transmise en tant qu'argument de méthode", qui est une véritable déclaration sur le langage Java. Évidemment, vous pouvez changer l'état de l'objet (tant qu'il n'est pas immuable).
ScArcher2

20
Gardez à l'esprit que vous ne pouvez pas réellement passer d'objets en java; les objets restent sur le tas. Des pointeurs vers les objets peuvent être passés (qui sont copiés sur le cadre de pile pour la méthode appelée). Vous ne modifiez donc jamais la valeur transmise (le pointeur), mais vous êtes libre de la suivre et de modifier l'élément sur le tas vers lequel elle pointe. C'est une valeur de passage.
Scott Stanchfield

10
On dirait que vous venez de passer une référence ? Je vais défendre le fait que Java est toujours un langage de copie par référence copié. Le fait qu'il s'agisse d'une référence copiée ne change pas la terminologie. Les deux RÉFÉRENCES pointent toujours vers le même objet. C'est l'argument d'un puriste ...
John Strickler

3
Java ne transmet pas d'objet, il transmet la valeur du pointeur à l'objet. Cela crée un nouveau pointeur vers cet emplacement mémoire de l'objet d'origine dans une nouvelle variable. Si vous modifiez la valeur (l'adresse mémoire vers laquelle il pointe) de cette variable de pointeur dans une méthode, le pointeur d'origine utilisé dans l'appelant de la méthode reste inchangé. Si vous appelez le paramètre une référence, le fait qu'il s'agisse d'une copie de la référence d'origine, et non de la référence d'origine elle-même, de sorte qu'il existe maintenant deux références à l'objet, signifie qu'il s'agit d'une valeur de passage
theferrit32

238

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

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

Cours intensif sur pile / tas avant d'arriver à l'implémentation Java: les valeurs montent et descendent de la pile de manière ordonnée, comme une pile d'assiettes dans une cafétéria. La mémoire dans le tas (également connue sous le nom de mémoire dynamique) est aléatoire et désorganisée. La JVM trouve juste de l'espace partout 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ésulte en ceci:

primitives sur la pile

Lorsque vous déclarez et instanciez un objet. L'objet réel va sur le tas. Que se passe-t-il 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";

ab * 7ch n'est pas un!

Un tableau est un objet, il va donc aussi sur le tas. Et qu'en est-il des objets du 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");

frères marx

Alors, qu'est-ce qui se passe lorsque vous appelez une méthode? Si vous passez un objet, ce que vous passez réellement 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 «référence» et de «valeur». Ce que vous appelez n'est pas aussi important que de comprendre 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 alloué dans le tas, et l'adresse de la chaîne est stockée dans la pile et compte tenu de l'identifiant hisName, car 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 segment de mémoire 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, nameest créé et attribué 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".


7
Cependant, vous devriez avoir suivi avec un exemple plus complexe où une fonction semble modifier une variable à l'adresse à laquelle elle a une référence.
Brian Peterson

34
Les gens ne "dansent pas autour du vrai problème" de la pile contre le tas, parce que ce n'est pas le vrai problème. C'est un détail d'implémentation au mieux, et carrément faux au pire. (Il est tout à fait possible que des objets vivent sur la pile; google "analyse d'échappement". Et un grand nombre d'objets contiennent des primitives qui ne vivent probablement pas sur la pile.) Le vrai problème est exactement la différence entre les types de référence et les types de valeur - en particulier, que la valeur d'une variable de type référence est une référence, pas l'objet auquel elle se réfère.
cHao

8
C'est un "détail d'implémentation" dans la mesure où Java n'est jamais obligé de vous montrer où se trouve un objet en mémoire et semble en fait déterminé à éviter de divulguer ces informations. Cela pourrait mettre l'objet sur la pile, et vous ne le sauriez jamais. Si vous vous souciez, vous vous concentrez sur la mauvaise chose - et dans ce cas, cela signifie ignorer le vrai problème.
cHao

10
Et de toute façon, "les primitives vont sur la pile" est incorrecte. Les variables locales primitives vont sur la pile. (S'ils n'ont pas été optimisés, bien sûr.) Mais alors, il en va de même pour les variables de référence locales . Et les membres primitifs définis dans un objet vivent partout où il vit.
cHao

9
Acceptez les commentaires ici. La pile / tas est un problème secondaire et non pertinent. Certaines variables peuvent être sur la pile, certaines sont dans la mémoire statique (variables statiques) et beaucoup vivent sur le tas (toutes les variables membres de l'objet). AUCUNE de ces variables ne peut être passée par référence: à partir d'une méthode appelée, il n'est JAMAIS possible de changer la valeur d'une variable qui est passée en argument. Par conséquent, il n'y a pas de référence de passage en Java.
fishinear

195

Juste pour montrer le contraste, comparez les extraits de code C ++ et Java suivants :

En C ++: Remarque: 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 n'a que les deux types de passage: par valeur pour les types intégrés et par valeur du pointeur pour les types d'objets.


7
+1 J'ajouterais également Dog **objPtrPtrà l'exemple C ++, de cette façon, nous pouvons modifier ce que le pointeur "pointe vers".
Amro

1
Mais cela ne répond pas à la question donnée en Java. En fait, tout dans la méthode Java est Pass By Value, rien de plus.
tauitdnmd

2
Cet exemple prouve simplement que java utilise l'équivalent du pointeur en C non? Où passer par la valeur en C sont en lecture seule? À la fin, tout ce fil est incompréhensible
amdev


166

Fondamentalement, la réaffectation des paramètres d'objet n'affecte pas l'argument, par exemple,

private void foo(Object bar) {
    bar = null;
}

public static void main(String[] args) {
    String baz = "Hah!";
    foo(baz);
    System.out.println(baz);
}

imprimera au "Hah!"lieu de null. La raison pour laquelle cela fonctionne est parce que barc'est une copie de la valeur de baz, qui n'est qu'une référence à "Hah!". Si elle était la référence elle - même, puis fooaurait redéfini bazà null.


8
Je dirais plutôt que la barre est une copie du baz de référence (ou alias baz), qui pointe initialement vers le même objet.
MaxZoom

n'y a-t-il pas une légère différence entre la classe String et toutes les autres classes?
Mehdi Karamosly

152

Je ne peux pas croire que personne n'ait encore mentionné Barbara Liskov. Lorsqu'elle a conçu CLU en 1974, elle a rencontré ce même problème de terminologie et elle a inventé le terme appel par partage (également appelé appel par partage d'objets et appel par objet ) pour ce cas spécifique de «l'appel par valeur où la valeur est une référence".


3
J'aime cette distinction dans la nomenclature. Il est malheureux que Java supporte l'appel en partageant pour les objets, mais pas l'appel par valeur (comme le fait C ++). Java prend en charge l'appel par valeur uniquement pour les types de données primitifs et non les types de données composites.
Derek Mahar

2
Je ne pense vraiment pas que nous avions besoin d'un terme supplémentaire - c'est simplement une valeur de passage pour un type de valeur spécifique. L'ajout d'un "appel par primitif" apporterait-il des précisions?
Scott Stanchfield


Je peux donc passer par référence en partageant un objet de contexte global, ou même en passant un objet de contexte qui contient d'autres références? Je passe toujours par valeur, mais au moins j'ai accès à des références que je peux modifier et les faire pointer vers autre chose.
YoYo

1
@Sanjeev: le partage d'appel par objet est un cas particulier de pass-by-value. Cependant, beaucoup de gens soutiennent avec véhémence que Java (et des langages similaires comme Python, Ruby, ECMAScript, Smalltalk) sont pass-by-reference. Je préférerais également l'appeler passe-par-valeur, mais le partage appel-par-objet semble être un terme raisonnable que même les gens qui soutiennent que ce n'est pas de la valeur-by-pass peuvent accepter.
Jörg W Mittag

119

Le nœud du problème est que le mot référence dans l'expression "passer par référence" signifie quelque chose de complètement différent de la signification habituelle du mot référence en Java.

Habituellement, en référence Java signifie une référence à un objet . Mais les termes techniques qui passent par référence / valeur de la théorie du langage de programmation parlent d'une référence à la cellule mémoire contenant la variable , ce qui est quelque chose de complètement différent.


7
@Gevorg - Alors qu'est-ce qu'une "NullPointerException"?
Hot Licks

5
@Hot: Une exception malheureusement nommée d'avant Java s'est fixée une terminologie claire. L'exception sémantiquement équivalente en c # est appelée NullReferenceException.
JacquesB

4
Il m'a toujours semblé que l'utilisation de «référence» dans la terminologie Java est une affectation qui entrave la compréhension.
Hot Licks

J'ai appris à appeler ces soi-disant "pointeurs vers des objets" comme une "poignée vers l'objet". Cette ambiguïté réduite.
moronkreacionz

86

En java, tout est référence, donc quand vous avez quelque chose comme: Point pnt1 = new Point(0,0);Java fait ce qui suit:

  1. Crée un nouvel objet Point
  2. Crée une nouvelle référence Point et initialise cette référence au point (voir) sur un objet Point créé précédemment.
  3. De là, à travers la vie de l'objet Point, vous accéderez à cet objet via la référence pnt1. Nous pouvons donc dire qu'en Java, vous manipulez l'objet via sa référence.

entrez la description de l'image ici

Java ne transmet pas d'arguments de méthode par référence; il les transmet par valeur. Je vais utiliser l'exemple de ce site :

public static void tricky(Point arg1, Point arg2) {
  arg1.x = 100;
  arg1.y = 100;
  Point temp = arg1;
  arg1 = arg2;
  arg2 = temp;
}
public static void main(String [] args) {
  Point pnt1 = new Point(0,0);
  Point pnt2 = new Point(0,0);
  System.out.println("X1: " + pnt1.x + " Y1: " +pnt1.y); 
  System.out.println("X2: " + pnt2.x + " Y2: " +pnt2.y);
  System.out.println(" ");
  tricky(pnt1,pnt2);
  System.out.println("X1: " + pnt1.x + " Y1:" + pnt1.y); 
  System.out.println("X2: " + pnt2.x + " Y2: " +pnt2.y);  
}

Déroulement du programme:

Point pnt1 = new Point(0,0);
Point pnt2 = new Point(0,0);

Création de deux objets Point différents avec deux références différentes associées. entrez la description de l'image ici

System.out.println("X1: " + pnt1.x + " Y1: " +pnt1.y); 
System.out.println("X2: " + pnt2.x + " Y2: " +pnt2.y);
System.out.println(" ");

Comme prévu, la sortie sera:

X1: 0     Y1: 0
X2: 0     Y2: 0

Sur cette ligne, le passage par valeur entre en jeu ...

tricky(pnt1,pnt2);           public void tricky(Point arg1, Point arg2);

Les références pnt1et pnt2sont passées par valeur à la méthode délicate, ce qui signifie que maintenant la vôtre fait référence pnt1et pnt2a son copiesnom arg1et arg2.So pnt1et arg1 pointe vers le même objet. (Idem pour le pnt2et arg2) entrez la description de l'image ici

Dans la trickyméthode:

 arg1.x = 100;
 arg1.y = 100;

entrez la description de l'image ici

Suivant dans la trickyméthode

Point temp = arg1;
arg1 = arg2;
arg2 = temp;

Ici, vous créez d'abord une nouvelle tempréférence de point qui pointera au même endroit que la arg1référence. Ensuite, vous déplacez la référence arg1pour pointer vers le même endroit comme arg2référence. Enfin arg2va pointer au même endroit comme temp.

entrez la description de l'image ici

De là , la portée de la trickyméthode est allé et vous n'avez pas accès plus aux références: arg1, arg2, temp. Mais il est important de noter que tout ce que vous faites avec ces références lorsqu'elles sont «dans la vie» affectera en permanence l'objet sur lequel elles pointent .

Donc, après avoir exécuté la méthode tricky, lorsque vous revenez à main, vous avez cette situation: entrez la description de l'image ici

Alors maintenant, complètement l'exécution du programme sera:

X1: 0         Y1: 0
X2: 0         Y2: 0
X1: 100       Y1: 100
X2: 0         Y2: 0

7
En java, quand vous dites "en java, tout est référence", vous voulez dire que tous les objets sont passés par référence. Les types de données primitifs ne sont pas transmis par référence.
Eric

Je peux imprimer la valeur échangée dans la méthode principale. dans la méthode trickey, ajoutez la déclaration suivante arg1.x = 1; arg1.y = 1; arg2.x = 2; arg2.y = 2;ainsi, comme arg1 maintenant la réfraction pnt2 et arg2 maintenant la référence pnt1, donc, son impressionX1: 2 Y1: 2 X2: 1 Y2: 1
Shahid Ghafoor

85

Java est toujours transmis par valeur, pas par référence

Tout d'abord, nous devons comprendre ce que sont le passage par valeur et le passage par référence.

Passer par valeur signifie que vous effectuez une copie en mémoire de la valeur réelle du paramètre qui est transmise. Il s'agit d'une copie du contenu du paramètre réel .

Le passage par référence (également appelé passage par adresse) signifie qu'une copie de l'adresse du paramètre réel est stockée .

Parfois, Java peut donner l'illusion de passer par référence. Voyons comment cela fonctionne en utilisant l'exemple ci-dessous:

public class PassByValue {
    public static void main(String[] args) {
        Test t = new Test();
        t.name = "initialvalue";
        new PassByValue().changeValue(t);
        System.out.println(t.name);
    }

    public void changeValue(Test f) {
        f.name = "changevalue";
    }
}

class Test {
    String name;
}

Le résultat de ce programme est:

changevalue

Comprenons étape par étape:

Test t = new Test();

Comme nous le savons tous, cela créera un objet dans le tas et renverra la valeur de référence à t. Par exemple, supposons que la valeur de t soit 0x100234(nous ne connaissons pas la valeur interne réelle de la JVM, ce n'est qu'un exemple).

première illustration

new PassByValue().changeValue(t);

Lors du passage de la référence t à la fonction, il ne transmettra pas directement la valeur de référence réelle du test d'objet, mais il créera une copie de t puis la transmettra à la fonction. Puisqu'il passe par valeur , il passe une copie de la variable plutôt que la référence réelle de celle-ci. Puisque nous avons dit que la valeur de t était 0x100234, t et f auront la même valeur et donc pointeront vers le même objet.

deuxième illustration

Si vous modifiez quelque chose dans la fonction en utilisant la référence f, cela modifiera le contenu existant de l'objet. C'est pourquoi nous avons obtenu la sortie changevalue, qui est mise à jour dans la fonction.

Pour comprendre cela plus clairement, considérons l'exemple suivant:

public class PassByValue {
    public static void main(String[] args) {
        Test t = new Test();
        t.name = "initialvalue";
        new PassByValue().changeRefence(t);
        System.out.println(t.name);
    }

    public void changeRefence(Test f) {
        f = null;
    }
}

class Test {
    String name;
}

Est-ce que cela va jeter un NullPointerException? Non, car il ne transmet qu'une copie de la référence. Dans le cas d'un passage par référence, il aurait pu lancer un NullPointerException, comme illustré ci-dessous:

troisième illustration

J'espère que cela vous aidera.


71

Une référence est toujours une valeur lorsqu'elle est représentée, quelle que soit la langue que vous utilisez.

Pour obtenir une vue extérieure à la boîte, regardons l'assemblage ou une gestion de la mémoire de bas niveau. Au niveau CPU, une référence à n'importe quoi devient immédiatement une valeur si elle est écrite dans la mémoire ou dans l'un des registres CPU. (C'est pourquoi le pointeur est une bonne définition. C'est une valeur, qui a un but en même temps).

Les données en mémoire ont un emplacement et à cet emplacement, il y a une valeur (octet, mot, peu importe). Dans Assembly, nous avons une solution pratique pour donner un nom à un certain emplacement (aka variable), mais lors de la compilation du code, l'assembleur remplace simplement Name par l'emplacement désigné, tout comme votre navigateur remplace les noms de domaine par des adresses IP.

Fondamentalement, il est techniquement impossible de transmettre une référence à quoi que ce soit dans n'importe quelle langue sans la représenter (lorsqu'elle devient immédiatement une valeur).

Disons que nous avons une variable Foo, son emplacement est au 47e octet en mémoire et sa valeur est 5. Nous avons une autre variable Ref2Foo qui est à 223e octet en mémoire, et sa valeur sera 47. Ce Ref2Foo pourrait être une variable technique , non explicitement créé par le programme. Si vous regardez simplement 5 et 47 sans aucune autre information, vous ne verrez que deux valeurs . Si vous les utilisez comme références, pour atteindre, 5nous devons voyager:

(Name)[Location] -> [Value at the Location]
---------------------
(Ref2Foo)[223]  -> 47
(Foo)[47]       -> 5

Voici comment fonctionnent les tables de saut.

Si nous voulons appeler une méthode / fonction / procédure avec la valeur de Foo, il existe plusieurs façons possibles de passer la variable à la méthode, en fonction du langage et de ses différents modes d'invocation de méthode:

  1. 5 est copié dans l'un des registres du processeur (c'est-à-dire EAX).
  2. 5 obtient PUSHd dans la pile.
  3. 47 est copié dans l'un des registres du processeur
  4. 47 APPUYER sur la pile.
  5. 223 est copié dans l'un des registres du processeur.
  6. 223 obtient PUSHd dans la pile.

Dans tous les cas au-dessus d'une valeur - une copie d'une valeur existante - a été créée, il appartient désormais à la méthode de réception de la gérer. Lorsque vous écrivez "Foo" à l'intérieur de la méthode, il est soit lu à partir d'EAX, soit automatiquement déréférencé , soit déréférencé deux fois, le processus dépend du fonctionnement du langage et / ou de ce que le type de Foo dicte. Ceci est caché au développeur jusqu'à ce qu'elle contourne le processus de déréférencement. Une référence est donc une valeur lorsqu'elle est représentée, car une référence est une valeur qui doit être traitée (au niveau de la langue).

Maintenant, nous avons passé Foo à la méthode:

  • dans les cas 1. et 2. si vous changez Foo ( Foo = 9), cela n'affecte que la portée locale car vous avez une copie de la valeur. De l'intérieur de la méthode, nous ne pouvons même pas déterminer où en mémoire le Foo d'origine était situé.
  • dans les cas 3. et 4. si vous utilisez des constructions de langage par défaut et changez Foo ( Foo = 11), cela pourrait changer Foo globalement (dépend du langage, c'est-à-dire Java ou comme le procedure findMin(x, y, z: integer;var m de Pascal : integer);). Cependant, si la langue vous permet de contourner le processus de déréférencement, vous pouvez changer 47, par exemple 49. À ce stade, Foo semble avoir été modifié si vous le lisez, car vous y avez changé le pointeur local . Et si vous deviez modifier ce Foo à l'intérieur de la méthode ( Foo = 12), vous fuierez probablement l'exécution du programme (aka. Segfault) parce que vous écrirez dans une mémoire différente de celle attendue, vous pouvez même modifier une zone qui est destinée à contenir l'exécutable Le programme et l'écriture modifieront le code en cours d'exécution (Foo n'est plus là 47). MAIS la valeur de Foo de47n'a pas changé globalement, seulement celui à l'intérieur de la méthode, car 47était également une copie de la méthode.
  • dans les cas 5. et 6. si vous modifiez 223à l'intérieur de la méthode, cela crée le même chaos que dans 3. ou 4. (un pointeur, pointant vers une valeur désormais mauvaise, qui est à nouveau utilisé comme pointeur) mais c'est toujours un local problème, car 223 a été copié . Cependant, si vous êtes en mesure de déréférencer Ref2Foo(c'est-à-dire 223), d'atteindre et de modifier la valeur pointée 47, disons, 49cela affectera Foo globalement , car dans ce cas, les méthodes ont obtenu une copie 223 mais le référencé 47n'existe qu'une seule fois, et changer cela à 49conduira chaque Ref2Foodouble déréférencement à une valeur erronée.

En choisissant des détails insignifiants, même les langues qui passent par référence transmettront des valeurs aux fonctions, mais ces fonctions savent qu'elles doivent les utiliser à des fins de déréférencement. Cette passe-la-référence-comme-valeur est juste cachée au programmeur car elle est pratiquement inutile et la terminologie n'est que passe-par-référence .

Une valeur de passage stricte est également inutile, cela signifierait qu'un tableau de 100 Mo devrait être copié chaque fois que nous appelons une méthode avec le tableau comme argument, par conséquent Java ne peut pas être strictement une valeur de passage. Chaque langage transmettrait une référence à cet énorme tableau (en tant que valeur) et emploie un mécanisme de copie sur écriture si ce tableau peut être modifié localement à l'intérieur de la méthode ou permet à la méthode (comme Java le fait) de modifier le tableau globalement (à partir de vue de l'appelant) et quelques langues permettent de modifier la valeur de la référence elle-même.

Donc, en bref et dans la propre terminologie de Java, Java est une valeur passe-par-valeur où la valeur peut être: soit une valeur réelle soit une valeur qui est une représentation d'une référence .


1
Dans une langue avec pass-by-reference, la chose qui est passée (la référence) est éphémère; le destinataire n'est pas "supposé" le copier. En Java, le passage d'un tableau est passé est un "identifiant d'objet" - équivalent à un bout de papier qui dit "Objet # 24601", lorsque le 24601e objet construit était un tableau. Le destinataire peut copier "Objet # 24601" où il veut, et toute personne disposant d'une feuille de papier indiquant "Objet # 24601" peut faire tout ce qu'il veut avec n'importe lequel des éléments du tableau. Le motif de bits transmis ne dirait pas réellement "Objet # 24601", bien sûr, mais ...
supercat

... le point clé est que le destinataire de l'identifiant d'objet qui identifie le tableau peut stocker cet identifiant d'objet où il veut et le donner à qui il veut, et n'importe quel destinataire pourrait accéder au tableau ou le modifier à tout moment veut. En revanche, si un tableau était passé par référence dans un langage comme Pascal qui prend en charge de telles choses, la méthode appelée pourrait faire ce qu'elle voulait avec le tableau, mais ne pourrait pas stocker la référence de manière à permettre au code de modifier le tableau après son retour.
supercat

En effet, en Pascal, vous pouvez obtenir l'adresse de chaque variable, qu'elle soit passée par référence ou copiée localement, addrqu'elle soit non typée, le @fasse avec le type, et vous pouvez modifier la variable référencée plus tard (sauf celles copiées localement). Mais je ne vois pas pourquoi vous feriez cela. Cette feuille de papier dans votre exemple (Objet # 24601) est une référence, son but est d'aider à trouver le tableau en mémoire, il ne contient aucune donnée de tableau en elle-même. Si vous redémarrez votre programme, le même tableau peut obtenir un identifiant d'objet différent même si son contenu sera le même que lors de l'exécution précédente.
karatedog

J'avais pensé que l'opérateur "@" ne faisait pas partie du Pascal standard, mais qu'il était implémenté comme une extension commune. Cela fait-il partie de la norme? Mon point était que dans un langage avec un vrai pass-by-ref, et aucune possibilité de construire un pointeur non éphémère vers un objet éphémère, code qui détient la seule référence à un tableau, n'importe où dans l'univers, avant de passer le tableau par la référence peut savoir qu'à moins que le destinataire ne «triche», elle conservera toujours la seule référence par la suite. Le seul moyen sûr d'y parvenir en Java serait de construire un objet temporaire ...
supercat

... qui encapsule un AtomicReferenceet n'expose ni la référence ni sa cible, mais inclut à la place des méthodes pour faire des choses à la cible; une fois que le code auquel l'objet a été transmis revient, le AtomicReference[auquel son créateur a conservé une référence directe] doit être invalidé et abandonné. Cela fournirait la bonne sémantique, mais ce serait lent et compliqué.
supercat

70

Java est un appel par valeur

Comment ça fonctionne

  • 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 contiennent la valeur du type de données primitif lui-même.C'est pourquoi si nous modifions la valeur de l'en-tête à l'intérieur de la méthode, elle ne reflète pas les modifications à l'extérieur.

  • S'il s'agit d'un type de données d'objet comme Foo foo = new Foo (), dans ce cas, une copie de l'adresse de l'objet passe comme un raccourci de fichier, supposons que nous ayons un fichier texte abc.txt sur C: \ desktop et supposons que nous faisons un raccourci de le même fichier et mettez-le dans C: \ desktop \ abc-shortcut donc quand vous accédez au fichier depuis C: \ desktop \ abc.txt et écrivez 'Stack Overflow' et fermez le fichier et encore une fois vous ouvrez le fichier depuis le raccourci puis vous écrire «est la plus grande communauté en ligne pour les programmeurs à apprendre», puis le changement total de fichier sera «Stack Overflow est la plus grande communauté en ligne pour les programmeurs à apprendre»ce qui signifie que peu importe où vous ouvrez le fichier, chaque fois que nous accédions au même fichier, ici, nous pouvons supposer Foo en tant que fichier et supposer foo stocké à 123hd7h (adresse d'origine comme C: \ desktop \ abc.txt ) address et 234jdid (adresse copiée commeC: \ desktop \ abc-shortcut qui contient en fait l'adresse d'origine du fichier à l'intérieur) .. Donc, pour une meilleure compréhension, créez un fichier de raccourci et ressentez-le ..


66

Il existe déjà d'excellentes réponses à ce sujet. Je voulais apporter une petite contribution en partageant un exemple très simple (qui va compiler) contrastant les comportements entre Pass-by-reference en c ++ et Pass-by-value en Java.

Quelques points:

  1. Le terme «référence» est surchargé avec deux significations distinctes. En Java, cela signifie simplement un pointeur, mais dans le contexte de "Pass-by-reference", cela signifie un handle vers la variable d'origine qui a été transmise.
  2. Java est pass-by-value . Java est un descendant de C (entre autres langages). Avant C, plusieurs (mais pas toutes) les langues antérieures comme FORTRAN et COBOL supportaient PBR, mais pas C. PBR a permis à ces autres langages d'apporter des modifications aux variables passées dans les sous-routines. Afin d'accomplir la même chose (c'est-à-dire changer les valeurs des variables à l'intérieur des fonctions), les programmeurs C ont passé des pointeurs vers des variables dans des fonctions. Les langages inspirés par C, tels que Java, ont emprunté cette idée et continuent de passer le pointeur vers les méthodes comme C l'a fait, sauf que Java appelle ses pointeurs Références. Encore une fois, il s'agit d'une utilisation différente du mot "référence" que dans "passe-par-référence".
  3. C ++ autorise le passage par référence en déclarant un paramètre de référence en utilisant le caractère "&" (qui se trouve être le même caractère utilisé pour indiquer "l'adresse d'une variable" en C et C ++). Par exemple, si nous passons un pointeur par référence, le paramètre et l'argument ne pointent pas seulement vers le même objet. Il s'agit plutôt de la même variable. Si l'un est défini sur une adresse différente ou sur null, l'autre aussi.
  4. Dans l'exemple C ++ ci-dessous, je passe un pointeur vers une chaîne terminée par null par référence . Et dans l'exemple Java ci-dessous, je passe une référence Java à une chaîne (à nouveau, la même chose qu'un pointeur vers une chaîne) par valeur. Notez la sortie dans les commentaires.

Exemple de passage par référence C ++:

using namespace std;
#include <iostream>

void change (char *&str){   // the '&' makes this a reference parameter
    str = NULL;
}

int main()
{
    char *str = "not Null";
    change(str);
    cout<<"str is " << str;      // ==>str is <null>
}

Java passe "une référence Java" par exemple de valeur

public class ValueDemo{

    public void change (String str){
        str = null;
    }

     public static void main(String []args){
        ValueDemo vd = new ValueDemo();
        String str = "not null";
        vd.change(str);
        System.out.println("str is " + str);    // ==> str is not null!!
                                                // Note that if "str" was
                                                // passed-by-reference, it
                                                // WOULD BE NULL after the
                                                // call to change().
     }
}

ÉDITER

Plusieurs personnes ont écrit des commentaires qui semblent indiquer qu'ils ne regardent pas mes exemples ou qu'ils ne reçoivent pas l'exemple c ++. Vous ne savez pas où se trouve la déconnexion, mais deviner l'exemple c ++ n'est pas clair. Je poste le même exemple dans pascal parce que je pense que le passage par référence semble plus propre dans pascal, mais je peux me tromper. Je pourrais juste dérouter davantage les gens; J'espère que non.

En pascal, les paramètres passés par référence sont appelés "paramètres var". Dans la procédure setToNil ci-dessous, veuillez noter le mot-clé 'var' qui précède le paramètre 'ptr'. Lorsqu'un pointeur est transmis à cette procédure, il est transmis par référence . Notez le comportement: lorsque cette procédure définit ptr sur nil (c'est-à-dire pascal pour NULL), elle définit l'argument sur nil - vous ne pouvez pas le faire en Java.

program passByRefDemo;
type 
   iptr = ^integer;
var
   ptr: iptr;

   procedure setToNil(var ptr : iptr);
   begin
       ptr := nil;
   end;

begin
   new(ptr);
   ptr^ := 10;
   setToNil(ptr);
   if (ptr = nil) then
       writeln('ptr seems to be nil');     { ptr should be nil, so this line will run. }
end.

EDIT 2

Quelques extraits de "THE Java Programming Language" de Ken Arnold, James Gosling (le gars qui a inventé Java) et David Holmes, chapitre 2, section 2.6.5

Tous les paramètres des méthodes sont passés "par valeur" . En d'autres termes, les valeurs des variables de paramètre dans une méthode sont des copies de l'invocateur spécifié comme arguments.

Il poursuit en faisant la même remarque concernant les objets. . .

Vous devez noter que lorsque le paramètre est une référence d'objet, c'est la référence d'objet - et non l'objet lui-même - qui est transmise "par valeur" .

Et vers la fin de la même section, il fait une déclaration plus large sur le fait que java n'est que passé par valeur et jamais passé par référence.

Le langage de programmation Java ne transmet pas d'objets par référence; il transmet les références d'objet par valeur . Étant donné que deux copies de la même référence font référence au même objet réel, les modifications apportées via une variable de référence sont visibles à travers l'autre. Il existe exactement un mode de passage de paramètres - passer par valeur - et qui permet de simplifier les choses.

Cette section du livre a une grande explication du passage de paramètres en Java et de la distinction entre pass-by-reference et pass-by-value et c'est par le créateur de Java. J'encourage n'importe qui à le lire, surtout si vous n'êtes toujours pas convaincu.

Je pense que la différence entre les deux modèles est très subtile et à moins que vous n'ayez fait la programmation là où vous avez réellement utilisé le passage par référence, il est facile de manquer où deux modèles diffèrent.

J'espère que cela règle le débat, mais ce ne sera probablement pas le cas.

EDIT 3

Je pourrais être un peu obsédé par ce post. Probablement parce que je pense que les créateurs de Java ont répandu par inadvertance de la désinformation. Si au lieu d'utiliser le mot "référence" pour les pointeurs, ils avaient utilisé autre chose, disons dingleberry, il n'y aurait pas eu de problème. Vous pourriez dire, "Java passe les dingleberries par valeur et non par référence", et personne ne serait confus.

C'est la raison pour laquelle seuls les développeurs Java ont des problèmes avec cela. Ils regardent le mot «référence» et pensent qu'ils savent exactement ce que cela signifie, donc ils ne prennent même pas la peine de considérer l'argument opposé.

Quoi qu'il en soit, j'ai remarqué un commentaire dans un article plus ancien, qui faisait une analogie avec un ballon que j'ai vraiment aimé. À tel point que j'ai décidé de coller des images clipart pour créer un ensemble de dessins animés pour illustrer ce point.

Passer une référence par valeur - Les modifications apportées à la référence ne sont pas reflétées dans la portée de l'appelant, mais les modifications apportées à l'objet le sont. En effet, la référence est copiée, mais l'original et la copie font référence au même objet. Passer des références d'objets par valeur

Passer par référence - Il n'y a pas de copie de la référence. La référence unique est partagée par l'appelant et la fonction appelée. Toute modification de la référence ou des données de l'objet est reflétée dans la portée de l'appelant. Passer par référence

EDIT 4

J'ai vu des articles sur ce sujet qui décrivent l'implémentation de bas niveau du passage de paramètres en Java, ce qui, je pense, est excellent et très utile car il rend concrète une idée abstraite. Cependant, pour moi, la question concerne davantage le comportement décrit dans la spécification du langage que l'implémentation technique du comportement. Ceci est un extrait de la spécification du langage Java, section 8.4.1 :

Lorsque la méthode ou le constructeur est invoqué (§15.12), les valeurs des expressions d'argument réelles initialisent les variables de paramètre nouvellement créées, chacune du type déclaré, avant l'exécution du corps de la méthode ou du constructeur. L'identifiant qui apparaît dans le DeclaratorId peut être utilisé comme un simple nom dans le corps de la méthode ou du constructeur pour faire référence au paramètre formel.

Cela signifie que java crée une copie des paramètres passés avant d'exécuter une méthode. Comme la plupart des gens qui ont étudié les compilateurs à l'université, j'ai utilisé "The Dragon Book" qui est LE livre des compilateurs. Il a une bonne description de "Appel par valeur" et "Appel par référence" dans le chapitre 1. La description Appel par valeur correspond exactement aux spécifications Java.

À l'époque où j'étudiais les compilateurs, dans les années 90, j'utilisais la première édition du livre de 1986 qui était antérieure à Java d'environ 9 ou 10 ans. Cependant, je viens de croiser une copie de la 2e édition de 2007 qui mentionne en fait Java! La section 1.6.6 intitulée «Mécanismes de passage de paramètres» décrit assez bien le passage de paramètres. Voici un extrait sous la rubrique "Call-by-value" qui mentionne Java:

En appel par valeur, le paramètre réel est évalué (s'il s'agit d'une expression) ou copié (s'il s'agit d'une variable). La valeur est placée à l'emplacement appartenant au paramètre formel correspondant de la procédure appelée. Cette méthode est utilisée en C et Java, et est une option courante en C ++, ainsi que dans la plupart des autres langages.


4
Honnêtement, vous pouvez simplifier cette réponse en disant que Java est une valeur de passage uniquement pour les types primitifs. Tout ce qui hérite d'Object est effectivement passé par référence, où la référence est le pointeur que vous passez.
Scuba Steve

1
@JuanMendes, je viens d'utiliser C ++ comme exemple; Je pourrais vous donner des exemples dans d'autres langues. Le terme "Passer par référence" existait bien avant que C ++ n'existe. C'est un terme de manuel avec une définition très spécifique. Et par définition, Java n'est pas passé par référence. Vous pouvez continuer à utiliser le terme de cette manière si vous le souhaitez, mais votre utilisation ne sera pas cohérente avec la définition du manuel. Ce n'est pas seulement un pointeur vers un pointeur. C'est une construction fournie par le langage pour permettre le "Pass By Reference". Veuillez regarder attentivement mon exemple, mais ne me croyez pas sur parole et cherchez par vous-même.
Sanjeev

2
@AutomatedMike Je pense que décrire Java comme "passe-par-référence" est également trompeur, leur référence n'est rien d'autre que des pointeurs. Comme Sanjeev l'a écrit, la référence et le pointeur sont des termes de manuel qui ont leur propre signification indépendamment de ce que les créateurs Java utilisent.
Jędrzej Dudkiewicz

1
@ArtanisZeratul le point est subtil, mais pas compliqué. Veuillez regarder le dessin animé que j'ai publié. J'ai senti que c'était assez simple à suivre. Que Java soit un pass-by-value n'est pas seulement une opinion. C'est vrai par la définition classique de la valeur de passage. En outre, «les gens qui disent toujours que c'est de la valeur» incluent des informaticiens comme James Gosling, le créateur de Java. Veuillez consulter les citations de son livre sous "Edit 2" dans mon article.
Sanjeev

1
Quelle bonne réponse, "passer par référence" n'existe pas en java (et dans d'autres langages comme JS).
newfolder

56

Pour autant que je sache, Java ne connaît l'appel que par valeur. Cela signifie que pour les types de données primitifs, vous travaillerez avec une copie et pour les objets, vous travaillerez avec une copie de la référence aux objets. Cependant, je pense qu'il y a des écueils; par exemple, cela ne fonctionnera pas:

public static void swap(StringBuffer s1, StringBuffer s2) {
    StringBuffer temp = s1;
    s1 = s2;
    s2 = temp;
}


public static void main(String[] args) {
    StringBuffer s1 = new StringBuffer("Hello");
    StringBuffer s2 = new StringBuffer("World");
    swap(s1, s2);
    System.out.println(s1);
    System.out.println(s2);
}

Cela remplira Hello World et non World Hello car dans la fonction de swap, vous utilisez des copys qui n'ont aucun impact sur les références dans le principal. Mais si vos objets ne sont pas immuables vous pouvez le changer par exemple:

public static void appendWorld(StringBuffer s1) {
    s1.append(" World");
}

public static void main(String[] args) {
    StringBuffer s = new StringBuffer("Hello");
    appendWorld(s);
    System.out.println(s);
}

Cela remplira Hello World sur la ligne de commande. Si vous changez StringBuffer en String, cela ne produira que Hello car String est immuable. Par exemple:

public static void appendWorld(String s){
    s = s+" World";
}

public static void main(String[] args) {
    String s = new String("Hello");
    appendWorld(s);
    System.out.println(s);
}

Cependant, vous pouvez créer un wrapper pour String comme celui-ci, ce qui le rendrait capable de l'utiliser avec Strings:

class StringWrapper {
    public String value;

    public StringWrapper(String value) {
        this.value = value;
    }
}

public static void appendWorld(StringWrapper s){
    s.value = s.value +" World";
}

public static void main(String[] args) {
    StringWrapper s = new StringWrapper("Hello");
    appendWorld(s);
    System.out.println(s.value);
}

edit: je crois que c'est aussi la raison d'utiliser StringBuffer pour "ajouter" deux chaînes car vous pouvez modifier l'objet d'origine que vous ne pouvez pas avec des objets immuables comme String.


+1 pour le test de swap - probablement le moyen le plus simple et le plus pertinent de distinguer entre le passage par référence et le passage d'une référence par valeur . Si vous pouvez facilement écrire une fonction swap(a, b)qui (1) permute aet à bpartir du PDV de l'appelant, (2) est indépendante du type dans la mesure où la saisie statique le permet (ce qui signifie que l'utiliser avec un autre type ne nécessite rien de plus que de changer les types déclarés de aet b) , et (3) n'exige pas que l'appelant passe explicitement un pointeur ou un nom, alors le langage prend en charge le passage par référence.
cHao

"..pour les types de données primitifs, vous travaillerez avec une copie et pour les objets, vous travaillerez avec une copie de la référence aux objets" - parfaitement écrit!
Raúl

55

Non, ce n'est pas passé par référence.

Java est passé par valeur selon la spécification du langage Java:

Lorsque la méthode ou le constructeur est invoqué (§15.12), les valeurs des expressions d'argument réelles initialisent les variables de paramètre nouvellement créées , chacune du type déclaré, avant l'exécution du corps de la méthode ou du constructeur. L'identifiant qui apparaît dans le DeclaratorId peut être utilisé comme un simple nom dans le corps de la méthode ou du constructeur pour faire référence au paramètre formel .


52

Permettez-moi d'essayer d'expliquer ma compréhension à l'aide de quatre exemples. Java est pass-by-value et non pass-by-reference

/ **

Passer par valeur

En Java, tous les paramètres sont transmis par valeur, c'est-à-dire que l'attribution d'un argument de méthode n'est pas visible pour l'appelant.

* /

Exemple 1:

public class PassByValueString {
    public static void main(String[] args) {
        new PassByValueString().caller();
    }

    public void caller() {
        String value = "Nikhil";
        boolean valueflag = false;
        String output = method(value, valueflag);
        /*
         * 'output' is insignificant in this example. we are more interested in
         * 'value' and 'valueflag'
         */
        System.out.println("output : " + output);
        System.out.println("value : " + value);
        System.out.println("valueflag : " + valueflag);

    }

    public String method(String value, boolean valueflag) {
        value = "Anand";
        valueflag = true;
        return "output";
    }
}

Résultat

output : output
value : Nikhil
valueflag : false

Exemple 2:

/ ** * * Pass By Value * * /

public class PassByValueNewString {
    public static void main(String[] args) {
        new PassByValueNewString().caller();
    }

    public void caller() {
        String value = new String("Nikhil");
        boolean valueflag = false;
        String output = method(value, valueflag);
        /*
         * 'output' is insignificant in this example. we are more interested in
         * 'value' and 'valueflag'
         */
        System.out.println("output : " + output);
        System.out.println("value : " + value);
        System.out.println("valueflag : " + valueflag);

    }

    public String method(String value, boolean valueflag) {
        value = "Anand";
        valueflag = true;
        return "output";
    }
}

Résultat

output : output
value : Nikhil
valueflag : false

Exemple 3:

/ ** Ce «Pass By Value» a un sentiment de «Pass By Reference»

Certaines personnes disent que les types primitifs et 'String' sont 'pass by value' et les objets 'pass by reference'.

Mais à partir de cet exemple, nous pouvons comprendre qu'il s'agit en fait d'un passage par valeur uniquement, en gardant à l'esprit que nous transmettons ici la référence comme valeur. ie: la référence est passée par valeur. C'est pourquoi sont capables de changer et cela reste vrai après la portée locale. Mais nous ne pouvons pas changer la référence réelle en dehors de la portée d'origine. ce que cela signifie est démontré par l'exemple suivant de PassByValueObjectCase2.

* /

public class PassByValueObjectCase1 {

    private class Student {
        int id;
        String name;
        public Student() {
        }
        public Student(int id, String name) {
            super();
            this.id = id;
            this.name = name;
        }
        public int getId() {
            return id;
        }
        public void setId(int id) {
            this.id = id;
        }
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        @Override
        public String toString() {
            return "Student [id=" + id + ", name=" + name + "]";
        }
    }

    public static void main(String[] args) {
        new PassByValueObjectCase1().caller();
    }

    public void caller() {
        Student student = new Student(10, "Nikhil");
        String output = method(student);
        /*
         * 'output' is insignificant in this example. we are more interested in
         * 'student'
         */
        System.out.println("output : " + output);
        System.out.println("student : " + student);
    }

    public String method(Student student) {
        student.setName("Anand");
        return "output";
    }
}

Résultat

output : output
student : Student [id=10, name=Anand]

Exemple 4:

/ **

En plus de ce qui a été mentionné dans l'exemple 3 (PassByValueObjectCase1.java), nous ne pouvons pas modifier la référence réelle en dehors de la portée d'origine. "

Remarque: je ne colle pas le code pour private class Student. La définition de classe pour Studentest identique à celle de Example3.

* /

public class PassByValueObjectCase2 {

    public static void main(String[] args) {
        new PassByValueObjectCase2().caller();
    }

    public void caller() {
        // student has the actual reference to a Student object created
        // can we change this actual reference outside the local scope? Let's see
        Student student = new Student(10, "Nikhil");
        String output = method(student);
        /*
         * 'output' is insignificant in this example. we are more interested in
         * 'student'
         */
        System.out.println("output : " + output);
        System.out.println("student : " + student); // Will it print Nikhil or Anand?
    }

    public String method(Student student) {
        student = new Student(20, "Anand");
        return "output";
    }

}

Résultat

output : output
student : Student [id=10, name=Nikhil]

49

Vous ne pouvez jamais passer par référence en Java, et l'une des façons évidentes consiste à renvoyer plusieurs valeurs à partir d'un appel de méthode. Considérez le bit de code suivant en C ++:

void getValues(int& arg1, int& arg2) {
    arg1 = 1;
    arg2 = 2;
}
void caller() {
    int x;
    int y;
    getValues(x, y);
    cout << "Result: " << x << " " << y << endl;
}

Parfois, vous souhaitez utiliser le même modèle en Java, mais vous ne pouvez pas; du moins pas directement. Au lieu de cela, vous pouvez faire quelque chose comme ceci:

void getValues(int[] arg1, int[] arg2) {
    arg1[0] = 1;
    arg2[0] = 2;
}
void caller() {
    int[] x = new int[1];
    int[] y = new int[1];
    getValues(x, y);
    System.out.println("Result: " + x[0] + " " + y[0]);
}

Comme expliqué dans les réponses précédentes, en Java, vous passez un pointeur vers le tableau en tant que valeur dans getValues. Cela suffit, car la méthode modifie ensuite l'élément de tableau et, par convention, vous vous attendez à ce que l'élément 0 contienne la valeur de retour. Évidemment, vous pouvez le faire par d'autres moyens, tels que la structuration de votre code afin que ce ne soit pas nécessaire, ou la construction d'une classe qui peut contenir la valeur de retour ou lui permettre d'être définie. Mais le modèle simple à votre disposition en C ++ ci-dessus n'est pas disponible en Java.


48

J'ai pensé apporter cette réponse pour ajouter plus de détails dans les spécifications.

Premièrement, quelle est la différence entre passer par référence et passer par valeur?

Le passage par référence signifie que le paramètre des fonctions appelées sera le même que l'argument passé des appelants (pas la valeur, mais l'identité - la variable elle-même).

Passer par valeur signifie que le paramètre des fonctions appelées sera une copie de l'argument passé des appelants.

Ou de wikipedia, sur le sujet du pass-by-reference

Dans l'évaluation d'appel par référence (également appelée passe-par-référence), une fonction reçoit une référence implicite à une variable utilisée comme argument, plutôt qu'une copie de sa valeur. Cela signifie généralement que la fonction peut modifier (c.-à-d. L'assigner à) la variable utilisée comme argument - quelque chose qui sera vu par son appelant.

Et au sujet de la valeur de passage

Dans l'appel par valeur, l'expression d'argument est évaluée et la valeur résultante est liée à la variable correspondante dans la fonction [...]. Si la fonction ou la procédure est en mesure d'affecter des valeurs à ses paramètres, seule sa copie locale est affectée [...].

Deuxièmement, nous devons savoir ce que Java utilise dans ses appels de méthode. Les états de spécification du langage Java

Lorsque la méthode ou le constructeur est invoqué (§15.12), les valeurs des expressions d'argument réelles initialisent les variables de paramètre nouvellement créées , chacune du type déclaré, avant l'exécution du corps de la méthode ou du constructeur.

Il affecte donc (ou lie) la valeur de l'argument à la variable de paramètre correspondante.

Quelle est la valeur de l'argument?

Considérons les types de référence, les états de la spécification de machine virtuelle Java

Il existe trois types de types de référence : les types de classe, les types de tableau et les types d'interface. Leurs valeurs sont des références à des instances de classe, des tableaux ou des instances de classe ou des tableaux créés dynamiquement qui implémentent des interfaces.

La spécification du langage Java indique également

Les valeurs de référence (souvent juste des références) sont des pointeurs vers ces objets et une référence null spéciale, qui ne fait référence à aucun objet.

La valeur d'un argument (d'un type de référence) est un pointeur sur un objet. Notez qu'une variable, un appel d'une méthode avec un type de retour de type référence et une expression de création d'instance ( new ...) se résolvent tous en une valeur de type référence.

Donc

public void method (String param) {}
...
String var = new String("ref");
method(var);
method(var.toString());
method(new String("ref"));

se lient tous la valeur d'une référence à un Stringexemple de paramètre nouvellement créée de la méthode, param. C'est exactement ce que décrit la définition de la valeur de passage. En tant que tel, Java est une valeur de passage .

Le fait que vous puissiez suivre la référence pour appeler une méthode ou accéder à un champ de l'objet référencé n'a absolument aucun rapport avec la conversation. La définition du passage par référence était

Cela signifie généralement que la fonction peut modifier (c.-à-d. L'assigner à) la variable utilisée comme argument - quelque chose qui sera vu par son appelant.

En Java, modifier la variable signifie la réaffecter. En Java, si vous réassigniez la variable dans la méthode, elle passerait inaperçue à l'appelant. La modification de l'objet référencé par la variable est un concept complètement différent.


Les valeurs primitives sont également définies dans la spécification de machine virtuelle Java, ici . La valeur du type est la valeur intégrale ou à virgule flottante correspondante, codée de manière appropriée (8, 16, 32, 64, etc. bits).


42

En Java, seules les références sont transmises et sont transmises par valeur:

Les arguments Java sont tous passés par valeur (la référence est copiée lorsqu'elle est utilisée par la méthode):

Dans le cas des types primitifs, le comportement Java est simple: la valeur est copiée dans une autre instance du type primitif.

Dans le cas des objets, c'est la même chose: les variables d'objet sont des pointeurs (compartiments) contenant uniquement l' adresse d'objet qui a été créée à l'aide du mot-clé "new" et sont copiées comme des types primitifs.

Le comportement peut apparaître différent des types primitifs: car la variable objet copiée contient la même adresse (vers le même objet). Le contenu / les membres de l'objet peuvent encore être modifiés dans une méthode et être ensuite accessibles à l'extérieur, donnant l'illusion que l'objet (contenant) lui-même a été transmis par référence.

Les objets "string" semblent être un bon contre-exemple à la légende urbaine qui dit que "les objets sont passés par référence":

En effet, en utilisant une méthode, vous ne pourrez jamais mettre à jour la valeur d'une chaîne passée en argument:

Un objet String, contient des caractères par un tableau déclaré final qui ne peut pas être modifié. Seule l'adresse de l'objet peut être remplacée par une autre utilisant "nouveau". L'utilisation de "nouveau" pour mettre à jour la variable ne permettra pas d'accéder à l'objet de l'extérieur, car la variable a été initialement passée par valeur et copiée.


C'est donc byRef en ce qui concerne les objets et byVal en ce qui concerne les primitives?
Mox

@mox s'il vous plaît lire: Les objets ne sont pas passés par référence, ceci est une ledgend: String a = new String ("inchanged");
user1767316

1
@Aaron "Passer par référence" ne signifie pas "passer une valeur qui est membre d'un type appelé" référence "en Java". Les deux utilisations de la «référence» signifient des choses différentes.
philipxy

2
Réponse assez confuse. La raison pour laquelle vous ne pouvez pas modifier un objet chaîne dans une méthode, qui est passée par référence, est que les objets chaîne sont immuables par conception et vous ne pouvez pas faire des choses comme strParam.setChar ( i, newValue ). Cela dit, les chaînes, comme toute autre chose, sont passées par valeur et, puisque String est un type non primitif, cette valeur est une référence à la chose qui a été créée avec new, et vous pouvez le vérifier en utilisant String.intern () .
zakmck

1
Au lieu de cela, vous ne pouvez pas modifier une chaîne via param = "une autre chaîne" (équivalent à une nouvelle chaîne ("une autre chaîne")) car la valeur de référence de param (qui pointe désormais vers "une autre chaîne") ne peut pas revenir de la méthode corps. Mais cela est vrai pour tout autre objet, la différence est que, lorsque l'interface de classe le permet, vous pouvez faire param.changeMe () et l'objet de niveau supérieur référencé changera parce que param le pointe, malgré la valeur de référence de param elle-même (l'emplacement adressé, en termes de C) ne peut pas revenir de la méthode.
zakmck

39

La distinction, ou peut-être juste la façon dont je me souviens, car j'avais l'habitude d'être sous la même impression que l'affiche originale, c'est la suivante: Java est toujours passé par valeur. Tous les objets (en Java, sauf les primitives) en Java sont des références. Ces références sont passées par valeur.


2
Je trouve que votre avant-dernière phrase est très trompeuse. Ce n'est pas vrai que "tous les objets en Java sont des références". Ce ne sont que les références à ces objets qui sont des références.
Dawood ibn Kareem du

37

Comme beaucoup de gens l'ont mentionné auparavant, Java est toujours une valeur de passage

Voici un autre exemple qui vous aidera à comprendre la différence ( l'exemple de swap classique ):

public class Test {
  public static void main(String[] args) {
    Integer a = new Integer(2);
    Integer b = new Integer(3);
    System.out.println("Before: a = " + a + ", b = " + b);
    swap(a,b);
    System.out.println("After: a = " + a + ", b = " + b);
  }

  public static swap(Integer iA, Integer iB) {
    Integer tmp = iA;
    iA = iB;
    iB = tmp;
  }
}

Tirages:

Avant: a = 2, b = 3
Après: a = 2, b = 3

Cela se produit car iA et iB sont de nouvelles variables de référence locales qui ont la même valeur que les références passées (elles pointent respectivement vers a et b). Donc, essayer de changer les références de iA ou iB ne changera que dans la portée locale et non en dehors de cette méthode.


32

Java a seulement passer par valeur. Un exemple très simple pour valider cela.

public void test() {
    MyClass obj = null;
    init(obj);
    //After calling init method, obj still points to null
    //this is because obj is passed as value and not as reference.
}
private void init(MyClass objVar) {
    objVar = new MyClass();
}

4
C'est le moyen le plus clair et le plus simple de voir que Java passe par valeur. La valeur de obj( null) a été transmise à init, pas une référence à obj.
David Schwartz

31

J'y pense toujours comme "passer par copie". Il s'agit d'une copie de la valeur, qu'elle soit primitive ou de référence. Si c'est une primitive c'est une copie des bits qui sont la valeur et si c'est un Object c'est une copie de la référence.

public class PassByCopy{
    public static void changeName(Dog d){
        d.name = "Fido";
    }
    public static void main(String[] args){
        Dog d = new Dog("Maxx");
        System.out.println("name= "+ d.name);
        changeName(d);
        System.out.println("name= "+ d.name);
    }
}
class Dog{
    public String name;
    public Dog(String s){
        this.name = s;
    }
}

sortie de java PassByCopy:

nom = Maxx
nom = Fido

Les classes wrapper primitives et les chaînes sont immuables, donc tout exemple utilisant ces types ne fonctionnera pas de la même manière que les autres types / objets.


5
« passer copie » est ce qui passe par la valeur des moyens .
TJ Crowder

@TJCrowder. "Passer par copie" peut être une façon plus appropriée d'exprimer le même concept à des fins Java: car sémantiquement, il est très difficile de chausse-pied dans une idée semblable à passer par référence, ce que vous voulez en Java.
mike rodent

@mikerodent - Désolé, je n'ai pas suivi ça. :-) "Pass-by-value" et "pass-by-reference" sont les termes d'art corrects pour ces concepts. Nous devons les utiliser (fournir leurs définitions lorsque les gens en ont besoin) plutôt que d'en inventer de nouvelles. Ci-dessus, j'ai dit que «passer par copie» est ce que signifie «passer par valeur», mais en réalité, ce n'est pas clair ce que signifie «passer par copie» - une copie de quoi? La valeur? (Par exemple, référence d'objet.) L'objet lui-même? Je m'en tiens donc aux termes de l'art. :-)
TJ Crowder

29

Contrairement à certains autres langages, Java ne vous permet pas de choisir entre passer par valeur et passer par référence - tous les arguments sont passés par valeur. Un appel de méthode peut transmettre deux types de valeurs à une méthode: des copies de valeurs primitives (par exemple, des valeurs de int et double) et des copies de références à des objets.

Lorsqu'une méthode modifie un paramètre de type primitif, les modifications apportées au paramètre n'ont aucun effet sur la valeur d'argument d'origine dans la méthode appelante.

En ce qui concerne les objets, les objets eux-mêmes ne peuvent pas être transmis aux méthodes. On passe donc la référence (adresse) de l'objet. Nous pouvons manipuler l'objet d'origine en utilisant cette référence.

Comment Java crée et stocke des objets: Lorsque nous créons un objet, nous stockons l'adresse de l'objet dans une variable de référence. Analysons l'énoncé suivant.

Account account1 = new Account();

«Account account1» est le type et le nom de la variable de référence, «=» est l'opérateur d'affectation, «new» demande la quantité d'espace requise du système. Le constructeur à droite du mot-clé new qui crée l'objet est appelé implicitement par le mot-clé new. L'adresse de l'objet créé (résultat de la valeur de droite, qui est une expression appelée "expression de création d'instance de classe") est affectée à la valeur de gauche (qui est une variable de référence avec un nom et un type spécifiés) à l'aide de l'opérateur d'affectation.

Bien que la référence d'un objet soit passée par valeur, une méthode peut toujours interagir avec l'objet référencé en appelant ses méthodes publiques à l'aide de la copie de la référence de l'objet. Étant donné que la référence stockée dans le paramètre est une copie de la référence transmise en tant qu'argument, le paramètre dans la méthode appelée et l'argument dans la méthode appelante font référence au même objet en mémoire.

La transmission de références à des tableaux, au lieu des objets tableau eux-mêmes, est logique pour des raisons de performances. Parce que tout en Java est passé par valeur, si des objets de tableau étaient passés, une copie de chaque élément serait passée. Pour les baies de grande taille, cela ferait perdre du temps et consommerait un espace de stockage considérable pour les copies des éléments.

Dans l'image ci-dessous, vous pouvez voir que nous avons deux variables de référence (appelées pointeurs en C / C ++, et je pense que ce terme facilite la compréhension de cette fonctionnalité.) Dans la méthode principale. Les variables primitives et de référence sont conservées dans la mémoire de la pile (côté gauche dans les images ci-dessous). Les variables de référence array1 et array2 "pointent" (comme les programmeurs C / C ++ l'appellent) ou font respectivement référence aux tableaux a et b, qui sont des objets (les valeurs que ces variables de référence contiennent sont des adresses d'objets) dans la mémoire de tas (côté droit dans les images ci-dessous) .

Exemple de passage par valeur 1

Si nous transmettons la valeur de la variable de référence array1 comme argument à la méthode reverseArray, une variable de référence est créée dans la méthode et cette variable de référence commence à pointer vers le même tableau (a).

public class Test
{
    public static void reverseArray(int[] array1)
    {
        // ...
    }

    public static void main(String[] args)
    {
        int[] array1 = { 1, 10, -7 };
        int[] array2 = { 5, -190, 0 };

        reverseArray(array1);
    }
}

Exemple de passage par valeur 2

Donc, si nous disons

array1[0] = 5;

dans la méthode reverseArray, cela fera un changement dans le tableau a.

Nous avons une autre variable de référence dans la méthode reverseArray (array2) qui pointe vers un tableau c. Si nous devions dire

array1 = array2;

dans la méthode reverseArray, la variable de référence array1 dans la méthode reverseArray cesserait de pointer vers le tableau a et commencerait à pointer vers le tableau c (ligne pointillée dans la deuxième image).

Si nous renvoyons la valeur de la variable de référence array2 comme valeur de retour de la méthode reverseArray et assignons cette valeur à la variable de référence array1 dans la méthode principale, array1 in main commencera à pointer vers le tableau c.

Écrivons donc tout ce que nous avons fait en même temps maintenant.

public class Test
{
    public static int[] reverseArray(int[] array1)
    {
        int[] array2 = { -7, 0, -1 };

        array1[0] = 5; // array a becomes 5, 10, -7

        array1 = array2; /* array1 of reverseArray starts
          pointing to c instead of a (not shown in image below) */
        return array2;
    }

    public static void main(String[] args)
    {
        int[] array1 = { 1, 10, -7 };
        int[] array2 = { 5, -190, 0 };

        array1 = reverseArray(array1); /* array1 of 
         main starts pointing to c instead of a */
    }
}

entrez la description de l'image ici

Et maintenant que la méthode reverseArray est terminée, ses variables de référence (array1 et array2) ont disparu. Ce qui signifie que nous n'avons maintenant que les deux variables de référence dans la méthode principale array1 et array2 qui pointent vers les tableaux c et b respectivement. Aucune variable de référence ne pointe vers l'objet (tableau) a. Il est donc éligible pour la collecte des ordures.

Vous pouvez également affecter la valeur de array2 in main à array1. array1 commencerait à pointer vers b.


27

J'ai créé un fil consacré à ce genre de questions pour tous les langages de programmation ici .

Java est également mentionné . Voici le bref résumé:

  • Java lui transmet les paramètres par valeur
  • "par valeur" est le seul moyen en java de passer un paramètre à une méthode
  • l'utilisation de méthodes à partir de l'objet donné en paramètre modifiera l'objet lorsque les références pointent vers les objets d'origine. (si cette méthode elle-même modifie certaines valeurs)

27

Pour faire court, les objets Java ont des propriétés très particulières.

En général, Java a des types primitifs ( int, bool, char, double, etc.) qui sont transmis directement par la valeur. Ensuite, Java a des objets (tout ce qui en découle java.lang.Object). Les objets sont en fait toujours gérés via une référence (une référence étant un pointeur que vous ne pouvez pas toucher). Cela signifie qu'en effet, les objets sont passés par référence, car les références ne sont normalement pas intéressantes. Cela signifie cependant que vous ne pouvez pas changer quel objet est pointé car la référence elle-même est passée par valeur.

Cela vous semble-t-il étrange et déroutant? Voyons comment les implémentations C passent par référence et passent par valeur. En C, la convention par défaut est passe par valeur. void foo(int x)passe un int par valeur. void foo(int *x)est une fonction qui ne veut pas int a, mais un pointeur vers un int: foo(&a). On utiliserait cela avec l' &opérateur pour passer une adresse variable.

Prenez ceci en C ++, et nous avons des références. Les références sont fondamentalement (dans ce contexte) du sucre syntaxique qui cachent la partie pointeur de l'équation: void foo(int &x)est appelé par foo(a), où le compilateur lui-même sait qu'il s'agit d'une référence et l'adresse de la non-référence adoit être transmise. En Java, toutes les variables faisant référence à des objets sont en fait de type référence, ce qui oblige en fait l'appel par référence pour la plupart des intentions et des buts sans le contrôle fin (et la complexité) offert, par exemple, par C ++.


Je pense qu'il est très proche de ma compréhension de l'objet Java et de sa référence. L'objet en Java est transmis à une méthode par une copie de référence (ou alias).
MaxZoom
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.