Accès rapide :
La vidéo
Qu'est qu'une méthode statique ?
La signature d'une méthode statique
Finalisation de paramètres et de variables locales
Finalisation de types primitifs
Finalisation de types objets
La surcharge de méthodes statiques
Travaux pratiques
Le sujet
La correction
Cette vidéo vous montre comment coder des méthodes statiques (méthodes de classes). Une simplification de fraction et un calcul de PGCD vous sont notamment proposés. La finalisation (mot clé final) de paramètres vous est aussi expliquée.
Dans de nombreux langages de programmation (tels que C, Python, Perl, ...) on peut définir ce qu'on appelle des fonctions. Une fonction permet de factoriser un certain nombre d'instructions en un point unique de votre programme et d'associer ce groupe d'instructions à un nom (le nom de la fonction). Par la suite, on peut, à de multiples points de votre programme, invoquer cette fonction par son nom et donc exécuter l'ensemble des instructions de cette fonction.
Le problème, c'est qu'en Java, on n'a pas ce concept de fonction. Du coup, comment factoriser du code en Java ? Et bien le concept Java le plus proche de la fonction, c'est justement celui de « méthode statique ». Pour faire simple, une méthode statique est en gros une fonction, mais qui est rattachée à un type de données (à une classe).
En fait, çà fait déjà un bon bout de temps qu'on utilise ce concept de méthode statique : effectivement, la méthode main
, présente
dans quasiment tous les fichiers de code que nous avons pour l'heure produit, est une méthode statique. D'ailleurs, validez bien la présence du mot
clé static
. Pour rappel, revoici un programme Java de type « Hello World » qui contient donc une méthode main
rattachée à la classe Demo
.
1 2 3 4 5 6 7 8 9 |
public class Demo { public static void main( String [] args ) { System.out.println( "Hello World" ); } } |
main
est un peu particulière dans le sens ou ce n'est pas vous qui déciderez de l'invoquer.
Au contraire, elle est invoquée automatiquement lors du démarrage de la JVM. Pour les autres méthodes, c'est vous qui déciderez de les invoquer
quand cela sera nécessaire.
Pour définir une méthode statique, il faut préciser un certain nombre de points, dans l'ordre indiqué ci-dessous.
Le qualificateur de visibilité : quatre possibilités existent en Java. Trois de ces possibilités sont associées aux mots clés suivants :
public
, private
et protected
. La quatrième possibilités consiste à ne rien spécifier pour la visibilité et dans
ce cas on parle de visibilité « package » : beaucoup de personnes pensent que ne rien mettre est équivalent à la visibilité public
mais c'est une erreur et il y a bien une différence. Nous ne sommes pas encore près à parler de ces possibilités et nous y reviendrons
dans un futur chapitre. Pour l'heure nous ne considérerons que la visibilité associée au mot clé public
.
Le mot clé static
qui indique que nous souhaitons définir une méthode statique. A noter, la visibilité et le mot clé static
peuvent être spécifiés dans n'importe quel ordre.
Ensuite, on trouve le type de retour de la méthode : la nature de ce qu'elle doit calculer. Si votre méthode statique exécute du code, mais ne
produit pas de résultat, alors on utilise le mot clé void
(littéralement, « vide » en Français).
C'est le cas du main
.
L'élément syntaxique suivant est le nom de la méthode : dans l'exemple précédent, notre méthode était nommée main
.
En Java, un nom de méthode doit être constitué de caractères parmi l'ensemble suivant : des lettres (minuscules ou majuscules), de chiffre, ou des
caractères _
et $
. Vous ne pouvez pas commencer le nom de votre méthode par un chiffre. De plus, conventionnement,
une méthode commence par une minuscule et on recapitalise à chaque nouveau mot (par exemple, parseInt
). Bien qu'autorisés, les
caractères _
et $
sont utilisés dans des cas bien précis sur lesquels nous reviendrons plus tard.
Ensuite, on doit lister les paramètres utilisés par votre méthode. Ces paramètres sont placés entre une paire de parenthèses et sont séparés par des
virgules. Un paramètre contient deux informations : son type et son nom. Dans le cas de notre méthode main
, on y trouve un unique
paramètre nommé args
(pour ARGumentS). Ce paramètre est de type String []
(tableau de chaînes de caractères).
Après la parenthèse fermante, une méthode peut spécifier la liste des exceptions (des erreurs) qu'elle peut déclencher en cas de problèmes.
Dans l'exemple précédent, notre main
ne spécifie pas d'erreur potentielle.
Enfin, une méthode doit contenir un ensemble d'instructions à exécuter. Ces instructions doivent être placées entre accolades.
Ce bloc d'instructions est aussi appelé le corps de la méthode. Dans l'exemple précédent, le corps de la méthode ne contenait qu'une seule
instruction d'affichage sur la console (System.out.println( "Hello World" );
).
Tous les éléments définis avant le corps de la méthode constituent la signature de la méthode. La signature d'une méthode (aussi appelé prototype de la méthode) est en quelque sorte la carte d'identité de cette méthode. Pour un nom de méthode, la signature utilisée doit être unique dans la classe. Si vous définissez deux méthodes de même nom et de même signature, une erreur de compilation sera inexorablement produite.
A titre d'exemple, nous allons coder deux méthodes statiques de calcul de minimum et de maximum parmi deux valeurs de type double
.
La première méthode, nommée mini
calculera, bien entendu, le minimum parmi deux valeur. La seconde sera nommée maxi
et calculera
un maximum. Dans les deux cas, on accepte deux paramètres. Ils seront nommés a
et b
et seront de type double
.
La valeur de retour calculée sera donc aussi de type double
. Voici le code de cet exemple.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
public class Mathematiques { public static int mini( int a, int b ) { return a < b ? a : b; } public static int maxi( int a, int b ) { return a > b ? a : b; } public static void main( String [] args ) { System.out.println( "mini( 3, 5 ) == " + mini( 3, 5 ) ); System.out.println( "mini( 11, 5 ) == " + Mathematiques.mini( 11, 5 ) ); System.out.println( "maxi( 3, 5 ) == " + maxi( 3, 5 ) ); } } |
Mathematiques.mini( 11, 5 ) )
pour
indiquer que vous souhaitez déclencher la méthode mini
de la classe Mathematiques
. Soit vous pouvez omettre le nom de la classe,
mais à la condition sine qua non que vous soyez déjà dans la classe Mathematiques
. Notez que nous avions déjà utilisé d'autres méthodes
statiques dans les précédents chapitres et par exemple Math.random()
ou Integer.parseInt( "10" )
. Notez aussi qu'un nom de
classe débute, normalement, par une majuscule, contrairement au nom de la méthode, qui lui doit commencer par une minuscule.
Voici les résultats produits par l'exemple ci-dessus.
$> java Mathematiques mini( 3, 5 ) == 3 mini( 11, 5 ) == 5 maxi( 3, 5 ) == 5 $>
Il est possible de plus ou moins contrôler les changements d'états de vos paramètres à l'intérieur d'une méthode. Par défaut un paramètre est modifiable, comme en atteste l'exemple ci-dessous.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public class Demo { public static void test( int value ) { while( value > 0 ) { System.out.println( value-- ); } } public static void main( String [] args ) { Demo.test(5); // Affiche 5 / 4 / 3 / 2 / 1 } } |
Si vous ajouter le mot clé final
devant un paramètre, alors il devient non modifiable.
Cela permet de sécuriser votre code, si vous ne voulez pas voir varier la valeur de ce paramètre.
Mais attention à ce que l'on considère comme étant non modifiable car tout dépend de leurs types.
Selon que l'on considère un type primitif ou un objet, les règles ne sont pas les mêmes.
Il en va de même pour vos variables locales et l'utilisation du mot clé final
. Pour rappel, une variable locale (définie dans une méthode)
a une durée de vie qui est liée à l'appel en cours : en sortie de la méthode, la variable est perdue.
Si vous finalisez un paramètre (ou une variable) basée sur un type primitif (int
, double
, ...), alors le paramètre ne
peut plus être modifié. Cela permet de sécuriser votre code contre des futures modifications si vous partez du principe que le paramètre ne doit
pas changer d'état : du coup, c'est garanti par compilation.
Voici un exemple ou le paramètre est marqué comme étant final
mais où l'on tente de modifier la valeur.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public class Demo { public static void test( final int value ) { while( value > 0 ) { System.out.println( value-- ); // Ne compile pas !!! } } public static void main( String [] args ) { Demo.test(5); } } |
Si l'on cherche à compiler ce code, l'erreur de compilation suivante vous sera renvoyée.
$> javac Demo.java Demo.java:5: error: final parameter value may not be assigned System.out.println( value-- ); ^ 1 error $>
Voici un exemple plus sérieux ou l'on garantit qu'on ne peut plus modifier ni a
, ni b
dans les deux méthodes
de calcul proposées.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
public class Mathematiques { public static int mini( final int a, final int b ) { return a < b ? a : b; } public static int maxi( final int a, final int b ) { return a > b ? a : b; } public static void main( String [] args ) { System.out.println( "mini( 3, 5 ) == " + mini( 3, 5 ) ); System.out.println( "mini( 11, 5 ) == " + Mathematiques.mini( 11, 5 ) ); System.out.println( "maxi( 3, 5 ) == " + maxi( 3, 5 ) ); } } |
Dans le cas des types objets, le fonctionnement du mot clé final
est un petit peu plus subtil. Effectivement, ce qu'on finalise, ce
n'est pas le contenu de l'objet, mais la référence (le pointeur) sur cet objet. Afin de mieux comprendre la chose, regardez l'exemple suivant :
la méthode test
accepte une date en paramètre. En ligne 7, on modifie l'état de l'objet : aucune erreur de compilation n'est produite
et, à l'exécution, la date est bien changée au 1 janvier 1970.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
import java.util.Date; public class Demo { public static void test( final Date date ) { System.out.println( "In test method: " + date ); date.setDate( 0 ); // L'objet à changé d'état System.out.println( "In test method: " + date ); } public static void main( String [] args ) { Date now = new Date(); System.out.println( "In main method: " + now ); Demo.test( now ); System.out.println( "In main method: " + now ); } } |
Date.setTime()
permet de changer ce timestamp. Du coup, la ligne 7 remet la date au 1er
janvier 1970.
Voici le résultat produit par le programme ci-dessus.
$> java Demo In main method: Mon Apr 30 18:48:22 CEST 2018 In test method: Mon Apr 30 18:48:22 CEST 2018 In test method: Thu Jan 01 00:00:00 CET 1970 In main method: Thu Jan 01 00:00:00 CET 1970 $>
Donc, pour comprendre le fonctionnement du final
, il faut chercher à changer la valeur de la référence (le pointeur, si vous préférez).
C'est ce que cherche à faire le bout de code suivant. Une fois édité, cherchez à compiler ce programme et vous constaterez la limitation produite par
le mot clé final
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
import java.util.Date; public class Demo { public static void test( final Date date ) { System.out.println( "In test method: " + date ); date = new Date( 0 ); // On cherche à référencer un nouvel objet // Le résultat serait le même avec : date = null; System.out.println( "In test method: " + date ); } public static void main( String [] args ) { Date now = new Date(); System.out.println( "In main method: " + now ); Demo.test( now ); System.out.println( "In main method: " + now ); } } |
Changer la référence revient à faire pointer la variable sur un nouvel objet (une nouvelle instance). C'est très exactement çà qu'interdit le
final
. De même il n'est plus possible de fixer la variable à null
(ce qui revient à changer la référence).
Voici les résultats produit par le compilateur.
$> javac Demo.java Demo.java:7: error: final parameter date may not be assigned date = new Date( 0 ); // On cherche à référencer un nouvel objet ^ 1 error $>
La surcharge de méthodes consiste à définir plusieurs méthodes de même nom dans classe, mais avec des signatures différentes.
Par exemple, nous pourrions définir une seconde méthode mini
mais qui accepterait trois paramètres.
En cas de surcharge, c'est l'appel à la méthode qui permettra au compilateur de déterminer quelle est la bonne méthode à invoquer
(en fonction des paramètres passés lors de cet appel, bien entendu). Voici un petit exemple de surcharge de méthodes.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
public class Mathematiques { public static int mini( final int a, final int b ) { return a < b ? a : b; } public static int mini( final int a, final int b, final int c ) { if ( a < b && a < c ) { return a; } else if ( b < a && b < c ) { return b; } else { return c; } } public static int maxi( final int a, final int b ) { return a > b ? a : b; } public static void main( String [] args ) { System.out.println( "mini( 3, 5 ) == " + mini( 3, 5 ) ); System.out.println( "mini( 3, 5, 2 ) == " + mini( 3, 5, 2 ) ); System.out.println( "maxi( 3, 5 ) == " + maxi( 3, 5 ) ); } } |
Deux appels aux méthodes mini
sont présents dans de programme. L'appel en ligne 23 invoque la première version de mini
avec
deux paramètres. Le second appel, en ligne 24, invoque la seconde version de mini
avec trois paramètres.
Afin de s'entrainer à coder des méthodes je vous propose de reprendre le dernier exemple de code ci-dessus (la classe Mathematiques
) et de
la compléter avec les trois méthodes suivantes :
public static int power( final int value, final int pow )
: cette méthode doit calculer value
élevée à la puisssance
pow
. Par exemple 23 = 2*2*2 = 8.
public static int pgcd( int first, int second )
: cette méthode doit trouver le plus grand commun diviseur de deux entiers.
Vous pouvez utiliser l'algorithme d'Euclide, ou tout autre algorithme de votre cru ;-).
Au cas où : https://fr.wikipedia.org/wiki/Algorithme_d%27Euclide.
public static int fact( int value )
: cette dernière méthode doit calculer la factorielle d'une valeur. Petit rappel : 5! (la factorielle de 5)
vaut 5*4*3*2*1. Un autre exemple 6! vaut 6*5*4*3*2*1.
A vous de jouer !
En cadeau, j'ai codé une méthode supplémentaire permettant de simplifier une fraction. Le concept de fraction est pris en charge par une classe très
basique : la classe Rational
, qui est constituée de deux parties (le numérateur et le dénominateur). Nous reviendrons ultérieurement sur ce
concept de classe (lors des chapitres relatifs à la programmation orientée objet).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
class Rational { int numerator; int denominator; } public class Mathematiques { public static int mini( final int a, final int b ) { return a<b ? a : b; } public static int mini( final int a, final int b, final int c ) { if ( a < b && a < c ) { return a; } else if ( b < a && b < c ) { return b; } else { return c; } } public static int maxi( final int a, final int b ) { return a>b ? a : b; } public static int power( final int value, final int pow ) { int accumulator = 1; for (int i = 0; i < pow; i++) { accumulator *= value; } return accumulator; } public static int pgcd( int first, int second ) { while( second != 0 ) { int rest = first % second; first = second; second = rest; } return first; } public static void simplify( final Rational r ) { int divisor = pgcd( maxi(r.numerator, r.denominator), mini(r.numerator, r.denominator) ); r.numerator /= divisor; r.denominator /= divisor; } public static int fact( int value ) { int accumulator = 1; for (int i = 2; i <= value; i++) { accumulator *= i; } return accumulator; } public static void main( String [] args ) { System.out.println( "mini( 3, 5 ) == " + mini( 3, 5 ) ); System.out.println( "mini( 11, 5 ) == " + Mathematiques.mini( 11, 5 ) ); System.out.println( "maxi( 3, 5 ) == " + maxi( 3, 5 ) ); System.out.println( "power( 2, 0 ) == " + power( 2, 0 ) ); // 1 System.out.println( "power( 2, 1 ) == " + power( 2, 1 ) ); // 2 System.out.println( "power( 2, 2 ) == " + power( 2, 2 ) ); // 4 System.out.println( "power( 2, 3 ) == " + power( 2, 3 ) ); // 8 System.out.println( "pgcd( 3*7*11*13, 2*3*7*11 ) == " + pgcd( 3*7*11*13, 2*3*7*11 ) ); int first = 3*7*11*13; int second = 2*3*7*11; System.out.println( "pgcd( 3*7*11*13, 2*3*7*11 ) == " + pgcd( first, second ) ); System.out.println( first + " - " + second ); Rational r = new Rational(); r.numerator = 3*7*11*13; r.denominator = 2*3*7*11; Mathematiques.simplify( r ); System.out.printf( "[%d/%d]\n", r.numerator, r.denominator ); // [13/2] System.out.println( "5! == " + fact( 5 ) ); // 120 System.out.println( "6! == " + fact( 6 ) ); // 720 return; } } |
Améliorations / Corrections
Vous avez des améliorations (ou des corrections) à proposer pour ce document : je vous remerçie par avance de m'en faire part, cela m'aide à améliorer le site.
Emplacement :
Description des améliorations :