Aller au contenu

Découvertes des notions : final, abstract et interface

1. Modificateur final (sur une classe ou une méthode)

Une classe déclarée avec le modificateur final ne peut pas être dérivée.

/**
* Exemple de classe final non dérivée
 * @author phil
 *
 */
public final class Unique {

	private String nom;
	
	public Unique(String nom)
	{
		this.nom=nom;
	}
}

class PasUnique extends Unique
{
	// bah non ! c'est pas possible !
}

Exemple avec une méthode :

final class Programmeur extends Employe

Une méthode déclarée avec le modificateur final ne peut pas être réécrite dans une classe dérivée :

class Employe
{
	private String prenom;
	/**
	 * Méthode qui sera impossible de réécrire dans une sous-classe.
	 * Essayez
	 * @return
	 */
	final public String getPrenom()
	{
		return prenom; 
	}
}

class JeBosse extends Employe {
	
	/**
	 * Impossible ! vous aurez le message  "Cannot override the final method from Employe"
	 */
	public String getPrenom() {
		
	}
}

2. Classes abstraites

Dans une hiérarchie de classes, on peut mettre en facteur commun des attributs et des méthodes.

Ci-dessous, la classe Contour servira de classe de base à un ensemble de sous-classes du genre Cercle, Rectangle et autres formes.

Nous allons donc par la suite la rendre abstraite puisque nous n’avons aucune raison de l’instancier !

/**
* Classe générique non abstraite
*/
class Contour {
	
	protected int x , y ; // pour l'héritage
	
	public Contour(int x, int y) {
		this.x=x;
		this.y=y;
	}
	
	public void deplace( int x , int y)
	{
		// efface();	// écriture illogique car on ne sait pas quoi effacer !
		this.x = x ; 
		this.y = y ;
		// dessine();	// écriture illogique car on ne sait pas quoi dessiner !
	}
}

Selon vous, peut-on écrire les méthodes efface() et dessine() de cette manière ?

Est-il raisonnable de créer un objet Contour ? Je vous donne la réponse : NON

Voici la manière d’interdire l’instanciation d’une classe Contour avec l’utilisation du mot-clé abstract :

/**
* Classe générique devenue abstraite !
*/
public abstract class Contour {
	
	protected int x , y ; // pour l'héritage
	
	public Contour(int x, int y) {
		this.x=x;
		this.y=y;
	}
	
	public void deplace( int x , int y)
	{
		// efface();	// écriture illogique car on ne sait pas quoi effacer !
		this.x = x ; 
		this.y = y ;
		// dessine();	// écriture illogique car on ne sait pas quoi dessiner !
	}
     // voici comment on déclare des méthodes abstraites (comme dans une interface ;) )
	 public abstract void dessine();
	 public abstract void efface();
	 
}

Ci-dessus, la classe Contour on ne peut pas écrire le code des méthodes efface() et dessine().

Comment faire figurer les signatures des méthodes dans la classe Contour sans fournir de code ?

Il suffit de les déclarer abstraites avec le mot clé abstract :

abstract public void efface(); // ou public abstract void dessine();
abstract public void dessine(); // public abstract void efface();

Il faut donc hériter de la classe Contour pour utiliser deplace() et fournir le code des méthodes efface() et dessine() que je définie comme abstract. Si vous vous souvenez de l’utilisation d’une interface en java, et bien les méthodes sont déclarées sans code comme ici !

Vous pouvez utiliser les syntaxes suivantes :

//======== classe dérivée Cercle
class Cercle extends Contour {

    private int rayon;

    public Cercle(int x, int y, int rayon) {
        super(x, y); // appel du constructeur de la super classe
        this.rayon = rayon;
    }
    public void dessine() {
        // votre code pour dessiner un cercle
    }
    public void efface() {
        // votre code pour effacer un cercle
    }
}

// classe dérivée Rectangle
class Rectangle extends Contour {

    private int hauteur;
    private int largeur;

    public Rectangle(int x, int y, int hauteur, int largeur) {
        super(x, y); // appel du constructeur de la super classe
        this.hauteur = hauteur;
        this.largeur = largeur;
    }
    public void dessine() {
        // votre code pour dessiner un rectangle
    }
    public void efface() {
        /* votre code pour effacer un rectangle
    	g.setColor(Color.WHITE);
    	g.fillRect(x, y, width, height);
    	g est un objet de type Graphics qui permet de dessiner des formes sur un composant graphique
    	x, y, width, height sont les coordonnées et les dimensions du rectangle.
    	*/
    }
}

Pour résumer, une classe abstraite peut contenir :

IMPORTANT : Si on déclare une méthode abstraite dans une classe, cette classe doit être déclarée abstraite.

Quelques règles de codage pour les méthodes abstraites :

3. Interfaces

Au sens java, une interface est :

Une classe abstraite dont toutes les méthodes sont abstraites.

Elle présente un plus haut degré d’abstraction qu’une classe abstraite et permet de définir un contrat entre les classes.

On appelle généralement les méthodes de l’interface depuis d’autres classes.

Une autre classe devra écrire le code pour les méthodes définies dans l’interface.

