Participer au site avec un Tip
Rechercher
 

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 :

Aspects avancés sur la définition d'interfaces

Expressions Lambdas et références sur méthodes Les classes scellées


Accès rapide :
La vidéo
Définition de méthodes statiques sur interface
Définition de méthodes par défaut sur interface
Objectif de cette évolution
Mise en oeuvre de méthodes par défaut sur interface
Définition de méthode privée sur interface
Travaux pratiques
Le sujet
La correction

La vidéo

Cette vidéo se concentre sur trois possibilités liées à la définition d'interfaces en Java : définition de méthodes statiques sur une interface, définition de méthodes par défaut (default methods) et la définition de méthodes privées sur interface. Ces trois possibilités étant disponibles depuis Java SE 8.0 et 9.0.


Aspects avancés sur la définition d'interfaces

Définition de méthodes statiques sur interface

La version 8.0 du Java SE a pas mal bousculé les choses en matière de définition d'interface. Avant cette version, une interface ne pouvait contenir que et uniquement que des méthodes abstraites. C'est, en gros, ce que je vous ai montré jusqu'à présent. Mais depuis la version 8.0, ce n'est plus vrai. Une interface peut maintenant avoir des méthodes directement implémentées dans sa définition. Mais attention : on ne peut pas implémenter n'importe quelle méthode. Seules certaines possibilités sont supportées.

La première consiste à définir une méthode statique portée par l'interface. Cette possibilité peut notamment être très utile pour définir ce qu'on appelle une « factory method ». Une « factory method » est un design pattern qu'on utilise quand on ne connaît pas à l'avance le type exact d'une instance à produire : on passe alors pas une méthode (la « factory method ») qui devra, en fonction de certains critères, faire le choix du type à produire.

Par exemple, imaginons que nous cherchions à produire une librairie de manipulation d'images à différents formats (GIF, JPEG, PNG, ...). Comment gérer le décodage de l'image en sachant que ces images peuvent être de différentes natures ? Et bien, on peut passer par une « factory method ». Voici comment déclarer l'interface Image qui sera, dans notre scénario, le type de base de toutes les autres classes d'images.

 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 
package fr.koor.interfaces;

import java.awt.Color;
import java.util.Date;

public interface Image {
    
    /*public static final*/ String PNG_EXTENSION = ".png";
    public static final String JPG_EXTENSION = ".jpg";
    public static final String GIF_EXTENSION = ".gif";
    
    public Color [][] getBitmap();
    
    public abstract void load();
    
    /*public abstract*/ void save();
    
    // Autres méthodes abstraites proposées par l'interface
    

    /* Notre "Factory Method" en charge de produire la bonne instance d'image */        
    public static Image createImage( String filename ) {
        int dotPos = filename.lastIndexOf( "." );
        if ( dotPos == -1 ) throw new RuntimeException( "Bad image filename: " + filename );
        String extension = filename.substring( dotPos );
        switch ( extension ) {
            case PNG_EXTENSION:
                return new PngImage( filename );
            case GIF_EXTENSION:
                return new GifImage( filename );
            case JPG_EXTENSION:
                return new JpgImage( filename );
            default:
                throw new RuntimeException( extension + " format not actually supported" );
        }
    }
    
}
Une interface permettant d'introduire le concept d'images
n'oubliez pas que par défaut une méthode d'interface est qualifiée public et abstract. Vous pouvez donc ne pas mettre ces deux mots clés devant les déclarations de méthodes, vous pouvez aussi les mettre explicitement. Et notez aussi que ce que je viens de rappeler ne s'applique qu'aux interfaces. Il en va autrement pour les méthodes de classes (visibilité « package » par défaut).
de même, si vous définissez un attribut sur une interface, il sera automatiquement considéré comme étant une constante. Il sera donc implicitement qualifié public static final. Vous pouvez, bien entendu, explicitement rajouter ces trois mots clés.

Et voici un exemple d'implémentation de classe d'image basée sur notre interface. Les méthodes load, save et getBitmap resteraient à implémenter. Si certains d'entre vous se sentent de regarder comment les fichiers PNG, GIF et JPEG sont compressés et de fournir un code concret, ce sera un excellent exercice. Dans ce cas, n'hésitez pas à me soumettre votre implémentation (bouton orange en haut et à droite) : perso, sur ce coup-là, je suis un peu fainéant ;-).

 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 
package fr.koor.interfaces;

import java.awt.Color;

public class PngImage implements Image {

    @SuppressWarnings("unused")
    private String filename;
    
    public PngImage( String filename ) {
        this.filename = filename;
        load();
    }
    
    @Override
    public void load() {
        System.out.println( "Imagine: PNG image loaded" );
    }

