Rechercher
 

Définition de méthodes statiques

Utilisation d'assertions Méthodes à nombre variable de paramètres



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

La vidéo

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.


Définition de méthodes statiques

Qu'est qu'une méthode statique ?

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" );
        
    }

}
Le main est une méthode statique
la méthode statique 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.

La signature d'une méthode statique

Pour définir une méthode statique, il faut préciser un certain nombre de points, dans l'ordre indiqué ci-dessous.

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 ) );

    }
            
}
Méthodes statiques de calcul de minimum et de maximum
deux syntaxes peuvent permettre l'exécution de vos méthodes statiques. Soit vous utiliser la syntaxe 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
$>

Finalisation de paramètres et de variables locales

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
    }
            
}
Par défaut, un paramètre de méthode peut être modifié

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.

Finalisation de types primitifs

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);       
    }
            
}
Exemple de finalisation d'un paramètre entier

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 ) );

    }
            
}
Finalisation de paramètres de types primitifs

Finalisation de types objets

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 );
    }
            
}
Maniulation du contenu d'un objet finalisé
en Java, une date est stockée en mémoire sous forme d'un timestamp (un entier long) correspondant au nombre de millisecondes écoulées depuis le premier janvier 1970 à 00H00. La méthode 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 );
    }
            
}
Manipulation de la référence d'un objet finalisé

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 statiques

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 ) );

    }
            
}
Exemple de surcharges de méthodes

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.

Travaux pratiques

Le sujet

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 :

A vous de jouer !

La correction

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;
    }

}
Fichier Mathematiques.java : quelques traitements mathématiques
je sais que j'insiste un peu sur les mathématiques, mais bon, si vous souhaitez continuer dans la voie de l'informatique les mathématiques seront juste incontournable. Du coup, ... A méditer.
ces méthodes sont déjà codées dans la librairie Java standard. Désolé ;-)
Mais bon, c'est pour s'entrainer sur des algorithmes simples et que normalement on connait tous un peu.


Utilisation d'assertions Méthodes à nombre variable de paramètres