Table des matières

TP Java: Les moutons

Comment utiliser à bon escient les attributs statiques et gérer la destruction des objets.

Description du problème

Nous allons travailler avec des moutons. Un mouton est blanc ou noir. Lorsqu'un mouton nait, il y a une chance sur deux pour qu'il soit blanc et une chance sur deux pour qu'il soit noir. Pour simplifier, on considère qu'après avoir créé un mouton blanc, on créera forcément un mouton noir.

L'objectif est de connaitre à un instant t le nombre total de moutons créés et le nombre total de moutons encore vivants. Un mouton peut être créé ou détruit à n'importe quel moment du programme.

Première analyse du problème

Proposition

La classe Mouton s'impose en tant que concept. L'idée est de réaliser une classe Troupeau à qui on va déléguer la gestion des moutons. En voici la première version, rien d'extraordinaire:

 +----------------------------+                 +----------------------+
 |          Mouton            |                 |      Programme       |
 +----------------------------+                 +----------------------+
 |-nom: String                |1..1           * |- nbMoutonsActuel: int|   
 |-couleur: String            |-----------------|- nbMoutonsTotal : int|
 |----------------------------|                 +----------------------+
 |+ String donneNom()         |                 |+ajouteMouton()       |
 |+ String donneCouleur()     |                 |+supprimeMouton()     |
 |+ Mouton(String c, String n)|                 |+supprimeMouton(int i)|
 |+ afficheInfos()            | //nom + couleur |+afficheStatut()      | //information sur le nombre de moutons
 +----------------------------+                 +----------------------+

Les objets Mouton seraient gérés par un tableau dynamique (ArrayList par exemple) dans la classe Programme.

Réalisez l'implémentation de ce modèle. Qu'en pensez-vous?

Critique

On est obligé d'utiliser une autre classe pour gérer les moutons (troupeau). Si on décide de créer un mouton sans passer par la classe programme, alors le nombre total de moutons devient faux, la couleur n'est pas forcement la bonne, etc.

Il faudrait arriver à contraindre encore plus la création des moutons: dès qu'un mouton est créé, n'importe où, il doit pouvoir mettre à jour nbMoutonsActuel et nbMoutonsTotal, et pouvoir décider quelle est la couleur à utiliser.

Cette solution ne répond donc pas au problème posé.

2ème analyse du problème

Proposition

L'idée est cette fois-ci d'utiliser des attributs de classe pour gérer les moutons.

Un attribut de classe concerne l'ensemble de la classe. Il est accessible et partagé par tous les objets de la classe et n'est stocké qu'une fois en mémoire.

En java, on définit un attribut ou une méthode de classe en utilisant le mot-clef “static”. En UML, on souligne l'attribut ou la méthode.

Voici donc notre nouveau modèle modifié, il ne contient plus qu'une seule classe:

 +----------------------------+
 |          Mouton            |
 +----------------------------+
 |S- nbMoutonsActuel: int     | // Variables de classe
 |S- nbMoutonsTotal : int     |
 |-nom: String                |
 |-couleur: String            |
 |----------------------------|
 |+ String donneNom()         |
 |+ String donneCouleur()     |
 |+ Mouton(String n)          |
 |+ afficheInfo()             |
 |S+ afficheStatut            | //méthode de classe
 +----------------------------+

Notez au passage que le constructeur Mouton(String n) n'a plus besoin qu'on lui passe la couleur dont il doit se servir pour créer un mouton: il peut la déduire de la parité de nbMoutonTotal.

Implémentation

public class Mouton
{
	//Attributs
	private static int nbMoutonsCrees = 0; //nombre de moutons crées depuis le lancement du programme
	private static int nbMoutons = 0; //nombre de moutons actuel
	private String couleur = "Noir";
	private String nom;
 
	//constructeur
	public Mouton()
	{
		/*
		* si le nombre de moutons est pair, le nouveau mouton est blanc
		* sinon, il est noir
		*/
		nom = "Mouton " + nbMoutonsCrees;
		if (nbMoutonsCrees % 2 ==0) // l'opérateur % donne le reste de la division euclidienne
		{
			couleur = "Blanc";
		}
 
		//j'augmente mon nombre de moutons. Les deux écritures suivantes sont équivalentes (n++ ou n= n+1)
		nbMoutons++;
		nbMoutonsCrees = nbMoutonsCrees + 1;
		afficheInfo();
	}
 
