Java >> Tutoriel Java >  >> Java

Comment échanger des types de référence Java dans une méthode

L'échange de variables en Java est une affaire délicate, mais tout le monde a probablement vu le processus en trois étapes. Malheureusement, un problème survient lorsque nous essayons d'abstraire ce processus dans une méthode. Si vous vous êtes retrouvé coincé à essayer de créer une méthode d'échange en Java, ne cherchez pas plus loin !

Description du problème

Comme vous pouvez probablement le deviner, tous les articles de cette série sont inspirés des questions des élèves. Plus précisément, j'ai eu plusieurs étudiants qui avaient du mal avec cette idée d'échanger des variables avec des méthodes.

Échange de types primitifs

Dans un premier temps, nous introduisons le concept d'échange de variables en montrant aux élèves un bloc de code qui se présente comme suit :

int a = 10;
int b = 13;
int temp = a;
a = b;
b = temp;

Maintenant, nous savons que le code ci-dessus fonctionne car, logiquement, il a beaucoup de sens. J'ai deux variables, a et b , qui stockent chacun des entiers. J'introduis ensuite une variable temporaire qui contient la valeur de l'une des variables que je suis sur le point de supprimer, a . Ensuite, j'écrase a avec la valeur de b , et mettre à jour b avec l'ancienne valeur de a .

Échange de types de référence

Bien sûr, un échange comme celui-ci fonctionnera pour n'importe quel moment de données, pas seulement pour les types primitifs. Nous pouvons faire la même chose pour les types de référence :

String a = "Draco"
String b = "Harry"
String temp = a;
a = b;
b = temp;

Encore une fois, la logique a beaucoup de sens ici. Cependant, il y a quelques rides supplémentaires. Par exemple, dans l'exemple de type primitif, chaque fois que nous utilisions le signe égal, nous créions une copie de la valeur. En d'autres termes, toute modification de temp n'aurait aucun effet sur a . Ce n'est pas nécessairement vrai pour les types de référence. Par exemple, lorsque nous essayons de copier a à la ligne 3 de notre code d'échange de type de référence, nous créons en fait ce qu'on appelle un alias . Les deux a et temp pointent vers la même valeur, "Draco". Aucune copie de cette chaîne n'a été faite.

Il s'avère que même si "Draco" n'est pas copié, il n'y a aucun problème avec le code d'échange ci-dessus car nous ne faisons que rediriger les références. Les adresses mémoire qui stockent à la fois "Draco" et "Harry" sont les mêmes. Ce sont les variables qui pointent maintenant vers des adresses opposées.

Présentation des méthodes

Cela dit, les deux exemples d'échange de variables ci-dessus fonctionnent bien. Cependant, imaginons que vous prévoyez de faire beaucoup d'échanges de variables. Que se passe-t-il si vous décidez d'être intelligent et de créer une méthode d'échange pour vos variables ? Voici un exemple avec nos types primitifs :

public static void swap(int x, int y) {
  int temp = x;
  x = y;
  y = x;
}

int a = 10;
int b = 13;
swap(a, b);

Bien que le code d'échange fonctionne toujours, a et b ne sera jamais échangé. La raison est quelque peu subtile. Lorsque nous dépassons a et b comme arguments, x et y deviennent des copies de ces arguments. Par conséquent, toute modification de x et y n'aura aucun effet sur a et b .

D'accord, mais ce sont des types primitifs. Nous savons plus ou moins qu'il n'y a aucun moyen de les aliaser. Les types de référence, en revanche, sont aliasés chaque fois qu'ils sont passés dans une méthode. Jetons un coup d'œil :

public static void swap(String x, String y) {
  String temp = x;
  x = y;
  y = x;
}

String a = "Draco";
String b = "Harry";
swap(a, b);

Dans le code ci-dessus, nous créons des variables x et y qui pointent vers les mêmes objets que a et b , respectivement. Ensuite, nous créons un temp variable qui pointe également vers le même objet que a et x . Après cela, nous disons x pour arrêter de pointer "Draco" et plutôt pointer sur "Harry". Pendant ce temps, l'inverse se produit pour y . Quand tout est dit et fait, voici les valeurs de nos variables :

Variable Valeur
un "Draco"
b "Harry"
temp "Draco"
x "Harry"
y "Draco"

En d'autres termes, rien n'est arrivé à a et b tandis que x et y ont été effectivement échangés. Alors, qu'est-ce que ça donne ? Comment échangeons-nous réellement a et b ? C'est le sujet de cet article.

L'histoire de deux types de référence

La réponse à la question "comment permuter les types de référence dans une méthode ?" a en fait deux réponses. Dans certains scénarios, c'est impossible (ou du moins déconseillé). Dans d'autres scénarios, c'est simple. Commençons par le scénario impossible.

Échange de types de référence immuables

Lorsque nous parlons de types en Java, nous différencions souvent les types primitifs des types de référence, mais il existe en fait trois classifications :

  • Primitive (par exemple, int, double, etc.)
  • Types de référence immuables (par exemple, chaîne, etc.)
  • Types de référence mutables (par exemple, ArrayList, etc.)

Il y a plusieurs raisons pour lesquelles nous introduisons cette distinction entre les types de référence immuables et mutables, mais la raison pour laquelle nous l'introduisons dans cet article est qu'il s'agit d'une distinction importante lorsque vous essayez d'échanger des variables.

Mutabilité est défini comme la capacité de modifier ou de muter les données d'un objet. La possibilité de faire muter un objet provient des méthodes publiques de mutation (également parfois appelées procédures). Les procédures donnent à l'utilisateur un accès indirect aux données sous-jacentes en les modifiant d'une manière ou d'une autre. Un manque de procédures publiques, ou du moins d'accès public aux champs de données sous-jacents, est ce qui rend un type de référence immuable.