    @Override
    public void save() {
        System.out.println( "Imagine: PNG image saved" );
    }

    @Override
    public Color[][] getBitmap() {
        // TODO Auto-generated method stub
        return null;
    }
}
Exemple de classe implémentant notre interface spécifiquement pour le format PNG

Définition de méthodes par défaut sur interface

Objectif de cette évolution

Le Java SE 8.0 a aussi rajouté au langage le concept de méthode par défaut d'interface (en anglais « default methods » ). Une méthode par défaut possède obligatoirement une implémentation (le corps de la méthode est défini dans l'interface) et n'est donc pas une méthode abstraite. Dit autrement, une interface Java moderne n'est pas 100% abstraite : elle peut posséder des implémentations de certaines méthodes.

Alors, il est vrai que ce sujet est délicat : les notions d'interface et de méthodes par défaut font qu'on se rapproche beaucoup de la notion de classe abstraite. Etait-il nécessaire d'apporter cette évolution ? Nous pouvions nous pas garder les interfaces sans méthodes par défaut ? Et ne pouvions-nous pas simplement utiliser un classe abstraite dans le cas où certaines méthodes pouvaient être implémentées ? La question mérite d'être posée.

Pour autant, ceux qui ont proposé cette évolution, la défende avec l'argument suivant : une fois qu'une interface a était exposée (pensez à java.util.List par exemple), comment la faire évoluer, en ajoutant de nouvelles méthodes, sans imposer à tous ceux qui l'ont implémentée de devoir obligatoirement retoucher leur code ? Effectivement, si on rajoute une nouvelle méthode abstraite à l'interface java.util.List, alors tous les codes actuels implémentant cette interface (et qui compilaient très bien jusque-là) ne compileraient tout simplement plus tant que la nouvelle méthode ne serait pas concrétisée. C'est clair qu'une telle évolution est très contraignante.

Or, avec le Java SE 8.0, l'interface java.util.List a évolué ! Elle propose maintenant des nouvelles méthodes telles que la méthode sort. Et bien, ces méthodes sont des « default methods ». Elles proposent une implémentation par défaut pour chaque méthode et vous n'avez pas à retoucher à vos propres implémentations de l'interface (si vous en aviez, bien entendu).

Mise en oeuvre de méthodes par défaut sur interface

Je vais rester sur notre exemple d'interface permettant d'introduire la notion d'image. Quel que soit le type de l'image (GIF, JPEG ou PNG), certaines opérations de traitement d'images seront identiques dans leurs implémentations. Par exemple, pour éclaircir ou assombrir une image, seul le tableau de pixels importe.

Pour introduire une méthode par défaut sur interface, il faut utiliser le mot clé default. Je vous propose ces deux implémentations par défaut sur notre interface.

 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 
package fr.koor.interfaces;

import java.awt.Color;
import java.util.Date;

public interface Image {
    
    // Le début de notre interface (voir ci-dessus)
        
    // Une méthode abstraite permettant de retrouver un tableau de pixels
    public Color [][] getBitmap();

    
    public default void brighter() {
        Color [][] bitmap = this.getBitmap();
        if ( bitmap != null ) {
            for( int y=0; y<bitmap.length; y++ ) {
                Color [] row = bitmap[y];
                for( int x=0; x<row.length; x++ ) {
                    row[x] = row[x].brighter();
                }
            }
        }
    }
    
    public default void darker() {
        Color [][] bitmap = this.getBitmap();
        if ( bitmap != null ) {
            for( int y=0; y<bitmap.length; y++ ) {
                Color [] row = bitmap[y];
                for( int x=0; x<row.length; x++ ) {
                    row[x] = row[x].darker();
                }
            }
        }
    }
    
    // La suite de notre interface
    
}
Exemple de « default methods » sur interface
le mot clé default n'est pas nouveau pour Java SE 8.0 : en fait il existait déjà et il était utilisé dans l'instruction switch. Il a simplement été recyclé pour servir aussi dans le contexte des méthodes par défaut.

Définition de méthode privée sur interface

Le Java SE 9 a lui aussi apporté une petite évolution sur la mise en oeuvre d'interface : il est maintenant possible d'y définir des méthodes privées concrètes ! Seules les méthodes par défaut de l'interface y auront accès étant donné le caractère « privé » de cette nouvelle possibilité.

Voici un exemple de définition d'une méthode privée dans une interface.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
package fr.koor.interfaces;

import java.awt.Color;
import java.util.Date;

public interface Image {
    
    // Le début de notre interface (voir ci-dessus)
        
    // Attention, Java SE 9 minimum requis !!!   
    private void log( String message ) {
        System.out.println( new Date() + " - " + message );
    }
    
}
Exemple de « default methods » sur interface

Travaux pratiques

Le sujet