	//méthodes d'accès
	public String donneNom(){return nom;}
	public String donneCouleur(){return couleur;}
	/*
        * Pourquoi les deux méthodes d'accès suivantes doivent-elles être statiques?
        * Sauriez-vous trouver la réponse sans regarder plus bas?
        */
	public static int donneNbMoutons(){return nbMoutons;}
	public static int donneNbMoutonsCrees(){return nbMoutonsCrees;}
 
	//méthodes
        /*
        * Cette méthode doit-elle nécessairement être statique?
        * Pourquoi est-elle privée?
        */
	private static void enleveMouton(int nbM)
	{
		nbMoutons = nbMoutons - nbM;
	}
 
        // Pourquoi cette méthode doit-elle être statique?
	public static void afficheStatut()
	{
		System.out.println("Il y a maintenant " + nbMoutons + " moutons.");
		System.out.println("Nous avons créé en tout " + nbMoutonsCrees + " moutons.");
	}
 
        // Pourquoi cette méthode n'est pas statique?
	public void afficheInfo()
	{
		System.out.println("Ce mouton s'appelle " + nom + " et il est " + couleur);
	}
}

Intéressons-nous maintenant à ce qui se passe lorsqu'on supprime un mouton.

En java, tout objet hérite de la classe Object (voir la doc en ligne sur cette classe).

La méthode finalize est appellée lors de la destruction d'un objet. Donc si je veux intervenir lors de la destruction d'un objet, je dois redéfinir la méthode finalize, ajouter le code que je veux exécuter, et finir en n'oubliant pas d'appeler en dernière instruction super.finalize() pour bien détruire l'objet. C'est parti:

        /*
	* Je redéfinie la méthode finalize dans la classe Mouton pour pouvoir gérer le nombre de moutons
	* de mon application: chaque fois que la méthode est appellée, ça retire automatiquement un mouton.
	*
	* La méthode finalize de la classe Object peut générer une exception "Throwable".
	* Je ne souhaite pas gérer cette exception dans la méthode suivante, c'est pourquoi je rajoute la clause
	* "throws" + "Nom de l'exception".
	* en gros, je demande à ceux qui vont utiliser la méthode finalize de Mouton de gérer les erreurs possibles 
	* (je me débarrasse du problème dans l'immédiat ;-), tout en sachant qu'il faudra à un moment faire un bloc 
	* Try-catch quand même!).
	* Cette technique est utile pour permettre de laisser aux utilisateurs d'une méthode qu'on a créée le soin de 
	* gérer les erreurs dans leur propre programme.
	*/
	public void finalize() throws Throwable
	{
		//on enlève un mouton:
		Mouton.enleveMouton(1);
		//appel de la méthode finalize
		super.finalize();
	}

On termine maintenant par le programme principal:

	//Programme principal
	public static void main(String arguments[])
	{
		/*
                * on affiche le statut
                * aurait-on pu faire cela si afficheStatut n'était pas statique?
                * voilà un exemple qui illustre pourquoi certaines méthodes doivent être statiques et d'autres non
                */
		Mouton.afficheStatut();
 
		//on commence à créer 2-3 bestioles:
		Mouton m1 = new Mouton();
		Mouton m2 = new Mouton();
		Mouton m3 = new Mouton();
 
		// La méthode finalize peut générer une erreur, on l'encadre donc avec un bloc try, et on attrape 
	 	// l'erreur pour la gérer si elle arrive dans le bloc catch
		try
		{
			m3.finalize();
			System.out.println("Mouton détruit!");
		}
		catch (Throwable t)
		{
			System.out.println("Erreur lors de la destruction du mouton!");
			t.printStackTrace(); // pour afficher la pile d'appels
		}
		m3 = null;
		Mouton.afficheStatut();
 
		//on continue avec quelques autres moutons:
		m3 = new Mouton();
		Mouton m4 = new Mouton();
		Mouton.afficheStatut();
 
		//etc.....
 
	}
}

Pour aller plus loin

	public static final double Pi = 3.141592;
	Mouton monMouton = new Mouton();
	monMouton.finalize();
	monMouton = null;
	//Pour planter le programme...
	Mouton m5 = m4;
	try
	{
		m5.finalize();
		m5 = null;
	}
	catch(Throwable t)
	{
		System.out.println("Erreur lors de la destruction du mouton!");
		t.printStackTrace(); // pour afficher la pile d'appels	
	}
	m4.afficheInfo();
	Mouton.afficheStatut();
	m4= null;

Que se passe-t-il? Pourquoi?

Auriez-vous une solution à proposer pour éviter ce genre de problème? Est-ce possible simplement?