Dans le monde réel, une interface est la zone, la frontière commune entre deux domaines ou milieux. L’interface permet les échanges entre ces deux éléments aux propriétés différentes.
Si on prend l’exemple d’un ordinateur, on pourra le brancher au secteur électrique quel que soit le pays où l’on se trouve. L’interface entre l’ordinateur et le réseau est multiple et se fait grâce à un adaptateur secteur.
Ce même adaptateur possède plusieurs interfaces interchangeables permettant de le brancher à une prise électrique. Et cette prise de courant est une interface vers l’alimentation électrique présente dans le bâtiment.
L’interface ici est le câble. C’est le lien entre l’appareil électrique et le réseau électrique.
En Java, une interface est une classe totalement abstraite.
package com.example.java.interfaces;
public interface InterfaceUSB {
public void communiquerParPortUSB(byte[] information);
}Pour qu’une classe implémente une interface, on utilise le mot clé implements.
package com.example.java.interfaces;
public class Telephone implements InterfaceUSB{
private String nom ;
public Telephone(String nom) {
this.nom = nom;
}
@Override
public void communiquerParPortUSB(byte[] information) {
System.out.println("Données reçues par Telephone("+nom+")");
System.out.println(new String(information));
System.out.println("Fin de la réception");
}
}Contrairement au mot clé extends, implements peut être suivi de plusieurs valeurs, séparées par des virgules :
package com.example.java.interfaces;
public class Imprimante implements InterfaceUSB, InterfaceReseau{
//...
}De cette manière on a une forme d’héritage multiple.
Le terme employé est polymorphisme (on le verra plus tard).
La classe Ordinateur utilise l’interface InterfaceUSB.
package com.example.java.interfaces;
public class Ordinateur {
public void envoyerDonneesUSB(InterfaceUSB ir, byte[] information) {
ir.communiquerParPortUSB(information);
}
}La méthode envoyerDonneesUSB utilise une variable du type InterfaceUSB.
Cette variable référence une instance qui implémente cette interface pour envoyer des données.
La classe Communication permet de brancher tout ces éléments entre eux:
public class Communication {
public static void main(String[] args) {
Ordinateur ordi = new Ordinateur();
byte[] donnees = "Bonjour".getBytes();
ordi.envoyerDonneesUSB(new Telephone("rouge"), donnees);
donnees = "Page à imprimer".getBytes();
InterfaceUSB imprimanteAnonyme = new InterfaceUSB() {
@Override
public void communiquerParPortUSB(byte[] information) {
System.out.println("//Je suis une imprimante et j'imprime une page:");
for (int i = 0; i < information.length; i++) {
System.out.println(String.valueOf(information[i]));
}
System.out.println("//Fin de l'impression");
}
};
ordi.envoyerDonneesUSB(imprimanteAnonyme, donnees);
}
}Qu’observez-vous dans ce code ?
Pensez-vous logique que l’on appelle un constructeur sur l’interface InterfaceUSB alors que c’est une classe abstraite ?
Eléments de réponse:
Une interface est toujours implémentée par une classe.
Cependant, certaines classes peuvent être anonymes.
C’est le cas ici de imprimanteAnonyme.
On affecte à cet attribut une classe anonyme, c’est à dire :
InterfaceUSB (qui pourtant est une interface et donc est abstraite)D’autres exemples sont disponibles dans le cours : https://en.wikibooks.org/wiki/Java_Programming/Interfaces
L’interface représente un contrat d’utilisation entre les classes qui l’implémentent et les classes qui l’utilisent.
On peut éventuellement ajouter des constantes à une interface, qui seront utilisables directement par les classes qui implémenteront l’interface.
On ajoute aussi des commentaires et on peut même indiquer des traitements d’exception.
package com.example.java.interfaces;
public interface InterfaceReseau {
public static final int VITESSE_RESEAU_1GB = 2^1000;
public static final int VITESSE_RESEAU_100MB = 2^100;
public static final int VITESSE_RESEAU_10MB = 2^10;
/**
* Permet de traiter les informations données en paramètre
* Cette méthode ne peut a priori être appelée qu'une fois
* que l'adresse réseau de l'imprimante a été affectée
* @param information l'information à traiter
* @throws IllegalAccessException si l'adresse réseau n'est pas configurée pour cet équipement
*/
public void communiquerParReseauFilaire(byte[] information) throws IllegalStateException;
/**
* Affecte l'adresse réseau à cet équipement.
*
* Cette méthode modifie l'état interne de l'instance
* pour permettre l'appel des autres méthodes de la classe
* @param adresse l'adresse à affecter sous forme d'un
* tableau de byte (pour prendre en charge IPv4 et IPv6)
*/
public void affecterAdresseReseau(byte[] adresse);
}Etudiez les commentaires Javadoc.
Quelle est leur utilité ?
Faire un schéma UML des classes précédentes
Implémentez-les et faites exécuter ce programme.
Complétez la classe Imprimante pour qu’elle affiche des informations lorsqu’elle est connectée au réseau (c’est-à-dire qu’elle a une adresse réseau affectée):
package com.example.java.interfaces;
public class Imprimante implements InterfaceUSB, InterfaceReseau{
// Ici on utilise une constante définie dans l'interface
int vitesse = VITESSE_RESEAU_100MB;
@Override
public void communiquerParReseauFilaire(byte[] information) throws IllegalStateException {
//...
}
@Override
public void communiquerParPortUSB(byte[] information) {
//...
}
@Override
public void affecterAdresseReseau(byte[] adresse) {
// TODO Auto-generated method stub
}
}Ajouter à la classe Ordinateur la méthode suivante qui utilisera l’interface InterfaceReseau pour envoyer des données:
public void envoyerDonneesReseau(InterfaceReseau ir, byte[] information) ;
Chaque communication sera affichée dans la console avec un préfixe: USB ou Réseau respectivement.
Testez votre implémentation en faisant envoyer des messages via les deux ports de communication de l’ordinateur à une instance d’Imprimante
Les interfaces permettent aux objets d’être polymorphes. C’est-à-dire, pour une instance, d’être de plusieurs types.
Une classe peut implémenter plusieurs interfaces.
On donne un type à la variable qui référence l’instance.
C’est le type de la variable qui détermine les méthodes appelables.
Une même instance peut être référencée par des variables de types différents:
En Java, l’héritage multiple n’est pas possible. Vous ne trouverez jamais ce genre de chose:

Et c’est tant mieux !
Il existe bien d’autres moyens de partager des fonctionnalités entre des classes différentes.
Les interfaces en Java permettent de définir une liste de fonctionnalités que fournira une classe.
Ces interfaces sont, on l’a dit, des classes totalement abstraites.

Code de l’interface VehiculeMotorise :
package com.example.transport;
public interface VehiculeMotorise {
/**
* Augmente la vitesse du véhicule de 1m/s
*/
public void accelerer();
}On remarque qu’ici, la Javadoc donne le contrat de fonctionnement de l’interface.
En Java, une classe implémente une interface mais n’en hérite pas.
Donc, l’inconvénient est qu’il faut réimplémenter la méthode de l’interface dans la classe.
Heureusement, on peut (par exemple) utiliser ce type de conception afin de réutiliser le code de la classe Moteur:

Tester les classes en utilisant les codes ci-dessous.
Code de la classe Vehicule:
package com.example.transport;
public class Vehicule {
private int vitesse;
private int position;
public void setVitesse(int vitesse){
this.vitesse = vitesse;
}
public int getVitesse(){
return vitesse;
}
public int getPosition(){
return position;
}
public void metAJourPosition(){
position += vitesse ;
}
}Code de la classe Moteur:
package com.example.transport;
public class Moteur {
private Vehicule vehicule;
public void setVehicule(Vehicule v){
vehicule = v;
}
public void propulse(){
vehicule.setVitesse(
vehicule.getVitesse() + 1);
}
}Code de la classe Automobile:
package com.example.transport;
public class Automobile extends Vehicule implements VehiculeMotorise {
private Moteur moteur = new Moteur();
public Automobile(){
moteur.setVehicule(this);
}
public void accelerer(){
moteur.propulse();
}
}Code de la classe FaireRoulerVoiture:
package com.example.transport;
public class FaireRoulerVoiture {
public static void main(String[] args) {
Automobile auto = new Automobile();
//On accélère
auto.accelerer(); //v = 1
//On avance
auto.metAJourPosition();
System.out.println(auto.getPosition());
auto.accelerer(); //v = 2
auto.metAJourPosition();
System.out.println(auto.getPosition());
auto.metAJourPosition();
System.out.println(auto.getPosition());
}
}Ici, c’est bien le moteur qui modifie la vitesse du véhicule.
Son code est réutilisable pour n’importe quel type de véhicule motorisé (Bateau, Avion, etc).
Le véhicule motorisé délègue au moteur la tâche d’accélération.
On parle donc ici d’une délégation. Le code est réutilisable, sans faire d’héritage.
Posez la question à l’intervenant pour éclaircir ce point si nécessaire.
Vous allez maintenant mettre en application ce qui précède.
Pour cela, créer trois classes selon le diagramme de classe suivant:

La méthode ecrire(String chaine) de stylo produira l’écriture sur la sortie standard.
La méthode ecrire(String chaine) de ses classes filles rajoutera ECRIRE_EN_couleur{ au début de la chaîne avant de l’écrire puis y rajoutera } à la fin de la chaîne.
Par exemple si on a dans la fonction main(String[]):
StyloRouge styloR = new StyloRouge();
styloR.ecrire("Bonjour");
// affiche: "ECRIRE_EN_ROUGE{Bonjour}"Petite difficulté supplémentaire: vous n’avez pas le droit d’utiliser de System.out.println dans les classes StyloRouge ni StyloBleu
Nous allons maintenant faire en sorte que notre stylo soit utilisé par un poète qui souhaite rédiger un poème. Il va simplement l’afficher à l’écran grâce au stylo.
Voici un diagramme de classe représentant notre programme:

Et voici le programme correspondant:
public class Poete {
private String monNouveauPoeme = "";
private Stylo stylo ;
public void ajouterVersAuPoeme(String vers){
monNouveauPoeme = monNouveauPoeme + "\n" ;
monNouveauPoeme += vers;
}
public void setStylo(Stylo stylo){
this.stylo = stylo;
}
public void ecrirePoeme(){
stylo.ecrire(monNouveauPoeme);
}
}La classe main(String[]) ressemble à ceci:
public class Main {
public static void main(String[] args) {
Poete poete = new Poete();
poete.ajouterVersAuPoeme("Maître Corbeau, sur un arbre perché");
poete.ajouterVersAuPoeme("Tenait en son bec un fromage");
poete.setStylo(new StyloRouge());
poete.ecrirePoeme();
}
}Modifiez ce code en supprimant la ligne suivante du main(String[]):
poete.setStylo(new StyloRouge());
Que constate-t-on ?
Cette erreur vient du fait que la variable monStylo n’est plus initialisée. Comment lui donner une valeur par défaut, de sorte que même si on n’appelle pas la méthode setStylo(Stylo) le programme ne plante pas ?
Réfléchissez à un moyen de vous passer des classes StyloRouge et StyloBleu tout en gardant les fonctionnalités qu’ils offrent.
Vous allez utiliser pour cela des attributs et modifier le comportement de la méthode ecrire(String).
Dessinez le diagramme de classe correspondant.
On s’intéresse ici à l’interface Hierarchisable :
Les implémentations de cette interface doivent respecter le contrat suivant:
la méthode estPlusGrandQue doit retourner true si l’objet courant (this) est considéré comme plus grand que l’objet reçu en paramètre. Sinon, elle renvoie false. Si l’objet reçu en paramètre n’est pas de la même classe que l’objet courant, la méthode retournera une exception de type IllegalArgumentException.
On définit maintenant une classe nommée Personne qui implémente l’interface Hierarchisable et qui comporte au minimum :
nom et prenom (type String), inaccessibles depuis l’extérieur de la classenom et prenom.Vous pouvez si vous le souhaiter réutiliser la classe Personne déjà créée précédemment.
La classe String implémente la méthode Comparable.compareTo(...). Etudiez cette méthode et vérifier si elle peut vous servir ici.
Créer un programme principal permettant de classer dans l’ordre alphabétique plusieurs personnes.
Voici l’interface Salutation:
public interface Salutation {
/**
* Renvoie une chaîne de caractère représentant
* une forme de salutation selon la langue à employer
*/
public String salut() ;
}Voici une méthonde principale utilisant cette interface:
Salutation francais = new Bonjour();
System.out.println(francais.salut()); //Doit afficher Bonjour
Salutation [] salutations = new Salutation [] {
new Bonjour() , //Français
new Hello() , //Anglais
new BuenosDias(), //Espagnol
new GutenTag() //Allemand
};
for (int i = 0; i < salutations.length; i++){
System.out.println(salutations[i].salut());
}Implémenter les classes Bonjour, Hello, BuenosDias et GutenTag.