Interface :
Pourquoi ?
Le Java ne supporte pas l'héritage multiple comme C++ :
super classe
A1
super classe
A2
super classe
A3 etc …
classe dérivée en C++
B
En Java une classe n'a pas le droit d'hériter directement deux super-classes. Par contre, Java permet à une classe d'être dérivée d'une seule super-classe mais implémente une ou plusieurs interfaces.
super classe en Java
A
interface I1
interface
I2 etc
sous-classe B
dérivée de A implémente I1, I2, etc
De plus, une interface contient souvent ses méthodes abstraites qui présentent certaines
comportements assez générales ( hasMoreElements() : avoir encore d’éléments,
nextElement() : prochain élément pour les structures linéaires, writeInt() : écrire un
entier, writeDouble() : écrire un réel, … pour les fichiers binaires, run() : déplacer, pour
animation, …). Ces méthodes abstraites seront réalisées dans les classes qui
implémentent cette interface.
Cas 1 de Java : StringTokenizer
classe Object
java.util
interface Enumeration
Method Summary
boolea
n hasMoreElements()
: avoir encore d’éléments
retourne true si cette énumération contient encore des elements : false sinon
Object nextElement()
: prochain élément
retourne l’élément suivant de cette énumération.
Par défaut, toutes ces méthodes sont abstraites et public.
java.util class StringTokenizer extends Object
implements java.util.Enumeration
StringTokenizer chaine = new StringTokenizer(“Java est populaire”);
while ( chaine.hasMoreElements() )
System.out.println( chaine.nexElement() );
nous donne : Java
est
populaire
Cas 2 de Java : Vector
super classe en Java AbstractList
interface
List
interface
Cloneable
interface
Serializable
public class Vector extends AbstractList
implements List, Cloneable, Serializable
(note : Vector n’implémente pas l’interface Enumeration mais une de ses méthodes retourne un objet de la « classe » Enumeration)
. . .
// cette méthode retourne une énumération des éléments de ce // vecteur
public final Enumeration elements();
. . .
Pour afficher tous les éléments d’un vecteur v :
for (Enumeration elem = v.elements() ; elem.hasMoreElements() ; )
System.out.println(elem.nextElement());
Cas 3 de Java : fichiers binaires
Java utilise le terme Reader/Writer pour désigner les suites de caractères dans le traitement des fichiers de type texte. Pour la lecture d’un fichier texte, on lit souvent ligne par ligne (avec
Entree.readLine()) et extraire des sous-chaînes de la chaîne lue puis faire la conversion en type voulu (exemple, avec Integer.parseInt(sous- chaîne)).
Contenu d’un fichier de type texte : Je suis une ligne
Je suis une autre ligne etc ….
Je suis la dernière ligne
Le Java peut détecter les caractères fin de ligne et fin de fichier.
Java utilise le terme STREAM (flux, flot) pour désigner des suites d'octets (bytes) dans le traitement des fichiers binaires :
Contenu d’un fichier binaire :
1 entier 1 réel 1 caractère 1 entier 1 réel 1 caractère etc …
writeInt() writeDouble() WriteChar() writeInt() writeDouble() WriteChar() etc … Il y a InputStream pour la lecture et OutputStream pour l'écriture.
Dans le cours IFT 1176, on traite aussi les fichiers à accès direct.
Pour localiser un fichier à écrire son contenu, Java combine 2 objets des deux classes DataOutputStream et FileOutputStream :
DataOutputStream aCreer = new DataOutputStream
( new FileOutputStream(nomFile));
On implémente et utilise des méthodes de l’interface DataOutput : public abstract void
writeInt
(int valeur);public abstract void
writeChar
(int v);public abstract void
writeDouble
(double val);etc ….
pour écrire un entier, un caractère ou un réel dans le fichier binaire.
public class DataOutputStream extends FilterOutputStream implements DataOutput
Dans ce cas ci, l’interface DataOutput est un groupe de méthodes abstraites indiquant que DataOutputStream est dotée d’un comportement supplémentaire par rapport aux méthodes héritées de sa super-classe FilterOutputStream.
Les deux pages suivantes décrivent des schémas de la création et de la lecture d’un fichier binaire.
Gestion de fichiers:
Quelques classes + interfaces utilisées pour la création d'un fichier binaire :
classe Object
OutputStream
FilterOutPutStream
public void close();
FileOutputStream
public FileOutputStream String name);
etc . . .
DataOutput (une interface)
public abstract void
writeInt
(int valeur);public abstract void
writeChar
(int v);public abstract void
writeDouble
(double val);etc . . .
DataOutputStream
public DataOutputStream(OutputStream os);
public final void
writeInt
(int valeur);public final void
writeChar
(int v);public final void
writeDouble
(double val);etc . . .
public class java.io.DataOutputStream
extends java.io.FilterOutputStream
implements java.io.DataOutput
(DataOutputStream dérivée de FilterOutputStream implémente l'interface DataOutput)
Quelques classes + interfaces utilisées pour la lecture d'un fichier binaire :
classe Object
InputStream
FilterInputStream
public void close();
FileInputStream
public FileInputStream String name);
etc . . .
DataInput (une interface)
public abstract int
readInt
();public abstract double
readDouble
();etc . . .
DataInputStream
public DataInputStream(InputStream is);
public final int
readInt
();public final double
readDouble
();etc . . .
public class java.io.DataInputStream extends java.io.FilterInputStream implements java.io.DataInput
(DataInputStream dérivée de FilterInputStream
implémente l'interface DataInput)
/**
* Ce programme permet :
* - de créer un fichier contenant les diviseurs de 720 * - de relire le fichier et d'afficher son contenu */
import java.io.*;
public class FichierBinaire1 {
// création d'un fichier
static void creerFichier(String nomFile, int nombre) throws IOException {
DataOutputStream aCreer = new DataOutputStream
( new FileOutputStream(nomFile));
for (int candidat = 1 ; candidat <= nombre ; candidat++) if ( nombre % candidat == 0)
aCreer.writeInt(candidat);
aCreer.close();
System.out.println("Fin de la creation du fichier " + nomFile);
}
// lecture d'un fichier
static void relireFichier(String nomFile, int nombre) throws IOException {
System.out.println("Lecture du fichier binaire " + nomFile +
" contenant les diviseurs de " + nombre);
DataInputStream aLire = new DataInputStream
( new FileInputStream(nomFile));
int rang = 0 , valeur = -555 ; boolean finFichier = false ; while ( ! finFichier ) {
try {
valeur = aLire.readInt();
} catch ( EOFException e ) { finFichier = true;
}
if (!finFichier)
System.out.println(++rang + ")\t" + valeur);
}
aLire.close();
System.out.println("Fin de la lecture du fichier " + nomFile + "\n");
}
public static void main (String[] args) throws IOException {
creerFichier("C:\\DiviDe720.dta", 720);
relireFichier("C:\\DiviDe720.dta", 720);
} }
/* Exécution :
Fin de la creation du fichier C:\DiviDe720.dta
Lecture du fichier binaire C:\DiviDe720.dta contenant les diviseurs de 720
1) 1 2) 2 3) 3 4) 4 5) 5 6) 6 7) 8 8) 9 9) 10 10) 12 11) 15 12) 16 13) 18 14) 20 15) 24 16) 30 17) 36 18) 40 19) 45 20) 48 21) 60 22) 72 23) 80 24) 90 25) 120 26) 144 27) 180 28) 240 29) 360 30) 720
Fin de la lecture du fichier C:\DiviDe720.dta
*/
Autre exemple : fichier binaire des pays /**
* Fichier FichierBinaire2.java
* But : lire un fichier texte (Pays.Txt), créer un fichier binaire des pays.
* relire le fichier binaire et afficher : * - la liste des pays d'amérique * - la liste des pays d'océanie *
* */
import java.io.*;
public class FichierBinaire2 {
// afficher un entier (nombre) avec tant de colonnes (nbCol) static void afficher(int nombre, int nbCol) {
// construire un objet de la classe Integer Integer n = new Integer(nombre);
// convertir en chaîne String chaine = n.toString();
// afficher des espaces avant
for (int i = 1 ; i <= nbCol-chaine.length() ; i++) System.out.print(" ");
// afficher le nombre System.out.print(chaine);
}
// lire le fichier texte PAYS.TXT et créer un fichier binaire PAYS.BIN static void lireCreer(String nomTexte, String nomBinaire)
throws IOException {
// préparer le fichier à lire et à écrire
BufferedReader entree = new BufferedReader(new FileReader (nomTexte));
DataOutputStream aCreer = new DataOutputStream ( new FileOutputStream(nomBinaire));
System.out.println("\nLecture du fichier texte : " + nomTexte);
boolean finFichier = false ; while ( !finFichier ) {
// lire une ligne de texte:
String ligneLue = entree.readLine();
if (ligneLue == null) finFichier = true ; else {
char continent = ligneLue.charAt(0);
String nom = ligneLue.substring(1, 21);
String capitale = ligneLue.substring(21,41);
int superficie = Integer.parseInt(ligneLue.substring(41,51).trim());
int population = Integer.parseInt(ligneLue.substring(51).trim());
// écrire en binaire :
aCreer.writeChar(continent);
aCreer.writeChars(nom);
aCreer.writeChars(capitale);
aCreer.writeInt(superficie);
aCreer.writeInt(population);
}
}
// fermer les fichiers entree.close();
aCreer.close();
System.out.println("\nFin de la creation du fichier binaire : " + nomBinaire);
}
// afficher les pays d'un continent voulu
static void afficher(String nomBinaire, char contVoulu) throws IOException { DataInputStream aLire = new DataInputStream
( new FileInputStream(nomBinaire));
String [] nomContinents = { "Afrique", "Amerique", "Asie", "Oceanie", "Europe"};
System.out.println("\nListe des pays d'" + nomContinents[ (int) (contVoulu - 49) ]);
final int LONGUEUR = 20 ; // longueur d'un nom ou d'une capitale int rang = 0, superficie = 0, population = 0;
char continent = ' ';
String nom = "", capitale = "";
boolean finFichier = false ; while ( ! finFichier ) {
try {
continent = aLire.readChar();
} catch ( EOFException erreur ) {
finFichier = true;
}
if (!finFichier) { nom = "";
for (int i = 0 ; i < LONGUEUR ; i++) nom += aLire.readChar();
capitale = "";
for (int i = 0 ; i < LONGUEUR ; i++) capitale += aLire.readChar();
superficie = aLire.readInt();
population = aLire.readInt();
if (continent == contVoulu) { afficher(++rang, 3);
afficher(population, 11);
System.out.println();
} } }
aLire.close();
}
public static void main (String[] args)throws IOException {
lireCreer("R:\\pays.txt", "R:\\pays.bin");
afficher("R:\\pays.bin", '2'); // afficher les pays d'Amérique afficher("R:\\pays.bin", '4'); // afficher les pays d'Océanie }
}
/* Exécution:
Lecture du fichier texte : R:\pays.txt
Fin de la creation du fichier binaire : R:\pays.bin Liste des pays d'Amerique
1) CUBA LA HAVANE 114524 10500000 2) BOLIVIE LA PAZ 1100000 7100000 3) BRESIL BRASILA 8512000 147000000 4) GUATEMALA GUATEMALA CIUDAD 109000 8900000 5) ARGENTINE BUENOS AIRES 2780000 31900000 6) COLOMBIE BOGOTA 1139000 31200000 7) HONDURAS TEGUCIGALPA 112088 5000000 8) DOMINICAINE SAINT-DOMINIQUE 48400 6200000 9) EQUATEUR QUITO 270670 10500000 10) PEROU LIMA 1285000 21400000 11) COSTA RICA SAN JOSE 51000 3000000 12) SALVADOR SAN SALVADOR 21393 5200000 13) NICARAGUA MANAGUA 148000 3500000 14) PANAMA PANAMA 77000 2400000 15) JAMAIQUE KINGSTON 11425 2500000 16) URUGUAY MONTEVIDEO 177500 3000000 17) VENEZUELA CARACAS 912000 19100000 18) HAITI PORT-AU-PRINCE 27750 6400000 19) ETATS-UNIS WASHINGTON 9364000 249600000 20) CANADA OTTAWA 9975000 26300000 21) PARAGUAY ASUNCION 407000 4200000 22) MEXIQUE MEXICO 1970000 86700000 Liste des pays d'Oceanie
1) NOUVELLE ZELANDE WELLINGTON 270000 3400000 2) AUSTRALIE CANBERRA 7700000 16800000
*/
Programmer nous-même les interfaces :
Pour trier un tableau des personnes selon leurs tailles, la méthode de tri suivant fonctionne :
public static void trier(Personne [] pers, nbPers) { Personne tempo;
for(int i = 0 ; i < nbPers-1; i++) { int indMin = i;
for(int j = i+1 ; j < nbPers; j++)
if (pers[j].getTaille() < pers[indMin].getTaille() ) indMin = j;
if (indMin != i) { tempo = pers[i];
pers[i]=pers[indMin];
pers[indMin]=tempo;
} }
}
Pour trier un tableau des rectangles selon leurs surfaces, la méthode de tri suivant répond aussi à nos besoins :
public static void trier(Rectangle [] rect, nbRect) { Rectangle tempo;
for(int i = 0 ; i < nbRect-1; i++) { int indMin = i;
for(int j = i+1 ; j < nbRect; j++)
if (rect[j].getSurface() < rect[indMin].getSurface() ) indMin = j;
if (indMin != i) { tempo = rect[i];
rect[i] = rect[indMin];
rect[indMin] = tempo;
} }
}
La seule différente entre ces deux méthodes est la comparaison selon les tailles ou selon les surfaces.
Peut-on écrire une seule méthode de tri ?
interface Comparable {
public abstract boolean plusPetit(Object obj);
}
Supposons aussi que les classes Personne, Rectangle, … implémentent l’interface Camparable (en réalisant au complet la méthode plusPetit dans Personne, dans Rectangle, etc . . .).
Il suffit d’écrire une seule méthode de tri comme la suivante : // comment trier un tableau des objets COMPARABLES ? public class Tri
// tri par sélection, basée sur la méthode plusPetit de l'interface Comparable { public static void trier(Comparable[] tableau, int nbElem) {
Comparable tempo;
for(int i = 0 ; i < nbElem-1; i++) { int indMin = i;
for(int j = i+1 ; j < nbElem; j++)
if (tableau[j].plusPetit(tableau[indMin])) indMin = j;
if (indMin != i) {
tempo = tableau[i];
tableau[i]=tableau[indMin];
tableau[indMin]=tempo;
} }
} }
Pour trier les personnes : Tri.trier(pers, nbPers) ; Pour trier les rectangles : Tri.trier(rect, nbRect) ; ect …..
/** Doit-on écrire plusieurs fonctions de tri pour :
* - trier des personnes selon leur taille ? * - trier des rectangles selon leur surface ? * - trier des pays selon leur capital ?
* - trier des . . . ? *
* Réponse : NON, on peut utiliser une seule méthode * Comment ?
* Pour trier, il faut COMPARER. Cependant, l'opérateur * < (est plus petit que) n'est pas défini sur les objets * On déclare une INTERFACE dont la méthode plusPetit n'est pas * "réalisée" :
*
* interface Comparable {
* boolean plusPetit(Object c);
* } *
* Chacune de ses sous-classes (exemple Personne,
* Rectangle, ...) doit s'ENGAGER à définir (réaliser) cette
* méthode *
* Dans cet exemple, on n'a pas la notion d'héritage multiple
*/
public class TestInterface {
// afficher le tableau d'objets (personnes, rectangles, ...) static void afficher(Object [] obj, String message) {
System.out.println(message);
for(int i = 0 ; i < obj.length ; i++) System.out.println(obj[i]);
System.out.println();
}
public static void main (String[] args) {
Personne2 pers[] = { new Personne2(1.75, 65.3, 'M'), new Personne2(1.62, 69.1, 'F'), new Personne2(1.89, 76.5, 'F'), new Personne2(1.45, 50.3, 'M'), new Personne2(1.77, 90.1, 'M') };
afficher(pers, "Lite des personnes avant le tri");
Tri.trier(pers, pers.length);
afficher(pers, "Lite des personnes apres le tri");
Rectangle3 rect[] = { new Rectangle3(12, 5), new Rectangle3(9, 6), new Rectangle3(60, 12), new Rectangle3(5, 12)};
afficher(rect, "Lite des rectangles avant le tri" + " selon la surface : ");
Tri.trier(rect, rect.length);
afficher(rect, "Lite des rectangles apres le tri selon" + " la surface : ");
} }
/* Exécution :
Lite des personnes avant le tri M mesure 1.75 metre et pese 65.3 kgs F mesure 1.62 metre et pese 69.1 kgs F mesure 1.89 metre et pese 76.5 kgs M mesure 1.45 metre et pese 50.3 kgs M mesure 1.77 metre et pese 90.1 kgs Lite des personnes apres le tri M mesure 1.45 metre et pese 50.3 kgs F mesure 1.62 metre et pese 69.1 kgs M mesure 1.75 metre et pese 65.3 kgs M mesure 1.77 metre et pese 90.1 kgs F mesure 1.89 metre et pese 76.5 kgs
Lite des rectangles avant le tri selon la surface :
<longueur : 12, 5, surface : 60>
<longueur : 9, 6, surface : 54>
<longueur : 60, 12, surface : 720>
<longueur : 5, 12, surface : 60>
Lite des rectangles apres selon la surface :
<longueur : 9, 6, surface : 54>
<longueur : 12, 5, surface : 60>
<longueur : 5, 12, surface : 60>
<longueur : 60, 12, surface : 720>
*/
public class Personne2 implements Comparable { private double taille, poids;
private char sexe ;
public Personne2(double t, double p, char s) { taille = t ;
poids = p;
sexe = s;
}
public double getTaille(){
return taille;
}
public char getSexe(){
return sexe;
}
public String toString() {
return sexe + " mesure " + taille + " metre et pese " + poids + " kgs";
}
// On s'engage à implémenter la méthode "plusPetit" : public boolean plusPetit(Object p) {
return this.taille < ( (Personne2) p).getTaille();
} }
public class Rectangle3 implements Comparable
{ // attributs (champs de données, membres de données) private int longueur, largeur;
public Rectangle3() { }
public Rectangle3(int lo, int largeur) { longueur = lo;
this.largeur = largeur;
}
public Rectangle3(int c) { this(c, c);
}
// méthode pour calculer et retourner le périmètre public int perimetre(){
return 2 * (longueur + largeur);
}
// méthode pour calculer et retourner la surface public int surface() {
return longueur * largeur ; }
public int getLongueur() { return longueur ; } public int getLargeur() { return largeur ; }
// On s'engage à IMPLÉMENTER la méthode "plusPetit" public public boolean plusPetit(Object r) {
return this.surface() < ( (Rectangle3) r).surface();
}
public String toString() {
return "<longueur : " + longueur + ", " + largeur + ", surface : "
+ surface() + ">";
} }
interface Comparable {
public abstract boolean plusPetit(Object obj);
}
Classes concrètes vs abstraites vs interfaces :
Création une instance avec new Oui Non Non Supporte des méthodes
abstraites Non Oui Oui
Supporte de l’héritage multiple Non Non Oui
Droits des constructeurs Oui Oui Oui
Implémentation d’une méthode nécessaire Oui si non
abstraite Non car toutes sont abstraites
Contenu Illimité Illimité public static
final et
Méthodes abstraites