Méthode et variable locale
On vient de voir comment définir les classes, les variables d'instance et les constructeurs. Cette section concerne la définition de méthodes permettant de représenter les fonctionnalités des objets.
Ajoutons à la classe Die
une méthode qui permet d'afficher la valeur de la face visible du dé sur la sortie standard. Cela peut s'avérer utile pour connaitre la face visible du dé puisqu'on ne peut pas obtenir cette valeur directement, la variable d'instance visibleFace
étant privée. Voici la classe Die
avec la définition de la méthode :
Comme l'illustre la figure 9, pour déclarer une nouvelle méthode, on utilise le modificateur de visibilité public
suivi du mot réservé void
et du nom de la méthode, qui doit être un identificateur Java. On termine par deux accolades délimitant le corps de la méthode.
La méthode printFace
qu'on vient de définir est une méthode void
. Voyons un exemple qui crée un objet de type Die
et puis appelle la méthode printFace
pour afficher la valeur de la face visible sur la sortie standard.
Rappelez-vous que la valeur initiale de la variable visibleFace
est choisie au hasard, et donc si vous exécutez plusieurs fois ce programme d'affilé, vous verrez un résultat différent sur la sortie standard. On pourrait par exemple avoir :
La face visible du dé est 5.
Flux de contrôle
Que se passe-t'il lorsqu'on invoque une méthode ? On a vu au chapitre 3 que l'exécution d'un programme n'était pas toujours linéaire, certaines instructions modifiant l'exécution linéaire du programme. On en a déjà vu certaines : if-else
, for
, while
, do
, break
et continue
.
Un appel de méthode modifie également le flux de contrôle. La figure 10 montre les différentes étapes qui se produisent lors d'une invocation de méthode. L'exemple utilisé est celui vu juste ci-dessus, où on appelle la méthode printFace
sur un objet de type Die
.
On peut décomposer l'appel de la méthode en cinq étapes dont deux vont provoquer une rupture de l'exécution linéaire du code :
- les instructions sont exécutées linéairement les unes après les autres ;
- on arrive à un appel de méthode, le contrôle est transféré vers le début du corps de la méthode
printFace
; - les instructions de la méthode s'exécutent linéairement les unes après les autres sur l'objet cible, c'est-à-dire celui référencé par la variable
myDie
; - on arrive à la fin de la méthode, le contrôle revient à l'instruction qui suit celle de l'appel de la méthode ;
- la suite du programme continue à s'exécuter de manière linéaire.
Remarquez que dans le corps de la méthode printFace
, on fait également un appel de méthode. En effet, on appelle la méthode println
sur l'objet System.out
. Le contrôle est donc transféré vers le corps de cette méthode qui fait partie de la librairie standard Java. Pour être complet, il aurait également fallu indiquer cette modification sur la figure.
La méthode qu'on vient de voir est une méthode void
. Exécuter une telle méthode consiste donc juste à transférer le contrôle au début de la méthode appelée et à exécuter les instructions de son corps; le contrôle revient ensuite à l'instruction qui suit celle d'appel.
Méthode qui renvoie une valeur
Voyons maintenant comment définir une méthode qui renvoie une valeur. Complétons la classe Die
en lui ajoutant une méthode pour récupérer la valeur de la face visible du dé. Contrairement à la méthode printFace
qui affiche cette valeur sur la sortie standard, la méthode getFace
, qu'on va définir tout de suite, va renvoyer cette valeur, de sorte qu'on puisse l'utiliser dans le code qui appelle la méthode.
Voici la définition de cette méthode :
La définition de la méthode getFace
est semblable à celle de la méthode printFace
à une différence près : le mot réservé void
a été substitué par le type int
. Puisque la méthode qu'on vient de définir est une méthode qui renvoie une valeur, on doit indiquer le type de cette valeur renvoyée. Voici un exemple d'appel de cette méthode :
L'appel de la méthode getFace
est une expression et on peut donc affecter sa valeur à la variable f
. Cette valeur est de type int
, ce qui correspond bien au type déclaré par la méthode. On a dit que la méthode getFace
renvoie une valeur de type int
et donc, un appel à cette méthode est une expression dont la valeur est de type int
. Il y a donc trois étapes qui se produisent :
- on déclare tout d'abord une variable dont le nom est
f
et de type primitifint
; - on a ensuite un appel de la méthode
getFace
sur l'objet référencé par la variablemyDie
, c'est-à-dire appel de la méthodegetFace
définie dans la classeDie
; - enfin, la valeur de retour de la méthode
getFace
est affectée à la variablef
.
Comment la valeur de retour d'une méthode est-elle définie ? C'est dans le corps de la méthode qu'il faut le faire en utilisant l'instruction return
. On va revenir en détails sur cette instruction à la section suivante, mais voyons avant tout une illustration de l'évolution du flux de contrôle pour cet appel sur la figure 11.
L'appel peut être décomposé en cinq étapes :
- les instructions sont exécutées linéairement les unes après les autres ;
- on arrive à un appel de méthode, le contrôle est transféré vers le début du corps de la méthode
getFace
; - les instructions de la méthode s'exécutent linéairement les unes après les autres sur l'objet cible, c'est-à-dire celui référencé par la variable
myDie
; - on tombe sur une instruction
return
, le contrôle revient à l'instruction qui suit celle d'appel de la méthode. La valeur de l'appel est celle de l'expression qui suit l'instructionreturn
; - la suite du programme continue à s'exécuter de manière linéaire.
La seule différence dans l'exécution de la méthode appelée se situe donc au niveau de l'instruction return
qui va avoir deux effets. Elle permet d'arrêter l'exécution de la méthode et de définir sa valeur de retour.
Instruction return
La syntaxe de l'instruction return
est simple : on utilise le mot réservé return
suivi d'une expression. La valeur de cette expression définit la valeur de retour de la méthode.
Lorsqu'on définit une méthode avec valeur de retour, il faut que tous les chemins d'exécution possibles de la méthode se terminent par une instruction return
, sans quoi le compilateur génèrera une erreur de type « Missing return statement ». Voici un exemple de méthode qui permet de tester si la face visible du dé est paire ou non :
Il y a deux chemins d'exécution possibles pour cette méthode. Soit la condition de l'instruction if
est vraie et on arrive à l'instruction return
, puis la méthode est quittée. Soit la condition est fausse et on atteint l'accolade fermante du bloc if
. La figure 12 montre ces deux chemins d'exécution.
Le chemin d'exécution (2) (à droite) atteint une instruction return
et ne pose donc pas de problème. Par contre, le premier chemin d'exécution (à gauche) atteint la fin de la méthode sans jamais croiser une instruction return
. Le compilateur va donc générer une erreur de compilation :
Die.java:29: missing return statement } ^ 1 error
Pour que la méthode compile, il suffit d'ajouter un else
avec un return false;
. On verra une solution plus élégante à la section 5.8.
L'instruction return
est une instruction de branchement qui a deux effets. Elle casse le déroulement normal linéaire du programme et elle permet de quitter une méthode appelée pour revenir à l'instruction qui suit l'appel de la méthode.
Instruction return
dans les méthodes void
On peut également utiliser l'instruction return
dans le corps d'une méthode void
. Dans ce cas, il ne faut pas spécifier d'expression et l'effet de l'instruction est juste de quitter la méthode appelée et de revenir après l'appel. Voici un exemple utilisant cette instruction :
Appeler cette méthode affichera donc Hello World !
sur la sortie standard. Dans cet exemple, on a placé l'instruction return
en fin de méthode. En fait, pour les méthodes de type void
, le compilateur ajoute une instruction return
en fin de méthode; celle-ci est donc toujours implicitement présente. L'exemple suivant provoquera une erreur de compilation de type « Unreachable Code » :
D'où vient cette erreur ? L'instruction return
quitte la méthode et déplace le contrôle après l'appel de la méthode, donc tout code qui suit une instruction return
et se trouvant dans le même bloc que celle-ci ne pourra et ne sera jamais exécuté, d'où l'erreur de compilation.
On peut également utiliser l'instruction return
dans la méthode main
. L'effet dans ce cas est de quitter le programme (ceci n'est pas vrai en général, notamment dans le cas où la méthode main
est lancée depuis une autre méthode ou dans le cas de programmes multi-threadés. On verra une illustration du premier cas au chapitre 6.).
Méthode paramétrée
Toutes les méthodes qu'on a vues jusqu'à présent sont sans paramètre. Voyons un exemple d'une méthode qui permet de savoir si la valeur de la face visible du dé est supérieure à trois ou non :
Si on souhaite une méthode pour tester si la face visible du dé est supérieure à 1, ou 2..., une solution pas très élégante consiste à définir autant de méthode que nécessaire : isGreaterThan1
, isGreaterThan2
... Une autre solution plus élégante et appropriée consiste à définir une méthode paramétrée. Lors d'un appel d'une telle méthode, on doit lui fournir des valeurs appelées paramètres. Voici la nouvelle version de la méthode isGreaterThan
avec un paramètre value
:
Vous voyez une déclaration de variable entre les parenthèses qui suivent le nom de la méthode. La variable int value
est un paramètre de la méthode, cette variable recevant une valeur à chaque fois que la méthode est appelée. Une méthode peut avoir autant de paramètres qu'elle le souhaite, il suffit de toutes les déclarer en les séparant avec des virgules.
Paramètre formel
Les paramètres qu'on déclare dans une méthode sont appelés paramètres formels. Ces paramètres sont des variables locales à la méthode. Leur portée s'étend à tout le corps de la méthode. La méthode isGreaterThan
possède donc un paramètre formel qui est int value
.
Paramètre réel
Lorsqu'on appelle une méthode qui prend des paramètres, il faut spécifier des valeurs pour ces paramètres lors de l'appel. Dans le programme suivant, on crée un nouveau dé et on vérifie si la valeur de sa face visible est plus grande que 4 :
Pour pouvoir appeler la méthode isGreaterThan
, on a fourni un entier de type int
(le littéral 4). Les paramètres fournis lors de l'appel sont appelés paramètres réels (ou paramètres effectifs) et doivent être des expressions.
Appel d'une méthode paramétrée
Voyons maintenant comment se déroule un appel de méthode paramétrée. Les valeurs des paramètres réels vont être affectées aux paramètres formels correspondants. Par conséquent, l'ordre et les types des paramètres doivent être respectés lors de l'appel, sans quoi le compilateur Java renverra une erreur de compilation de type « The method XXX in the type YYY is not applicable for the arguments (ZZZ) ».
On verra comment fonctionne le passage des paramètres en détails au chapitre 6, mais voyons quand même tout de suite les différentes étapes qui constituent un appel de méthode paramétrée. La figure 13 illustre l'évolution du contrôle pour l'exemple de programme que l'on vient de voir, avec l'appel de la méthode isGreaterThan
.
L'appel de méthode se déroule en cinq étapes :
- les instructions sont exécutées linéairement les unes après les autres ;
- on arrive à un appel de méthode, le contrôle est transféré vers le début du corps de la méthode
isGreaterThan
et la valeur du paramètre réel4
est affectée au paramètre formelint value
; - les instructions de la méthode s'exécutent linéairement les unes après les autres sur l'objet cible, c'est-à-dire celui référencé par la variable
myDie
; - on arrive à une instruction
return
, le contrôle revient à l'instruction qui suit celle d'appel de la méthode. La valeur de l'appel est celle de l'expression qui suit l'instructionreturn
(false
dans notre exemple) ; - la suite du programme continue à s'exécuter de manière linéaire.
Variable locale et portée
Revenons un moment sur les deux sortes de variables qu'on a vues jusqu'à présent. Il y a tout d'abord les variables d'instance qui sont liées à un objet et existent tant que l'objet existe. Ces variables sont utilisables dans tout le corps de la classe dans laquelle elles sont déclarées, leur portée s'étend sur tout le corps de la classe. On peut également accéder à ces variables avec l'opérateur d'appel si on possède une référence vers une instance de la classe. L'accès est contrôlé par la visibilité de la variable que l'on peut actuellement définir à public
et private
.
Une variable déclarée dans le corps d'une méthode et les paramètres formels sont des variables locales. Elles n'existent que dans le corps de la méthode dans laquelle elles ont été déclarées, à partir de leur déclaration jusque la fin du corps de la méthode. La portée de ces variables se limite au bloc de code dans lequel elles ont été déclarées.
Syntaxe d'une déclaration de méthode
La définition d'une méthode comporte deux parties : la signature de la méthode et son corps. La signature de la méthode est une ligne composée de différents éléments dont certains sont optionnels :
- un modificateur de visibilité (optionnel) ;
- le type de la valeur de retour ou
void
si la méthode ne renvoie rien ; - le nom de la méthode (qui est un identificateur) ;
- une liste (éventuellement vide) de paramètres formels entre parenthèses et séparés par des virgules.
Comment le montre la figure 14, la signature de la méthode contient toute l'information nécessaire pour savoir comment appeler la méthode. C'est d'ailleurs cette information qui se retrouve dans la documentation de la librairie standard Java. Vient ensuite le corps de la méthode qui contient une suite d'instructions Java, délimitée par des accolades ({ }
). La signature et le corps forment la définition de la méthode.
En ce qui concerne le modificateur de visibilité, les méthodes qu'on a vues jusqu'à présent étaient toutes déclarées public
. On verra à la section 5.7 qu'on peut également déclarer une méthode private
. Celles-ci ne pourront être utilisées que depuis le corps de la classe. Vient ensuite le type de retour de la méthode. Il s'agit soit du mot réservé void
pour une méthode qui ne renvoie rien, soit d'un type de donnée (primitif ou objet) qui indique le type de retour de la méthode pour celles qui renvoient une valeur. La liste des paramètres est constituée de déclarations de variables séparées par des virgules, mise entre parenthèses.
Retour sur le constructeur
Revenons un moment sur le constructeur d'une classe qui peuvent également être paramétrés. On va ainsi pouvoir par exemple préciser le nombre de faces qu'on veut pour un dé que l'on crée. En effet, tous les dés existants n'ont pas nécessairement six faces. Il existe par exemple des dés à vingt faces ! La figure 15 montre un dé à quatre faces.
L'exemple suivant montre un autre constructeur pour la classe Die
. Celui-ci prend un paramètre de type int
qui représente le nombre de faces désiré.
Le constructeur de la classe prend donc un paramètre qui est un entier de type int
, représentant le nombre de faces qu'on souhaite pour le dé. Les instructions du constructeur ont donc dûs être changées. Le paramètre formel int face
qui contient le nombre de faces désirées est une variable locale et n'existe que dans le corps du constructeur. Si on veut pouvoir accéder à cette valeur dans les autres méthodes de la classe et pendant toute la durée de vie de l'objet, il faut la stocker dans une variable d'instance. On affecte donc la valeur du paramètre formel faces
à la variable d'instance nbFaces
.
Maintenant, lorsqu'on va vouloir créer une instance de la classe Die
, il va donc falloir fournir un paramètre réel comme le montre l'exemple suivant :
On crée donc deux objets Die
. Pour le premier dé, on passe le paramètre réel 4
au constructeur et pour le second, 12
. La figure 16 montre les deux objets créés.
Nombre de paramètres variable
Toutes les méthodes qu'on a vues jusque présent ont un nombre fixe de paramètres formels. On a vu au chapitre précédent qu'il existait également des méthodes qui ont un nombre de paramètres variable. La méthode printf
de l'objet PrintStream
est un exemple d'une telle méthode. On ne va pas entrer dans les détails maintenant, mais on y reviendra au chapitre 8. On va pour l'instant se contenter d'un exemple d'une telle méthode :
En faisant suivre le type de donnée d'un paramètre formel de trois points (...
), on déclare qu'on pourra avoir un nombre variable de paramètres réels pour ce dernier. Dans le corps de la méthode, on peut connaitre le nombre de paramètres réels reçus en accédant à la variable length
du paramètre formel. Enfin, pour accéder au $i$e paramètre, on utilise des crochets derrière le nom de variable, entre lesquels on place la position de l'élément auquel on souhaite accéder, sachant que le premier élément est en position 0, le second en position 1... La méthode d'exemple permet de calculer la somme de tous les entiers passés en paramètres. Voici un exemple d'utilisation de cette méthode qui suppose que la variable var
contient une référence vers la classe dans laquelle cette méthode est définie :
L'exécution de ce programme affiche sur la sortie standard :
0 6 21
Une méthode ne peut avoir qu'un seul paramètre de taille variable. De plus, il ne peut se trouver qu'en dernière position. On les reverra en détails au chapitre 8.