Java est un langage “passage par valeur” (pass-by-value). Cela signifie qu’une copie de la variable est créée et que la méthode reçoit cette copie. Les affectations effectuées dans la méthode n’affectent pas l’appelant. Voyons un exemple :
public static void main(String[] args) {
int nombre = 4;
nouveauNombre(nombre);
System.out.print(nombre); // 4
}
public static void nouveauNombre(int nombre) {
nombre = 8;
}
À la ligne 3, nombre est affecté à la valeur 4. Ensuite a la ligne 4, nous appelons une méthode. À la ligne 8, le paramètre nombre dans la méthode est défini sur 8. Bien que ce paramètre porte le même nom que la variable à la ligne 3, c’est une coïncidence. Le nom pourrait être n’importe quoi. La variable à la ligne 3 ne change jamais car aucune affectation ne lui est faite.
Passage d’Objets
Maintenant que vous avez vu les primitives, essayons un exemple avec un type référence. Qu’est-ce qui est affiché par le code suivant selon vous ?
public class Chien {
public static void main(String[] args) {
String nom = "Filou";
parler(nom);
System.out.print(nom);
}
public static void parler(String nom) {
nom = "Marcel";
}
}
La bonne réponse est Filou. Tout comme dans l’exemple primitif, l’affectation de variable ne concerne que le paramètre de méthode et n’affecte pas l’appelant.
Remarquez comment nous continuons à parler d’affectations de variables. C’est parce que nous pouvons appeler des méthodes sur les paramètres. Voici un exemple de code qui appelle une méthode sur le StringBuilder passé à la méthode :
public class Chien {
public static void main(String[] args) {
var nom = new StringBuilder("Filou");
parler(nom);
System.out.print(nom); // FilouMarcel
}
public static void parler(StringBuilder s) {
s.append("Marcel");
}
}
Dans ce cas, parler() appelle une méthode sur le paramètre. Il ne réassigne pas s à un objet différent. Vous pouvez voir comment le passage par valeur est toujours utilisé. La variable s est une copie de la variable nom. Les deux pointent vers le même StringBuilder, ce qui signifie que les modifications apportées au StringBuilder sont disponibles pour les deux références.
Passage par Valeur vs. Passage par Référence
Différents langages gèrent les paramètres de différentes manières. Le passage par valeur est utilisé par de nombreux langages, y compris Java. Dans cet exemple, la méthode echanger() ne change pas les valeurs d’origine. Elle ne change que a et b dans la méthode.
public static void main(String[] args) {
int original1 = 1;
int original2 = 2;
echanger(original1, original2);
System.out.println(original1); // 1
System.out.println(original2); // 2
}
public static void echanger(int a, int b) {
int temp = a;
a = b;
b = temp;
}
L’autre approche est le passage par référence. Elle est utilisée par défaut dans quelques langages, tels que Perl. Dans un langage de passage par référence, les variables seraient échangées et la sortie serait inversée.
Pour réviser, Java utilise le passage par valeur pour obtenir des données dans une méthode. L’affectation d’une nouvelle primitive ou référence à un paramètre ne change pas l’appelant. L’appel de méthodes sur une référence à un objet peut affecter l’appelant.
Renvoi d’Objets
Récupérer des données d’une méthode est plus facile. Une copie de la primitive ou de la référence est faite et renvoyée par la méthode. La plupart du temps, cette valeur renvoyée est utilisée. Par exemple, elle peut être stockée dans une variable. Si la valeur renvoyée n’est pas utilisée, le résultat est ignoré. Attention à cela. Les valeurs renvoyées ignorées sont délicates.
Essayons un exemple. Faites attention aux types de retour.
public class BilletsZoo {
public static void main(String[] args) {
int billets = 2; // billets = 2
String invites = "abc"; // invites = abc
ajouterBillets(billets); // billets = 2
invites = ajouterInvites(invites); // invites = abcd
System.out.println(billets + invites); // 2abcd
}
public static int ajouterBillets(int billets) {
billets++;
return billets;
}
public static String ajouterInvites(String invites) {
invites += "d";
return invites;
}
}
C’est délicat car il y a beaucoup de choses à suivre. Quand vous voyez de telles questions, notez les valeurs de chaque variable. Les lignes 3 et 4 sont des affectations simples. La ligne 5 appelle une méthode. La ligne 10 incrémente le paramètre de méthode à 3 mais laisse la variable billets dans la méthode main() à 2. Bien que la ligne 11 renvoie la valeur, l’appelant l’ignore. L’appel de méthode sur la ligne 6 n’ignore pas le résultat, donc invites devient “abcd”. Souvenez-vous que cela se produit en raison de la valeur renvoyée et non du paramètre de méthode.
Autoboxing et Unboxing de Variables
Java prend en charge certaines fonctionnalités utiles concernant le passage de types de données primitives et wrapper, tels que int et Integer. Souvenez-vous que nous pouvons convertir explicitement entre primitives et classes wrapper en utilisant des méthodes intégrées.
int coin = 5;
Integer coincoin = Integer.valueOf(coin); // Convertir int en Integer
int coincoincoin = coincoin.intValue(); // Convertir Integer en int
Utile, mais un peu verbeux. Heureusement, Java a des gestionnaires intégrés dans le langage Java qui convertissent automatiquement entre primitives et classes wrapper et vice versa.
L’autoboxing est le processus de conversion d’une primitive en sa classe wrapper équivalente, tandis que l’unboxing est le processus de conversion d’une classe wrapper en sa primitive équivalente.
int coin = 5;
Integer coincoin = coin; // Autoboxing
int coincoincoin = coincoin; // Unboxing
Le nouveau code est équivalent au code précédent, car le compilateur “fait le travail” de conversion automatique des types pour vous. L’autoboxing s’applique à toutes les primitives et à leurs types wrapper associés, comme suit :
Short queue = 8; // Autoboxing
Character p = Character.valueOf('p');
char patte = p; // Unboxing
Boolean museau = true; // Autoboxing
Integer n = Integer.valueOf(9);
long oreilles = n; // Unboxing, puis casting implicite
Chacun de ces exemples se compile sans problème. Dans la dernière ligne, n est déboxé en une valeur int. Comme une valeur int peut être stockée dans une variable long via un casting implicite, le compilateur autorise l’affectation.
Limites de l’Autoboxing et de la Promotion Numérique
Alors que Java va implicitement caster une primitive plus petite à un type plus grand, ainsi qu’autoboxer, il ne fera pas les deux en même temps. Voyez-vous pourquoi ce qui suit ne se compile pas ?
Long mauvaisGorille = 8; // NE SE COMPILE PAS
Java va automatiquement caster ou autoboxer la valeur int en long ou Integer, respectivement. Aucun de ces types ne peut être affecté à une variable de référence Long, donc le code ne se compile pas. Comparez ce comportement à l’exemple précédent avec oreilles, où la valeur primitive déboxée pouvait être implicitement castée vers un type primitif plus grand.
Que pensez-vous qu’il se passe si vous essayez de déboxer un null ?
Character elephant = null;
char mauvaisElephant = elephant; // NullPointerException
À la ligne 10, nous stockons null dans une référence Character. C’est légal car une référence null peut être affectée à n’importe quelle variable de référence. À la ligne 11, nous essayons de déboxer ce null en primitive char. C’est un problème. Java essaie d’obtenir la valeur char de null. Puisque l’appel de toute méthode sur null donne une NullPointerException, c’est exactement ce que nous obtenons. Soyez prudent lorsque vous voyez null en relation avec l’autoboxing et l’unboxing.
Là où l’autoboxing et l’unboxing brillent vraiment, c’est lorsque nous les appliquons aux appels de méthodes.
public class Chimpanze {
public void grimper(long t) {}
public void balancer(Integer u) {}
public void sauter(int v) {}
public static void main(String[] args) {
var c = new Chimpanze();
c.grimper(123);
c.balancer(123);
c.sauter(123L); // NE SE COMPILE PAS
}
}
Dans cet exemple, l’appel à grimper() se compile car la valeur int peut être implicitement castée en long. L’appel à balancer() est également autorisé, car la valeur int est autoboxée en Integer. En revanche, l’appel à sauter() entraîne une erreur de compilateur car un long doit être explicitement casté en int. En d’autres termes, Java ne convertira pas automatiquement en un type plus étroit.
Comme précédemment, la même limitation concernant l’autoboxing et la promotion numérique s’applique aux appels de méthodes. Par exemple, ce qui suit ne se compile pas :
public class Gorille {
public void reposer(Long x) {
System.out.print("long");
}
public static void main(String[] args) {
var g = new Gorille();
g.reposer(8); // NE SE COMPILE PAS
}
}
Java va caster ou autoboxer la valeur automatiquement, mais pas les deux en même temps.