Repartir de l'exemple d'API de chargement d'images proposées dans ce document et rajouter une classe pour le format GIF et une autre pour le format JPEG. Comme indiqué précédemment, il s'agit d'un exemple de code et nous allons imaginer avoir codé le décodage et l'encodage des images aux différents formats. Si certains d'entre vous se sentent de proposer les algos d'encodage et de décodage, alors bon courage.

Validez le bon fonctionnement de la méthode de construction d'image avec quelques exemples dans un main. Rajouter les deux « default methods » proposées ainsi que la méthode privée de « log ». Invoquez cette méthode privée dans les autres méthodes de l'interface afin de tracer l'activité de votre programme.

Comme toujours, essayez de réaliser cet exercice par vous-même avant de passer à la correction. ;-)

La correction

Voici donc les différents fichiers constituant la correction de l'exercice proposé.

 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 
package fr.koor.interfaces;

import java.awt.Color;
import java.util.Date;

public interface Image {
    
    /*public static final*/ String PNG_EXTENSION = ".png";
    public static final String JPG_EXTENSION = ".jpg";
    public static final String GIF_EXTENSION = ".gif";
    
    public Color [][] getBitmap();
    public abstract void load();
    /*public abstract*/ void save();
    
    public default void brighter() {
        Color [][] bitmap = this.getBitmap();
        if ( bitmap != null ) {
            for( int y=0; y<bitmap.length; y++ ) {
                Color [] row = bitmap[y];
                for( int x=0; x<row.length; x++ ) {
                    row[x] = row[x].brighter();
                }
            }
        }
        log( "image too bright" );
    }
    
    public default void darker() {
        Color [][] bitmap = this.getBitmap();
        if ( bitmap != null ) {
            for( int y=0; y<bitmap.length; y++ ) {
                Color [] row = bitmap[y];
                for( int x=0; x<row.length; x++ ) {
                    row[x] = row[x].darker();
                }
            }
        }
        log( "image too darker" );
    }
    
    public static Image createImage( String filename ) {
        int dotPos = filename.lastIndexOf( "." );
        if ( dotPos == -1 ) throw new RuntimeException( "Bad image filename: " + filename );
        String extension = filename.substring( dotPos );
        switch ( extension ) {
            case PNG_EXTENSION:
                return new PngImage( filename );
            case GIF_EXTENSION:
                return new GifImage( filename );
            case JPG_EXTENSION:
                return new JpgImage( filename );
            default:
                throw new RuntimeException( extension + " format not actually supported" );
        }
    }
    
    private void log( String message ) {
        System.out.println( new Date() + " - " + message );
    }
    
}
Définition de l'interface Image

 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 
package fr.koor.interfaces;

import java.awt.Color;

public class GifImage implements Image {

    @SuppressWarnings("unused")
    private String filename;
    
    public GifImage( String filename ) {
        this.filename = filename;
        load();
    }
    
    @Override
    public void load() {
        System.out.println( "Imagine: GIF image loaded" );
    }

    @Override
    public void save() {
        System.out.println( "Imagine: GIF image saved" );
    }

    @Override
    public Color[][] getBitmap() {
        // TODO Auto-generated method stub
        return null;
    }
}
Pseudo implémentation pour le format GIF

 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 
package fr.koor.interfaces;

import java.awt.Color;

public class JpgImage implements Image {

    @SuppressWarnings("unused")
    private String filename;
    
    public JpgImage( String filename ) {
        this.filename = filename;
        load();
    }
    
    @Override
    public void load() {
        System.out.println( "Imagine: JPG image loaded" );
    }

    @Override
    public void save() {
        System.out.println( "Imagine: JPG image saved" );
    }

    @Override
    public Color[][] getBitmap() {
        // TODO Auto-generated method stub
        return null;
    }
}
Pseudo implémentation pour le format JPEG

 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 
package fr.koor.interfaces;

import java.awt.Color;

public class PngImage implements Image {

    @SuppressWarnings("unused")
    private String filename;
    
    public PngImage( String filename ) {
        this.filename = filename;
        load();
    }
    
    @Override
    public void load() {
        System.out.println( "Imagine: PNG image loaded" );
    }

    @Override
    public void save() {
        System.out.println( "Imagine: PNG image saved" );
    }

    @Override
    public Color[][] getBitmap() {
        // TODO Auto-generated method stub
        return null;
    }
}
Pseudo implémentation pour le format PNG

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
package fr.koor.interfaces;

public class Start {

    public static void main(String[] args) {
        System.out.println( Image.PNG_EXTENSION );
        
        Image image = Image.createImage( "toto.gif" );
        image.darker();
    }

}
Classe de démarrage du projet


Expressions Lambdas et références sur méthodes Les classes scellées