En Java spécifiquement, mais probablement aussi dans d'autres langages: quand serait-il utile d'avoir deux références au même objet?
Exemple:
Dog a = new Dog();
Dob b = a;
Y a-t-il une situation où cela serait utile? Pourquoi serait-ce une solution préférée à l'utilisation de a
chaque fois que vous souhaitez interagir avec l'objet représenté par a
?
Un exemple est quand vous voulez avoir le même objet dans deux listes séparées:
Dog myDog = new Dog();
List dogsWithRabies = new ArrayList();
List dogsThatCanPlayPiano = new ArrayList();
dogsWithRabies.add(myDog);
dogsThatCanPlayPiano.add(myDog);
// Now each List has a reference to the same dog
Une autre utilisation est lorsque vous avez le même objet jouant plusieurs rôles:
Person p = new Person("Bruce Wayne");
Person batman = p;
Person ceoOfWayneIndustries = p;
C'est en fait une question étonnamment profonde! L'expérience du C++ moderne (et des langages issus du C++ moderne, comme Rust) suggère que très souvent, vous ne voulez pas ça! Pour la plupart des données, vous voulez un unique ou unique ("propriétaire") ) référence. Cette idée est également la principale raison de systèmes de type linéaire .
Cependant, même dans ce cas, vous voulez généralement des références "empruntées" de courte durée qui sont utilisées pour accéder brièvement à la mémoire mais qui ne durent pas pendant une fraction significative du temps où les données existent. Le plus souvent, lorsque vous passez un objet comme argument à une fonction différente (les paramètres sont aussi des variables!):
void encounter(Dog a) {
hissAt(a);
}
void hissAt(Dog b) {
// ...
}
Un cas moins courant lorsque vous utilisez l'un des deux objets en fonction d'une condition, faisant essentiellement la même chose quel que soit votre choix:
Dog a, b;
Dog goodBoy = whoseAGoodBoy ? a : b;
feed(goodBoy);
walk(goodBoy);
pet(goodBoy);
Pour revenir à des utilisations plus courantes, mais en laissant les variables locales derrière, nous nous tournons vers les champs: par exemple, les widgets dans les frameworks GUI ont souvent des widgets parents, donc votre grand cadre contenant dix boutons aurait au moins dix références pointant dessus (plus quelques autres de son parent et peut-être des écouteurs d'événements, etc.). Tout type de graphe d'objets, et certains types d'arbres d'objets (ceux avec des références parent/frère), ont plusieurs objets se référant chacun au même objet. Et pratiquement chaque ensemble de données est en fait un graphique ;-)
Variables temporaires: considérez le pseudocode suivant.
Object getMaximum(Collection objects) {
Object max = null;
for (Object candidate IN objects) {
if ((max is null) OR (candidate > max)) {
max = candidate;
}
}
return max;
}
Les variables max
et candidate
peuvent pointer vers le même objet, mais l'affectation des variables change en utilisant des règles différentes et à des moments différents.
Cette méthode est idéale lorsque vous avez plusieurs objets qui rappellent tous un autre objet qui peut être utilisé de manière non contextuelle.
Par exemple, si vous avez une interface à onglets, vous pouvez avoir Tab1, Tab2 et Tab3. Vous voudrez peut-être également pouvoir utiliser une variable commune quel que soit l'onglet sur lequel l'utilisateur se trouve pour simplifier votre code et éviter d'avoir à comprendre à la volée et sur quel onglet votre utilisateur se trouve.
Tab Tab1 = new Tab();
Tab Tab2 = new Tab();
Tab Tab3 = new Tab();
Tab CurrentTab = new Tab();
Ensuite, dans chacun des onglets numérotés onClick, vous pouvez modifier CurrentTab pour référencer cet onglet.
CurrentTab = Tab3;
Maintenant, dans votre code, vous pouvez appeler "CurrentTab" en toute impunité sans avoir besoin de savoir sur quel onglet vous vous trouvez. Vous pouvez également mettre à jour les propriétés de CurrentTab et elles descendent automatiquement vers l'onglet référencé.
Il existe de nombreux scénarios dans lesquels b
doit être une référence à un "a
" inconnu pour être utile . En particulier:
b
pointe au moment de la compilation.Par exemple:
Paramètres
public void DoSomething(Thing &t) {
}
t
est une référence à une variable d'une portée extérieure.
valeurs de retour et autres valeurs conditionnelles
Thing a = Thing.Get("a");
Thing b = Thing.Get("b");
Thing biggerThing = Thing.max(a, b);
Thing z = a.IsMoreZThan(b) ? a : b;
biggerThing
et z
sont chacun des références à a
ou b
. Nous ne savons pas lequel au moment de la compilation.
Lambdas et leurs valeurs de retour
Thing t = someCollection.FirstOrDefault(x => x.whatever > 123);
x
est un paramètre (exemple 1 ci-dessus) et t
est une valeur de retour (exemple 2 ci-dessus)
Collections
indexByName.add(t.name, t);
process(indexByName["some name"]);
index["some name"]
est, dans une large mesure, une apparence plus sophistiquée b
. C'est un alias pour un objet qui a été créé et inséré dans la collection.
Boucles
foreach (Thing t in things) {
/* `t` is a reference to a thing in a collection */
}
t
est une référence à un élément retourné (exemple 2) par un itérateur (exemple précédent).
Pour compléter les autres réponses, vous pouvez également vouloir parcourir une structure de données différemment, en partant du même endroit. Par exemple, si vous aviez une BinaryTree a = new BinaryTree(...); BinaryTree b = a
, vous pouvez parcourir le chemin le plus à gauche de l'arborescence avec a
et son chemin le plus à droite avec b
, en utilisant quelque chose comme:
while (!a.equals(null) && !b.equals(null)) {
a = a.left();
b = b.right();
}
Cela fait un moment que je n'ai pas écrit Java, de sorte que le code peut ne pas être correct ou sensé. Prenez-le plus comme pseudocode.
C'est un point crucial, mais à mon humble avis, cela vaut la peine d'être compris.
Toutes les langues OO font toujours des copies de références, et ne copient jamais un objet de manière "invisible". Il serait beaucoup plus difficile d'écrire des programmes si OO les langues fonctionnaient d'une autre manière. Par exemple, les fonctions et les méthodes ne pourraient jamais mettre à jour un objet. Java, et la plupart des langages OO seraient presque impossibles à utiliser sans une complexité supplémentaire importante).
Un objet dans un programme est censé avoir une signification. Par exemple, il représente quelque chose de spécifique dans le monde physique réel. Il est généralement logique d'avoir de nombreuses références à la même chose. Par exemple, mon adresse personnelle peut être communiquée à de nombreuses personnes et organisations, et cette adresse fait toujours référence au même emplacement physique. Donc, le premier point est que les objets représentent souvent quelque chose de spécifique, réel ou concret; et donc être capable d'avoir de nombreuses références à la même chose est extrêmement utile. Sinon, il serait plus difficile d'écrire des programmes.
Chaque fois que vous passez a
comme argument/paramètre à une autre fonction, par exemple appelfoo(Dog aDoggy);
ou appliquez une méthode à a
, le code de programme sous-jacent fait une copie de la référence, pour produire une deuxième référence au même objet.
De plus, si le code avec une référence copiée se trouve dans un thread différent, les deux peuvent être utilisés simultanément pour accéder au même objet.
Ainsi, dans la plupart des programmes utiles, il y aura plusieurs références au même objet, car c'est la sémantique de la plupart des langages de programmation OO.
Maintenant, si nous y pensons, parce que le passage par référence est le mécanisme niquement disponible dans de nombreux langages OO (C++ prend en charge les deux), nous pouvons nous attendre à ce que ce soit le 'right' par défaut comportement.
À mon humble avis, l'utilisation de références est la bonne par défaut, pour deux raisons:
Il y a aussi un argument d'efficacité; la copie d'objets entiers est moins efficace que la copie d'une référence. Cependant, je pense que cela manque le point. Les références multiples au même objet ont plus de sens et sont plus faciles à utiliser, car elles correspondent à la sémantique du monde physique réel.
Donc, à mon humble avis, il généralement est logique d'avoir plusieurs références au même objet. Dans les cas inhabituels où cela n'a pas de sens dans le contexte d'un algorithme, la plupart des langues offrent la possibilité de faire un "clone" ou une copie complète. Cependant, ce n'est pas la valeur par défaut.
Je pense que les gens qui soutiennent que cela devrait pas être la valeur par défaut utilisent un langage qui ne fournit pas de collecte automatique des ordures. Par exemple, C++ à l'ancienne. Le problème est qu'ils doivent trouver un moyen de collecter des objets "morts" et non de récupérer des objets qui pourraient encore être nécessaires; avoir plusieurs références au même objet rend cela difficile.
Je pense que si C++ avait une collecte des ordures suffisamment peu coûteuse, de sorte que tous les objets référencés soient récupérés, alors une grande partie de l'objection disparaît. Il y aura toujours des cas où la sémantique de référence est pas ce qui est nécessaire. Cependant, d'après mon expérience, les personnes qui peuvent identifier ces situations sont également généralement capables de choisir la sémantique appropriée de toute façon.
Je crois qu'il y a des preuves qu'une grande quantité de code dans un programme C++ est là pour gérer ou atténuer la récupération de place. Cependant, l'écriture et le maintien de ce type de code "infrastructurel" augmentent les coûts; il est là pour rendre le langage plus facile à utiliser ou plus robuste. Ainsi, par exemple, le langage Go est conçu en mettant l'accent sur la correction de certaines des faiblesses de C++, et il n'a pas d'autre choix que la récupération de place.
Ceci n'est bien sûr pas pertinent dans le contexte de Java. Il a également été conçu pour être facile à utiliser, tout comme la collecte des ordures. Par conséquent, avoir plusieurs références est la sémantique par défaut et est relativement sûr dans le sens où les objets ne sont pas récupérés tant qu'il y a une référence à eux. Bien sûr, ils peuvent être conservés par une structure de données, car le programme ne se range pas correctement lorsqu'il a vraiment fini avec un objet.
Donc, en revenant à votre question (avec un peu de généralisation), quand voudriez-vous plus d'une référence au même objet? À peu près dans toutes les situations auxquelles je peux penser. Ils sont la sémantique par défaut du mécanisme de passage de paramètres de la plupart des langues. Je suggère que c'est parce que la sémantique par défaut de la manipulation des objets qui existe dans le monde réel doit à peu près être par référence ('car les objets réels sont là-bas).
Toute autre sémantique serait plus difficile à gérer.
Dog a = new Dog("rover"); // initialise with name
DogList dl = new DogList()
dl.add(a)
...
a.setOwner("Mr Been")
Je suggère que le "rover" dans dl
soit celui effectué par setOwner
ou que les programmes deviennent difficiles à écrire, à comprendre, à déboguer ou à modifier. Je pense que la plupart des programmeurs seraient perplexes ou consternés autrement.
plus tard, le chien est vendu:
soldDog = dl.lookupOwner("rover", "Mr Been")
soldDog.setOwner("Mr Mcgoo")
Ce type de traitement est courant et normal. La sémantique de référence est donc la valeur par défaut, car elle est généralement plus logique.
Résumé: Il est toujours judicieux d'avoir plusieurs références au même objet.
Bien sûr, un autre scénario où vous pourriez vous retrouver avec:
Dog a = new Dog();
Dog b = a;
c'est quand vous gérez le code et que b
était autrefois un chien différent, ou une classe différente, mais est maintenant desservi par a
.
En règle générale, à moyen terme, vous devez retravailler tout le code pour faire référence directement à a
, mais cela peut ne pas se produire immédiatement.
Vous voudriez cela à chaque fois que votre programme a une chance de tirer une entité en mémoire à plusieurs endroits, probablement parce que différents composants l'utilisent.
Identity Maps a fourni un magasin local définitif d'entités afin que nous puissions éviter d'avoir deux ou plusieurs représentations distinctes. Lorsque nous représentons le même objet deux fois, notre client court le risque de provoquer un problème de simultanéité si une référence de l'objet persiste ses changements d'état avant l'autre instance. L'idée est que nous voulons nous assurer que notre client traite toujours la référence définitive à notre entité/objet.
Je l'ai utilisé lors de l'écriture d'un solveur Sudoku. Lorsque je connais le numéro d'une cellule lors du traitement des lignes, je souhaite que la colonne contenant connaisse également le numéro de cette cellule lors du traitement des colonnes. Les colonnes et les lignes sont donc des tableaux d'objets Cell qui se chevauchent. Exactement comme l'a montré la réponse acceptée.