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?
java
methods
parameter-passing
pass-by-reference
pass-by-value
utilisateur4315
la source
la source
call-by-value
etcall-by-reference
diffère de manière très cruciale . (Personnellement, je préfère utilisercall-by-object-sharing
ces jours-cicall-by-value[-of-the-reference]
, car cela décrit la sémantique à un niveau élevé et ne crée pas de conflit aveccall-by-value
, ce qui est l'implémentation sous-jacente.)swap(x,y)
test .Réponses:
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:
Dans l'exemple ci
aDog.getName()
- dessus sera toujours de retour"Max"
. La valeur à l'aDog
intérieurmain
n'est pas modifiée dans la fonctionfoo
avecDog
"Fifi"
car la référence d'objet est passée par valeur. Si elle était adoptée par référence, leaDog.getName()
dansmain
retournerait"Fifi"
après l'appel àfoo
.Également:
Dans l'exemple ci-dessus,
Fifi
est le nom du chien après l'appel àfoo(aDog)
car le nom de l'objet a été défini à l'intérieur defoo(...)
. Toutes les opérations quifoo
s'exécutentd
sont telles que, à toutes fins pratiques, elles sont exécutéesaDog
, mais il n'est pas possible de modifier la valeur de la variableaDog
elle-même.la source
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
n'est pas un chien; c'est en fait un pointeur vers un chien.
Ce que cela signifie, c'est quand vous avez
vous passez essentiellement l' adresse de l'
Dog
objet créé à lafoo
méthode.(Je dis essentiellement parce que les pointeurs Java ne sont pas des adresses directes, mais il est plus facile de les penser de cette façon)
Supposons que l'
Dog
objet réside à l'adresse mémoire 42. Cela signifie que nous passons 42 à la méthode.si la méthode était définie comme
regardons ce qui se passe.
someDog
est réglé sur la valeur 42someDog
est suivi jusqu'à ceDog
qu'il pointe vers (l'Dog
objet à l'adresse 42)Dog
(celui à l'adresse 42) est invité à changer son nom en MaxDog
est créé. Disons qu'il est à l'adresse 74someDog
à 74Dog
qu'il pointe vers (l'Dog
objet à l'adresse 74)Dog
(celui à l'adresse 74) est prié de changer son nom en RowlfVoyons maintenant ce qui se passe en dehors de la méthode:
A
myDog
changé?Voilà la clé.
Gardant à l'esprit qu'il
myDog
s'agit d'un pointeur et non d'un réelDog
, la réponse est NON.myDog
a toujours la valeur 42; il pointe toujours vers l'originalDog
(mais notez qu'en raison de la ligne "AAA", son nom est maintenant "Max" - toujours le même chien;myDog
la 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
foo
méthode que nous avons définie ci-dessus aurait changé oùmyDog
pointait lors de son affectationsomeDog
sur la ligne BBB.Considérez les paramètres de référence comme des alias pour la variable transmise. Lorsque cet alias est affecté, la variable transmise l'est également.
la source
Java transmet toujours les arguments par valeur , PAS par référence.
Permettez-moi d'expliquer cela à travers un exemple :
Je vais l'expliquer par étapes:
Déclarer une référence nommée
f
de typeFoo
et lui affecter un nouvel objet de typeFoo
avec un attribut"f"
.Du côté méthode, une référence de type
Foo
avec un noma
est déclarée et elle est initialement affectéenull
.Lorsque vous appelez la méthode
changeReference
, la référence sea
verra attribuer l'objet qui est passé en argument.Déclarer une référence nommée
b
de typeFoo
et lui affecter un nouvel objet de typeFoo
avec un attribut"b"
.a = b
fait une nouvelle affectation à la référencea
, nonf
, de l'objet dont son attribut est"b"
.Lorsque vous appelez la
modifyReference(Foo c)
méthode, une référencec
est créée et affectée à l'objet avec attribut"f"
.c.setAttribute("c");
changera l'attribut de l'objet qui fait référencec
à lui, et c'est le même objet qui fait référencef
à lui.J'espère que vous comprenez maintenant comment fonctionne le passage d'objets comme arguments en Java :)
la source
a
pointe vers le même objet quef
(et n'obtient jamais sa propre copie de l'objetf
pointe vers), toutes les modifications apportées à l'objet effectuées à l'aide dea
devraient également être modifiéesf
(car elles fonctionnent toutes les deux avec le même objet ), il faut donc à un moment donnéa
obtenir sa propre copie de l'objetf
pointé vers.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.
Pensez maintenant à ce que fait / est la référence / variable d'un objet:
Dans ce qui suit (veuillez ne pas essayer de compiler / exécuter ceci ...):
Ce qui se produit?
Une image vaut mieux que mille mots:
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!
Bien sûr, vous pouvez le raccourcir et dire simplement que Java est une valeur de passage!
la source
public void foo(Car car){ ... }
,car
est local àfoo
et qu'il contient l'emplacement de tas de l'objet? Donc, si je modifiecar
la valeur decar = new Car()
, cela pointera vers un objet différent sur le tas? et si je change la valeur decar
la propriété decar.Color = "Red"
, l'objet dans le tas pointé parcar
sera modifié. De même, c'est la même chose en C #? Répondez, s'il vous plaît! Merci!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.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
int
,long
, etc.), le passage par valeur est la valeur réelle de la primitive (par exemple, 3).Donc, si vous en avez
doSomething(foo)
et quepublic 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.
la source
Java transmet les références par valeur.
Vous ne pouvez donc pas modifier la référence transmise.
la source
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:
résulte en ceci:
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:
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.
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.
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 appelonsshout()
: un nouveau cadre de pile est créé et un nouvel identifiant,name
est créé et attribué l'adresse de la chaîne déjà existante.Alors, valeur, référence? Vous dites "pomme de terre".
la source
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.
En Java,
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.
la source
Dog **objPtrPtr
à l'exemple C ++, de cette façon, nous pouvons modifier ce que le pointeur "pointe vers".Java transmet les références aux objets par valeur.
la source
Fondamentalement, la réaffectation des paramètres d'objet n'affecte pas l'argument, par exemple,
imprimera au
"Hah!"
lieu denull
. La raison pour laquelle cela fonctionne est parce quebar
c'est une copie de la valeur debaz
, qui n'est qu'une référence à"Hah!"
. Si elle était la référence elle - même, puisfoo
aurait redéfinibaz
ànull
.la source
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".
la source
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.
la source
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: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 :
Déroulement du programme:
Création de deux objets Point différents avec deux références différentes associées.
Comme prévu, la sortie sera:
Sur cette ligne, le passage par valeur entre en jeu ...
Les références
pnt1
etpnt2
sont passées par valeur à la méthode délicate, ce qui signifie que maintenant la vôtre fait référencepnt1
etpnt2
a soncopies
nomarg1
etarg2
.Sopnt1
etarg1
pointe vers le même objet. (Idem pour lepnt2
etarg2
)Dans la
tricky
méthode:Suivant dans la
tricky
méthodeIci, vous créez d'abord une nouvelle
temp
référence de point qui pointera au même endroit que laarg1
référence. Ensuite, vous déplacez la référencearg1
pour pointer vers le même endroit commearg2
référence. Enfinarg2
va pointer au même endroit commetemp
.De là , la portée de la
tricky
mé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:Alors maintenant, complètement l'exécution du programme sera:
la source
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
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:
Le résultat de ce programme est:
Comprenons étape par étape:
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).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.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:
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 unNullPointerException
, comme illustré ci-dessous:J'espère que cela vous aidera.
la source
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,
5
nous devons voyager: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:
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:
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é.Foo = 11
), cela pourrait changer Foo globalement (dépend du langage, c'est-à-dire Java ou comme leprocedure 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 changer47
, par exemple49
. À 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 de47
n'a pas changé globalement, seulement celui à l'intérieur de la méthode, car47
était également une copie de la méthode.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érencerRef2Foo
(c'est-à-dire223
), d'atteindre et de modifier la valeur pointée47
, disons,49
cela affectera Foo globalement , car dans ce cas, les méthodes ont obtenu une copie223
mais le référencé47
n'existe qu'une seule fois, et changer cela à49
conduira chaqueRef2Foo
double 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 .
la source
addr
qu'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.AtomicReference
et 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, leAtomicReference
[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é.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 ..
la source
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:
Exemple de passage par référence C ++:
Java passe "une référence Java" par exemple de valeur
É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.
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
Il poursuit en faisant la même remarque concernant les objets. . .
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.
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 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.
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 :
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:
la source
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:
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:
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:
Cependant, vous pouvez créer un wrapper pour String comme celui-ci, ce qui le rendrait capable de l'utiliser avec Strings:
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.
la source
swap(a, b)
qui (1) permutea
et àb
partir 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 dea
etb
) , 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.Non, ce n'est pas passé par référence.
Java est passé par valeur selon la spécification du langage Java:
la source
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:
Résultat
Exemple 2:
/ ** * * Pass By Value * * /
Résultat
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.
* /
Résultat
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 pourStudent
est identique à celle de Example3.* /
Résultat
la source
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 ++:
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:
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.la source
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?
Ou de wikipedia, sur le sujet du pass-by-reference
Et au sujet de la valeur de passage
Deuxièmement, nous devons savoir ce que Java utilise dans ses appels de méthode. Les états de spécification du langage Java
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
La spécification du langage Java indique également
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
se lient tous la valeur d'une référence à un
String
exemple 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
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).
la source
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.
la source
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 () .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.
la source
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 ):
Tirages:
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.
la source
Java a seulement passer par valeur. Un exemple très simple pour valider cela.
la source
obj
(null
) a été transmise àinit
, pas une référence àobj
.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.
sortie de java PassByCopy:
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.
la source
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» 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) .
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).
Donc, si nous disons
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
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.
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.
la source
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é:
la source
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écoulejava.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 pasint 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é parfoo(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érencea
doit ê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 ++.la source