L'immuabilité est étonnamment rare en Java, mais elle existe dans l'un des types d'objets les plus courants de tout le langage, Strings. En d'autres termes, il n'y a pas de méthodes de String qui modifient la String sous-jacente. Même des méthodes comme toUpperCase() ne rien faire à la chaîne d'origine. Au lieu de cela, ces types de méthodes renvoient de nouvelles chaînes, en utilisant une copie de la chaîne d'origine.

Donc, s'il n'y a aucun moyen de modifier une chaîne, comment pouvons-nous les échanger dans une méthode. Eh bien, nous ne le faisons pas! Il n'y a aucun moyen d'écrire une méthode qui peut échanger des types de référence immuables comme Strings .

Bon, je mens un peu. Il existe probablement des moyens d'échanger des types de référence immuables, mais ils sont fortement déconseillés car ils sont probablement fragiles, bogués et éventuellement dangereux. Cela dit, si cet avertissement ne vous a pas arrêté, je vous recommande de consulter Reflection, une bibliothèque permettant d'inspecter les membres cachés d'une classe.

Échange de types de référence mutables

Avec les types de référence mutables, l'histoire est quelque peu différente. Puisque nous sommes capables de modifier l'objet sous-jacent, nous pouvons utiliser nos alias pour effectuer un échange de valeur. Voici à quoi cela pourrait ressembler en utilisant des tableaux :

public static void swap(int[] x, int[] y) {
  int[] temp = {x[0], x[1], x[2]};
  x[0] = y[0]
  x[1] = y[1]
  x[2] = y[2]
  y[0] = temp[0]
  y[1] = temp[1]
  y[2] = temp[2]
}

int[] a = {4, 1, 2};
int[] b = {2, 1, 6};
swap(a, b);

Comme vous pouvez l'imaginer, ce code sera différent pour chaque type d'objet. Par exemple, l'exemple de tableau ne fonctionne que si les deux tableaux ont la même longueur (et dans ce cas, une longueur de 3). Cela dit, étant donné que les types de données modifiables nous permettent de manipuler les objets sous-jacents, l'échange de variables revient à manipuler les deux objets jusqu'à ce que leurs valeurs soient correctement échangées.

Toujours confus ? Dessinons !

Une chose que je trouve difficile dans le développement de logiciels est que nous évitons parfois d'essayer de conceptualiser ces idées visuellement. Donc, ce que je veux faire, c'est réintroduire l'idée des types de référence à travers un exemple idiot.

Pour aider à illustrer l'idée du fonctionnement des types de référence, nous devons comprendre ce qu'est une référence. Pour simplifier, une référence est une adresse mémoire. Plutôt que de stocker la valeur directement dans une variable, la variable stocke l'adresse. Ensuite, chaque fois que nous voulons faire quelque chose avec notre objet, nous allons à l'adresse enregistrée.

Si nous considérons une référence comme une adresse personnelle littérale, cela commence à comprendre pourquoi les méthodes fonctionnent comme elles le font. Par exemple, nous pouvons considérer les méthodes comme des services qui sont effectués sur notre maison comme la peinture, l'aménagement paysager, etc. Tout ce que nous avons à faire est de donner une copie de notre adresse au service, et ils viendront l'exécuter.

Maintenant, en Java, nous pouvons échanger des adresses avec quelqu'un sans problème. Nous avons juste besoin des deux parties (c'est-à-dire deux variables) et d'un intermédiaire (c'est-à-dire une variable temporaire).

Là où les choses deviennent un problème, c'est si nous impliquons un service d'échange (c'est-à-dire une méthode). Pour effectuer l'échange, les deux parties donneraient une copie de leur adresse au service d'échange. A partir de là, le service serait d'échanger les copies en interne. Cependant, à la fin de la journée, les deux parties auraient toujours leurs adresses d'origine.

Ce que le service d'échange pourrait faire, cependant, c'est ordonner que des services soient exécutés sur les maisons, afin qu'elles se ressemblent. Par exemple, si une partie avait une maison bleue et l'autre une maison rouge, le service d'échange pourrait engager des peintres pour peindre les maisons de leurs couleurs respectives.

En fin de compte, cependant, le service d'échange ne peut jamais réellement échanger les adresses personnelles car les adresses données au service d'échange sont des copies. Il s'agit d'une limitation de Java, en ce sens que nous ne pouvons pas transmettre les références d'origine à notre méthode. D'autres langages comme C nous permettent de le faire, mais les choses se compliquent rapidement (par exemple, imaginez donner au service d'échange l'acte de propriété du terrain au lieu de simplement l'adresse).

Les types de référence sont désordonnés

Quand il s'agit d'écrire du code en Java, la vie n'est pas trop mauvaise jusqu'à ce que nous commencions à jouer avec les types de référence. Bien sûr, ils sont difficiles à éviter. Après tout, il n'y a que 8 types primitifs dans tout le langage. Toutes les autres données sont un type de référence, vous devez donc vous habituer à les utiliser.

Cela dit, il y a très longtemps, j'ai écrit une courte série pour mettre les gens au courant de Java. Si vous êtes intéressé, je vous recommande de le vérifier. Il adopte une approche conceptuelle de Java qui commence par le binaire et l'arithmétique avant de passer directement à la programmation orientée objet.

Sinon, j'apprécierais que vous jetiez un coup d'œil à certains de ces messages connexes :

  • Méfiez-vous de la division par zéro en Java
  • Comment aborder Python d'un point de vue Java
  • Le else if Le mot-clé n'existe pas en Java

Cela dit, merci d'être resté. A la prochaine !


Balise Java