IMPORTANT : Une interface est un type au même titre qu’une classe (Vous découvrirez ultérieurement l’utilité de ce type particulier)

Il existe plusieurs sens du terme interface :

Comme une interface constitue un type, on peut déclarer une référence du type de l’interface. Pour déclarer une Interface, on remplace le mot class par interface.

Dans une interface, toutes les méthodes sont abstraites. Pas besoin d’ajouter le mot clé abstract

Exemple :

interface Executable
{
public void execute( );
}

La classe qui fournit le code est dérivée de l’interface en utilisant le mot clef implements

La classe doit fournir le code pour les méthodes définies dans l’interface comme ceci :

class Traitement implements Executable
{
public void execute( )
{
 // Le code à exécuter
}
}

La classe qui va utiliser le code aura une méthode attendant un objet du type de l’interface.

class Gestionnaire

	public void aFaire( Executable tache )
	{
		// Appel du code écrit dans la classe Traitement
		tache.execute( ) ; 
	}

public static void main(String args[])
{

	Gestionnaire g = new Gestionnaire( );
	Traitement t = new Traitement( );
	g.aFaire( t );
}
}

La classe Gestionnaire ne connaît que l’interface Executable ! Elle ignore la classe Traitement qui est finalement du type Executable.

Exemple avec Comparable et TreeSet

Dans cet exemple, nous allons voir l’utilisation de l’interface *Comparable (java.lang.Comparable) qui permet de déterminer par programmation le classement d’une série d’objets stockés dans une collection de type TreeSet.

L’implémentation de cette interface nous oblige à redéfinir une méthode compareTo() qui sera automatiquement appelée par notre collection TreeSet lors de l’affichage des objets.

public int compareTo(Object objet)

La valeur retournée est :

Voici la classe Voiture qui implémente notre interface Comparable :

package interfaceComparableEtTreeSet;
/**
 *	Utilisation de l'interface java.lang.Comparable
 *	Qui permet de comparer deux objets en fonction
 *	de critères définis par le programmeur. 
 */
public class Voiture implements Comparable<Object>
{
	// Déclaration des attributs
	public String numero;
	public String modele;

	// Constructeur
	public  Voiture (String numero, String modele)
	{
		this.numero = numero;
		this.modele = modele;
	}
	/**
	 *	affichage des données 
	 */
	public String toString()
	{
		return "Voiture numero : " + numero + " est un modèle : " + modele ;
	}
	/*
	 	Implémenter la méthode public int compareTo(Object param)
	 	de l'interface java.lang.Comparable	et comparer les objets
	 	en effectuant la concaténation des textes modele+numero
	 */  
	public int compareTo(Object objet)
	{
		// on stocke la référence de l'objet Voiture
		Voiture objetAComparer = (Voiture)objet;
		/* on concatène modèle et numéro de l'objet en cours (this).
		   Nous allons utiliser cette chaine concaténée pour effectuer
		   notre classement
		 */
		String voitureGauche = this.modele+this.numero ;
		// on fait de même avec l'objet passé en paramètre
		String voitureDroite = objetAComparer.modele+objetAComparer.numero;
		// renvoi la position
		return voitureGauche.compareTo(voitureDroite);
	}
}

Voici la classe TreeSet qui va nous permettre de stocker les objets de type Voiture :

package interfaceComparableEtTreeSet;
import java.util.Iterator;
import java.util.TreeSet;
//importez le package ou se trouve la classe TreeSet.
/**
 * 
 * Utilisation de la classe Voiture qui implémente
 * l'interface Comparable.
 */
public class TestTreeSet
{	
	public TestTreeSet()
	{
		// Déclarez et initialisez la variable locale de type TreeSet
		TreeSet treeSet = new TreeSet();		
		// Ajoutez 2 instances de la classe Voiture à treeSet :
		//("California 2345","Megane") et ("VV 7 CF 4","XM").
		treeSet.add(new Voiture( "468 BHY 75","Peugeot" ));
		treeSet.add(new Voiture( "315 VV 78","Renault" ));
		// Affichez le contenu du treeSet
		affiche(treeSet);
		affiche("=============");

		// Ajoutez une instance de la classe Voiture : ("952 POI 95","Volvo").		
		treeSet.add(new Voiture("201 ABC 22","Audit"));
		// Affichez le contenu de treeSet
		affiche(treeSet) ;
		affiche("=============");
/* Remarques :

La voiture "201 ABC 22 est un modèle : Audit" a été placée en
première position alors que nous l'avons ajouté en dernier. Ceci
grâce à la méthode compareTo() que nous avons défini dans la classe
Voiture qui implémente l'interface Comparable.
*/
	}
	public static void main (String[] arg)
	{
		new TestTreeSet();
	}

	public void affiche(TreeSet<Voiture> t)
	{
		Iterator<Voiture> it = t.iterator();
		while( it.hasNext() )
		{
			System.out.println( it.next() ) ;
		}
	}
	public void affiche(String msg)
	{
		if( msg != null )
		{
			System.out.println( msg ) ;
		}
	}
}

Exécutez ce programme et vérifiez l’ordre d’affichage des objets Voiture dans la console !