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:
- 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.
- 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".
- 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.
- 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 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 :
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.