03 Janvier 2012
Résumé
Ceci est un support de cours DAIGL (Développement d'Applications Informatiques et Génie Logiciel) à destination d'étudiants en STS IG (Section de Technicien Supérieur en Informatique de Gestion).
Il fait suite à 4 mois d'initiation à la programmation avec PHP.
Ce support est basé sur le langage Java, et mis en oeuvre sous GNU/Linux, mais peut l'être sur d'autres environnements non ouverts, non libres, comme MS-Windows, sans aucun problème particulier.
Ce document a été réalisé sous GNU/Linux avec vim , au format docbook , mis en page avec le processeur XSLT saxon développé par Michael Kay et les feuilles de styles de Norman Walsh .
Ce document est placé sous la Licence GNU Free Documentation License, Version 1.1 ou ultérieure publiée par la Free Software Foundation.
Table des matières
Les concepts de la programmation orientée objet datent des années 60 avec le langage Algol puis Simula. Le langage Smalltalk est une référence en la matière et plus récemment Eiffel (fin de année 80 début année 90).
Objectifs principaux de l'approche objet.
Produire des logiciels fiables, garants d'une certaine robustesse.
Rechercher les qualités de réutilisabilité et d'extensibilité.
Faciliter le déploiement et la maintenabilité.
C'est, en d'autres mots, répondre aux exigences du génie logiciel dont l'objectif est de produire du logiciel de qualité.
Java est un langage de programmation qui permet une certaine approche objet des solutions.
Comme tout langage de programmation, Java dispose de bibliothèques spécialisées sous forme de modules fonctionnels, comme en PHP, et de classes d'objets. Nous aurons l'occasion d'étudier ces deux concepts, car ils sont, avec le concept d'interface, centraux en programmation objet.
C'est un changement de paradigme par rapport à la programmation structurée (basée sur la notion de fonction). La plupart des langages issus de l'approche structurée ont évolué vers l'objet : C -> C++, Pascal -> Pascal Object/Delphi, VB -> VB.NET, Ada, PHP ... D'autres langages sont directement conçus dans une approche objet, après Simula dès 1966 : Eiffel, Smalltalk, Java, C#...
Java est le fruit d'un travail initié par une équipe d'ingénieurs de chez SUN Microsystems. Java s'est fortement inspiré du C++ dont il a éliminé certaines caractéristiques comme les pointeurs et limité d'autres comme l'héritage multiple.
Quelques caractéristiques remarquables de Java
Fortement typé.
Distinction forte entre les erreurs survenant à la compilation (erreur de syntaxe, de non concordance de type, de visibilité...) et les erreurs d'exécution (chargement et liaison des classes, génération de code, optimisation...).
Indépendance des plates-formes d'exécution.
Dans la mesure où celle-ci dispose d'une machine virtuelle Java (JVM)
Sûreté de fonctionnement.
Le système d'exécution Java, inclus dans la JVM, vérifie que le code n'est pas altéré avant son exécution (absence de virus et autre modification du code).
Gestion automatique de la mémoire.
Libération automatique de la mémoire réalisée par un garbage-collector (ramasse-miettes). Mais ordre de création des objets à l'initiative du développeur (opérateur new)
Orienté Objet.
Diminution du temps de développement grâce à la réutilisation de composants logiciels .
Réalisation d'interface utilisateur graphique.
Une hiérarchie de classe (Java Fundation Classes - jfc) connue sous le nom de Swing, permet de construire des IHM de qualité.
Interprété et multitâches.
Le code est d'abord compilé, traduit en Byte code. C'est ce byte code qui sera interprété par la JVM.
Le langage supporte le multithreading avec primitives de synchronisation.
Robuste.
La gestion des exceptions est intégrée au langage.
Orienté réseau et client/serveur.
Client/serveur traditionnel : Application classique, gestion de protocoles réseaux, pont d'accès aux bases de donnée (jdbc), RMI. . .
Client/serveur Web : Applet côté client et Servlet côté serveur.
Déploiement facilité.
Les classes peuvent être assemblées en unités d'organisation physique et logique (package). Présence d'une API d'internationalisation.
Gratuit et "participatif".
De grandes entreprises participent avec SUN à la réussite de Java, et croient à l'avenir de ce langage : IBM, ORACLE, BORLAND...
Dispose d'une grosse bibliothèque de base (plus 1600 classes et interfaces pour le JDK 1.2.2).
Les principales Versions du jdk
jdk 1.0 : première version, orienté développement Web (applet)
jdk 1.1 : version plus costaud, destiné aussi aux applications d'entreprises.
jdk 1.2 : améliore et enrichie la 1.1 : collections, interface utilisateur swing etc.
jdk 1.3 : de nouvelles performances et de nouvelles classes (3D . . . )
jdk 1.4 : amélioration de la vitesse d'exécution, de nouvelles fonctionnalités (XML, String...)
jdk 1.5 et sup : collections typées, encapsulation automatique des types primitifs...
Pour développer en java, vous devez disposer d'un JDK ( Java Developement Kit ), un kit de développement, ainsi qu'une documentation associée et un éditeur.
Pour installer un JDK sur votre machine, vous pouvez vous référer ici http://www.linux-france.org/prj/edu/archinet/DA/install-java. Vous y trouverez une procédure détaillée, contenant en fin de document un lien vers une ressource d'installation de java pour d'autres systèmes (Mac, Windows).
Parmi les sous-répertoires du jdk, on trouve
bin : Fichiers exécutables dépendants de la plate-forme (java, javac . . . )
demo : Une collection de programmes de démonstration (jfc et applet)
jre : Java Runtime Environment. Un environnement d'exécution pour la plateforme.
lib : Diverses librairies.
A la racine de l'installation se trouve le fichier src.jar qui contient les sources de la librairie. Il peut être décompressé dans un sous répertoire à créer pour l'occasion (par exemple <rep du jdk>/src).
Le point d'entrée d'un programme Java standard est une fonction, nommée main, à l'entête prédéfinie :
/*** fonction appelée automatiquement
* au lancement du programme.
* @param args la liste des arguments reçus en ligne de commande
*/
static public void main(String[] args)
Elle est appelée par le système, après chargement, suite à son lancement.
En programmation objet, toute fonction est forcément placée à l'intérieur d'une structure appelée classe .
Un programme Java est donc composé d'au moins une classe, et au plus... il n'y a pas de limite théorique ! Ainsi il n'est pas rare de voir des projets ayant plus d'un millier de classes !
Avant tout chose, créons un répertoire. Il est fortement conseillé de créer un répertoire par projet . Un projet regroupe l'ensemble des fichiers nécessaires à l'application.
[java]$ mkdir tp1 [java]$ cd tp1 [tp1]$
Bon, créons notre classe. Comment allons nous l'appeler ? Nous avons pris l'habitude en PHP, de bien nommer nos identifiants, nous ferons bien entendu de même avec Java (ou tout autre langage).
Nous enregistrons notre classe dans un fichier nommé AppTp1.java .
/*** fichier : AppTp1.java
* date : 02-novembre-2003
*/
public class AppTp1 {
/**
* fonction appelée automatiquement
* au lancement du programme.
* @param args la liste des arguments
* reçus en ligne de commande
*/
static public void main(String[] args) {
// ne fait rien
}
}
Vous constaterez que, comme PHP, les structures de blocs sont délimitées par les symboles { et } , et le commentaires par // bout de ligne commenté , pour une ligne, ou /* plusieurs lignes */ .
Il existe une convention très intéressante concernant les commentaires : Ceux multilignes de la forme /** ... */ , observez les deux étoiles du début . S'ils sont placés avant certaines parties (classe, attributs, méthodes), ils seront perçus comme des commentaires javadoc , qui servent de base de données à la construction automatique de la documentation des interfaces (API) de votre code. Vous n'avez pas à vous en inquiéter pour le moment.
Vous devez faire attention à deux choses ici : 1/ l'extension d'un programme source java est .java , en minuscule. 2/ le nom du fichier est identique au nom de la classe (lorsque celle-ci est déclarée publique).
![]() | Déclaration des variables |
|---|---|
Toute variable utilisée au sein d'un programme Java doit être déclarée avant toute utilisation. Déclarer une variable, c'est lui associer définitivement un type. |
Tel quel, le programme n'est pas exécutable.
Il doit être compilé , c'est à dire traduit en un autre langage, appelé byte code pour être compris par l'interpréteur. Au cours de cette traduction, de nombreuses et précieuses vérifications sont réalisées. Si des erreurs sont rencontrées par le compilateur (le traducteur), il ne générera pas la version compilée, charge alors au développeur de corriger sa copie. Exemple de compilation avec javac :
[tp1]$ ll total 4 -rw-rw-r-- 1 kpu kpu 94 nov 1 20:46 AppTp1.java [tp1]$ [tp1]$ javac AppTp1.java [tp1]$ [tp1]$ ll -tr total 8 -rw-rw-r-- 1 kpu kpu 94 nov 1 20:46 AppTp1.java -rw-rw-r-- 1 kpu kpu 257 nov 1 20:47 AppTp1.class [tp1]$
La compilation a bien fonctionné. Un nouveau fichier est apparu, avec une extension .class .
Nous utiliserons la commande java (comme la commande précédente, mais sans le c pour compilateur). En prenant bien soin de ne pas mentionner l'extension ! Exemple :
[tp1]$ java AppTp1 [tp1]$
Ainsi, il ne se passe rien ! Que s'est-il passé au juste ?
La commande java a fait en sorte de charger la classe AppTp1.class en mémoire, puis la méthode main est appelée. Comme elle ne comporte aucune instruction, son exécution se termine et le programme s'arrête.
Modifions notre programme afin qu'il affiche un message à l'écran :
public class AppTp1 {
static public void main(String[] arg) {
System.out.println ("Hello, ce n'est pas encore tout à fait");
System.out.println ("de la programmation objet !");
}
}
Pour réaliser l'affichage, nous utilisons l'instruction System.out.println(...) . Au regard de la convention de nommage, et avec notre expérience de la programmation en PHP, nous pouvons affirmer sans erreur que :
System est une classe; le premier caractère est une majuscule, la suite en minuscule.
out est un identificateur qui commence par une minuscule et s'utilise sans parenthèse : c'est donc une variable (autres appellations courantes attribut , champ ou membre). Ici out est un attribut publique détenu par la classe System , il n'y a pas de variables globales en programmation objet .
println() débute par une minuscule et s'utilise avec des parenthèses, c'est donc une fonction (mais on dit méthode ). Il n'y a pas de fonctions globales en programmation objet .
Équivalent PHP : print() ou echo() .
Après avoir sauvegardé les modifications, nous devons recompiler le programme pour, de nouveau, le tester :
[tp1]$ vim AppTp1.java # ici nous avons utiliser l'éditeur vim [tp1]$ javac AppTp1.java # compilation [tp1]$ java AppTp1 # exécution Hello, ce n'est pas encore tout à fait de la programmation objet ! [tp1]$
Voilà, le tour est joué. Mais pourquoi avoir mentionné que ce n'est pas tout à fait de la programmation objet ?
Simplement parce que (pratiquement) aucun objet n'a été crée ! Et pourtant il y a un lien extrêmement étroit entre une classe et un objet.
Prenons un exemple en PHP. Ce programme, qui ne devrait pas vous poser de difficulté, il s'attend à recevoir 2 arguments en ligne de commande, à savoir un nom et un prénom.
<?php
require_once("utilitaires.php");
if (argc() == 3) {
$nom = strtoupper(argv(1));
$prenom = argv(2);
echo "Bonjour " . $prenom . " ". $nom. ".\n";
}
else {
echo "Bonjour inconnu(e).\n";
}
?>
Exemple d'exécution :
$ php -q test.php Bonjour inconnu(e).
Le même en Java :
public class Bonjour {
static public void main(String[] arg) {
if (arg.length == 2) {
String nom;
nom = arg[0].toUpperCase();
String prenom = arg[1];
System.out.println("Bonjour " + prenom + " "+ nom +".");
}
else {
System.out.println ("Bonjour inconnu(e).");
}
}
}
Exemple d'exploitation (le code source a été sauvegardé dans un fichier nommé Bonjour.java :
$ javac Bonjour.java $ java Bonjour Bonjour inconnu(e).
Les variables sont explicitement typées. Le typage d'une variable est réalisé lors de sa déclaration et non plus lors de son affectation comme en PHP. Exemple :
... String nom; ...
Conséquence, vous devez fournir à une variable une valeur cohérente avec son type, dans le cas contraire le compilateur refusera toute traduction. Exemple :
... String nom; nom = toto; // ERREUR : si toto n'est pas une variable de type String nom = 'toto'; // ERREUR : le symbole ' est réservé au type char (caractère) nom = "toto"; // OK ! nom = 12; // ERREUR : 12 est une valeur numérique (un entier) nom = 12.0; // ERREUR : 12.0 est un réel (double) nom = false; // ERREUR : false et nom = true; // true sont (les seules valeurs) de type boolean nom = System.out; // ERREUR : out est un Objet de type PrintStream ...
Les fonctions sont préfixées par un identificateur suivi d'un point. Cet identificateur peut être soit le nom d'une classe soit une référence à un objet. Exemple de fonction 'static' :
... System.exit(0); // Stop l'exécution de la machine virtuelle java (JVM) ...
D'un point de vue PHP, on peut dire que exit est une fonction de la bibliothèque System.
D'un point de vue Java, exit est une fonction utilitaire (static) de la classe System.
Ces deux points de vue sont strictement équivalents.
Toute fois, les fonctions utilitaires sont marginales en Java (et dans tout langage objet). Dans la majorité des cas nous avons à faire à des fonctions membres d'un objet (instance d'une classe). Exemple :
... String nom = arg[0].toUpperCase(); ...
arg[0] référence un objet de type String en mémoire. Cet objet dispose d'un état (un ensemble ordonné de caractères) et propose certains services sous la forme de fonctions membres appelées plus généralement méthode.
Avant d'aborder plus avant les notions d'interface, de classe et d'objet, nous allons nous familiariser avec la séquence : édition, compilation et exécution.
Au lancement d'un programme java, la fonction main de la classe nommée, reçoit une liste d'arguments. Chaque argument est de type chaîne de caractères. Dans le programme suivant, la liste d'arguments est représentée par la variable args, cette liste peut éventuellement être vide.
public class Application {
static public void main(String [] args) {
System.out.println(args);
}
}
Une exécution donne :
[kpu@kpu tp]$ java Application java.lang.String;@1a16869
args est une variable qui référence un objet de type "Tableau de chaînes de caractères". C'est l'adresse de cet objet qui est affichée ici (sans intérêt pour nous). Voici deux des caractéristiques d'un objet de type Tableau (array) :
length : un attribut public de l'objet référencé par args.
=> Généralement les attributs d'un objet ne sont pas publics mais privés.
=> Attention : écrire args.length et non args.length() (pas une méthode)
Exemple d'utilisation de length :
public class Application {
static public void main(String [] args) {
System.out.println("Je reçois " + args.length + " argument(s)");
}
}
Exemple d'exécutions :
[kpu@c419-12 tp]$ javac Application.java [kpu@c419-12 tp]$ java Application Je reçois 0 argument(s) [kpu@c419-12 tp]$ java Application a Je reçois 1 argument(s) [kpu@c419-12 tp]$ java Application aaa Je reçois 1 argument(s) [kpu@c419-12 tp]$ java Application aaa a Je reçois 2 argument(s)
[ ] : les crochets servent à atteindre la valeur d'un élément du tableau.
Entre les crochets, on donne le rang (indice) de l'élément dans le tableau.
=> Attention, l'indice du premier élément est 0 (zéro) et non 1.
Exemple d'utilisation de [ ] :
public class Application {
static public void main(String [] args) {
System.out.println("Le premier argument est " + args[0]);
}
}
Exemple d'exécution :
[kpu@c419-12 tp]$ java Application aaa Le premier argument est aaa
Nous souhaitons afficher un message particulier lorsque qu'il n'y a pas d'argument. Pour cela nous testons la valeur de length dans une expression booléenne. Nous utilisons l'opérateur arithmétique de comparaison ==
public class Application {
static public void main(String [] args) {
if (args.length == 0 ) {
System.out.println("Aucun argument.");
}
else {
System.out.println("Je reçois " + args.length + " argument(s)");
}
}
}
Exemple d'exécution :
[kpu@c419-12 tp]$ java Application Aucun argument. [kpu@c419-12 tp]$ java Application sd Je reçois 1 argument(s) [kpu@c419-12 tp]$ java Application sd zz Je reçois 2 argument(s)
La structure de décision est la même qu'en PHP.
![]() | Structure conditionnelle |
|---|---|
La forme générale de cette structure de contrôle de flux est :
if (expr. bool) {
// instruction(s) si l'expr. bool est true
}
[ else {
// instruction(s) si l'expr. bool est false
} ]
|
Retrouver les 4 erreurs (syntaxe et non respect des conventions) qui se cachent dans le programme suivant :
public class application {
public void main(String [] args) {
if (args.length > 1 ) {
String $s1 = args(0);
s2 = args[1];
System.out.print("Les 2 premiers arguments sont : ");
System.out.println($s1 + " et " + s2);
}
}
}
Transformer le programme Application.java de sorte qu'il affiche le mot argument sans s lorsqu'il reçoit un seul argument, et avec un s lorsqu'il en reçoit plusieurs.
Afficher la valeur des arguments.
Rappel : Le premier argument est : args[0], le second args[1], etc.
On se contentera des 3 premiers arguments au maximum (s'ils existent). S'il y en a plus, le programme affichera des points de suspension (...) après le troisième.
Modifier le programme de sorte que, s'il reçoit deux arguments dont la valeur du premier est soit "Mr" soit "Me", le programme affichera une formule du genre : "Bonjour madame machin" ou "Bonjour monsieur chose", en fonction du deuxième argument.
Modifier le programme afin qu'il affiche les initiales de la personne.
Vous trouverez ci-dessous, quelques méthodes offertes par tout objet de type String, qui pourrait bien vous être utiles.
Afficher l'ensemble de TOUS les arguments reçus en ligne de commande. Pour cela vous utiliserez une boucle for, dont la syntaxe est identique à celle de PHP.
Retrouver les 4 erreurs (syntaxe et non respect des conventions) qui se cachent dans le programme suivant :
public class Aapplication { // les noms des classes commencent par une Majuscule static public void main(String [] args) { if (args.length > 1 ) { String $s1 = args[0]; String s2 = args[1]; System.out.print("Les 2 premiers arguments sont : "); System.out.println($s1 + " et " + s2); } } }
Transformer le programme Application.java de sorte qu'il affiche le mot argument sans s lorsqu'il reçoit un seul argument, et avec un s lorsqu'il en reçoit plusieurs.
Afficher la valeur des arguments.
Rappel : Le premier argument est : args[0], le second args[1], etc.
On se contentera des 3 premiers arguments au maximum (s'ils existent). S'il y en a plus, le programme affichera des points de suspension (...) après le troisième.
public class AfficheArg {
static public void main(String[] args) {
if (args.length >=1) System.out.println("arg0 = "+args[0]);
if (args.length >=2) System.out.println("arg1 = "+args[1]);
if (args.length >=3) System.out.println("arg2 = "+args[2]);
if (args.length >=4) System.out.println("...");
}
}
Modifier le programme de sorte que, s'il reçoit deux arguments dont la valeur du premier est soit "Mr" soit "Me", le programm affichera une formule du genre : "Bonjour madame machin" ou "Bonjour monsieur chose", en fonction du deuxième argument.
public class AfficheArg {
static public void main(String[] args) {
String pol="";
if (args.length ==3) {
if (args[0].equals("Mr") pol="monsieur ";
if (args[0].equals("Me") pol="madame ";
System.out.println("Bonjour " + pol + args[1] + " " + args[2]);
}
}
Modifier le programme afin qu'il affiche les initiales de la personne.
public class Initiales {
static public void main(String[] args) {
if (args.length == 2 ) {
String nom = args[0];
String prenom = args[1];
System.out.print("Les initiales sont : ");
System.out.println(nom.substring(0,1).toUpperCase()
+ prenom.substring(0,1).toUpperCase());
}
}
}
Afficher l'ensemble de TOUS les arguments reçus en ligne de commande. Pour cela vous utiliserez une boucle for, dont la syntaxe est identique à celle de PHP.
public class AfficheArg {
static public void main(String[] args) {
int i = 0;
for (i=0; i<args.length; i++) {
System.out.println("arg "+i+" = "+args[i]);
}
}
}
En Java, une variable est soit de type primitif soit de type référence .
De ce fait le langage Java ne peut être qualifié de langage "purement objet", bien que cette différence s'estompe avec un jdk >= 5.O.
Java traite les variables de type primitif selon une sémantique par valeur et les variables de type objet (classe, interface, tableau - array -) selon une sémantique par référence (ce n'est pas la valeur de la variable qui nous intéresse, mais l'objet référencé). Exemple :
Le fragment de code suivant n'EST PAS VALIDE :
... int x = 2; System.out.println(x .toString() ); // INTERDIT !! (avec jdk < 1.5.0) // x ne référence pas un objet. // int n'est pas une classe d'objet, mais un type primitif ...
Java propose 9 types primitifs, leur nom est entièrement en minuscule ( int, boolean, char, byte, float, double, long, short, void) :
Les types primitifs ne sont pas des classes.
Chaque type primitif a son correspondant en type Objet. Alors pourquoi pas le "tout objet" ?
Les créateurs du langage Java ont souhaité conserver l'utilisation des types primitifs dans un but d'efficacité. Un point de vue remis partiellement en cause avec la version 1.5.0 du jdk...
Point de vue plus technique (peut être sauté) : Les "objets" de type primitif sont automatiquement créés sur la pile, et leur valeur confondue avec leur identificateur (sémantique par valeur). Toutes les autres entités sont de véritables objets, créés sur le tas (et non sur la pile) accessible via un ou plusieurs identificateurs; des variables de type référence (sémantique par référence).
La colonne la plus à droite du tableau des types primitifs donne le nom des classe associées à chaque type primitif. Ces classes sont porteuses d'informations telles que les limites du domaine de définition, mais aussi proposent des fonctions utilitaires de conversion relatives au type élémentaire dont elle ont la charge (et qu'elle peuvent représenter en les enveloppant, c'est pour cela qu'on les qualifie de classes wrapper).
Conversion de type String vers un
type primitif :
... String s1 = "123"; String s2 = ".23"; int x1 = Integer.parseInt(s1); double x2 = Double.parseDouble(s2); float x3 = Float.parseFloat(s2); float x4 = Float.valueOf(s2); // même effet ...
... une méthode de la forme typePrimitif ClasseWrapper.parseTypePrimitif(String s) .
diverses autres fonctions de conversion, comme par exemple la classe Integer qui propose par exemple les méthodes static String toBinaryString(int i) , static String toHexString(int i) , etc. voir l'aide en ligne.
Une entreprise de location de voitures loue des véhicules de tourisme au conditions suivantes :
Forfait journalier : 50 €, majoré de 10% pour les véhicules de plus de 10 chevaux.
Le prix du km est de 0.10 €.
Concevoir ce programme en java. Les données sont reçues en ligne de commande (via la fonction main), dans un tableau de String : elles devront donc être converties en type numérique.
Une solution : Loca.java
Nous changeons de registre ici, et présentons quelques moyens pratiques d'interagir avec l'utilisateur d'un programme écrit en java.
On utilise l'instruction showMessageDialog() .
Attention, cette méthode n'EST PAS UNE MÉTHODE D'INSTANCE (elle est déclarée static ). C'est une fonction disponible dans le module (la classe) JOptionPane . On préfixera le nom de cette méthode par le nom de la classe dans laquelle elle est définie , soit ici JOptionPane , qui se trouve dans le package javax.swing :
import javax.swing.*;
public class Test {
static public void main(String[] args) {
Object obj = "Hello !"; // une chaîne de caractères est un objet.
javax.swing.JOptionPane.showMessageDialog(null, obj);
}
}
Ce qui donne :
Nous utiliserons la méthode showInputDialog de la classe JOptionPane .
L'exemple ci-dessous, demande un nom à l'utilisateur. Si ce dernier clique sur Annuler , la fonction rendra la valeur null , mais si l'utilisateur ne saisit aucune valeur et clique sur OK la fonction rendra une chaîne vide. Dans le cas où l'utilisateur fournit une valeur, elle est affectée au nom
de l'étudiant.
import javax.swing.*;
public class Test {
static public void main(String[] args) {
String nom = JOptionPane.showInputDialog("Entrez un nom");
if (nom == null) {
nom = "";
}
if (! "".equals(nom) ) {
javax.swing.JOptionPane.showMessageDialog(null, "Bonjour " + nom);
}
}
}
Vous êtes bien entendu invité à tester ce programme.
Plus d'info sur http://www.linux-france.org/prj/edu/archinet/DA/
![]() | Test d'égalité |
|---|---|
Pour comparer deux valeurs primitives on utilise (comme en PHP) l'opérateur |
En absence de contexte graphique, nous pouvons utiliser l'entrée standard avec un echo à l'écran. C'est cette technique que nous utilisons ci-après.
On utilise l'instruction System.out.println() ou System.out.print() - même fonction mais ne provoque pas de retour chariot. Exemple :
public class Application {
static public void main(String[] args) {
System.out.print("He");
System.out.println("llo !");
}
}//Application
Ce qui donne :
$ java Application Hello ! $
Depuis Java 5.0, c'est un peu plus facile. En effet la classe Scanner (de la librairie java.util) est capable de prendre facilement en charge les informations en provenance de l'entrée standard.
Voici un programme qui demande des valeurs de différents types à l'utilisateur :
import java.util.Scanner;
public class Test {
public static void main(String[] args) {
Scanner clavier = new Scanner(System.in);
System.out.println("Entrez votre nom : ");
String nom = clavier.next();
System.out.println("Entrez votre age : ");
int age = clavier.nextInt();
System.out.println("Entrez votre moyenne : ");
float moyenne = clavier.nextFloat();
System.out.println("Vos caractéristiques sont : \n");
System.out.println("Nom : " + nom);
System.out.println("Age : " + age);
System.out.println("Moyenne : " + moyenne);
}
}
Ce qui donne à l'éxécution :
Entrez votre nom : toto Entrez votre age : 2 Entrez votre moyenne : 12,5 Vos caractéristiques sont : Nom : toot Age : 2 Moyenne : 12.5
On appelle palindrome un mot qui se lit de la même façon de gauche à droite et de droite à gauche. Par exemple « elle », « radar » sont des palindromes.
Concevoir un programme en interaction avec l'utilisateur, fidèle au scénario suivant :
1. Le programme demande un mot à l'utilisateur. 2. Tant Que ce mot est non vide le programme 3. Informe l'utilisateur de la qualité palindrome ou non du mot. 4. Redemande un mot à l'utilisateur. 5. Fin TQ.
Deux nombres sont dits amis si la somme des chiffres les composant sont identiques.
Concevoir un programme qui demande deux nombres à l'utilisateur (en fait des chaînes de caractères) et l'informe s'ils sont amis ou non.
Lister les ensembles de nombres amis compris entre 1 et 20, puis entre 1 et 100.
Coup de pouce : les ensembles seront stockés dans un tableau de chaînes de caractères dont la déclaration est : String[] amis = new String[20];
Les nombres amis d'un nombre n seront accessibles à l'indice n dans le tableau amis. Par exemple les amis de 3 sont dans amis[3] soit "3, 12, 21, 30"
Pour terminer, vous afficherez, sans redondance, les ensembles d'amis.
Concevoir un programme qui reçoit un ensemble de notes dont la valeur est soit dans l'intervalle [0..20] soit 'abs'.
Exemple : 12 8 2 20 15 abs 13 0
Le programme calcule la moyenne des notes, sans prendre en compte les abs
moyenne (12 8 2 20 15 abs 13 0) => (12+8+2+20+13) / 5
2 cas de figure à programmer : les données sont reçues en argument (et réceptionnées par le main dans
un tableau de String) ou reçues dans une seule String qu'il faudra décomposer - voir la méthode split de String.
=> Dans un second temps, le programme affichera un message d'avertissement en cas d'impossibilité de déterminer la moyenne (au lieu de boguer).
Un objet est caractérisé par un ensemble d'opérations (méthodes) et d'attributs, retenus pour leur pertinence par rapport au domaine étudié.
Un attribut est caractérisé par un type, un nom et une valeur. Une méthode par un type, un nom et des instructions. Par exemple :
Etudiant :
// les attributs
(entier, age, 18)
(réel, taille, 1.75)
(string, nom, "Dupond")
(booléen, stage_réalisé, faux)
(réel, moyenne, 12)
...
// les méthodes
(booléen, passage2annee, { retourner (stage_ok et moyenne > 10) })
...
La valeur que peut prendre un attribut, une méthode, est fonction de son type. Par exemple l'opération passage2annee retournera soit false soit true.
On appelle état d'un objet, l'ensemble des valeurs de ses attributs à un instant t.
A un instant donné, un objet étudiant aura comme état par exemple {age=18, taille=1.8, nom="Toto", stage_realise=true, ...}
Nous avons vu qu'une classe est une sorte de module pouvant comportée des fonctions utilitaires (marquées static).
Une classe sert aussi, et c'est en fait son premier rôle, à représenter les caractéristiques types d'un objet. Par exemple en Java, la classe Etudiant se représente ainsi.
public class Etudiant {
/**** parties cachées ****/
// attributs
private String nom;
private String prenom;
private String login;
private boolean stage_realise;
/**** parties publiques ****/
// constructeur
/**
* Création d'un étudiant
* avec Anonymous comme valeur du nom par défaut.
*/
public Etudiant() {
this.nom="Anonymous";
}
// méthodes
/**
* Représentation texte de l'objet
*/
public String toString() {
String res = "*** Etudiant ***";
res = res + "\nNom : " + this.getNom();
res = res + "\nPrenom : " + this.getPrenom();
res = res + "\nLogin : " + this.getLogin();
res = res + "\nStage : ";
res = res + ((stage_realise) ? "Réalisé" : "Non réalisé");
return res;
}
/**
* @return String le login
*/
public String getLogin() {
return this.login;
}
/**
* @param unLogin le nouveau login
*/
public void setLogin(String unLogin) {
this.login = unLogin;
}
/**
* @return String le nom
*/
public String getNom() {
return this.nom;
}
/**
* @param unPrenom le nouveau prenom
*/
public void setPrenom(String unPrenom) {
this.prenom = unPrenom;
}
/**
* @return String le prenom
*/
public String getPrenom() {
return this.prenom;
}
/**
* @param unNom le nouveau nom
*/
public void setNom(String unNom) {
this.nom = unNom;
}
}
La classe décrit les caractéristiques types des objets. C'est une sorte de moule servant à la fabrication (via l'opérateur new) des objets.
Le mot clé this désigne l'objet en cours d'exécution (création ou utilisation).
Nous allons compiler la classe Etudiant :
$ javac Etudiant.java ll total 8 -rw-rw-r-- 1 user user 1205 jan 17 09:28 Etudiant.class -rw-rw-r-- 1 user user 1303 jan 17 09:27 Etudiant.java $
La version compilée (Etudiant.class) est maintenant disponible pour utilisation.
![]() | Comment utiliser la classe Etudiant ? |
|---|---|
Il existe au moins deux façons de mettre en oeuvre le concept objet : par délégation (utilisation d'objets) et par héritage (spécialisation de classes). |
La délégation est LA façon la plus naturelle d'utiliser des objets.
Un programme délégue à un objet (ou à plusieurs) certaines responsabilités.
L'utilisateur d'un objet ne voit que les caractéristiques publiques (public) de l'objet. On lui fournit la version compilée et une documentation des interfaces (API). L'outil javadoc permet de générer une telle documentation. Exemple d'utilisation
$ javadoc Etudiant.java -d docs/ $ cd docs/ $ firefox index.html &
Ce qui donne :
Un lien vers ce fichier : documentation API Etudiant
Un objet informatique est comme un objet du "monde réel" ; il naît, vit et meurt.
Naît sous l'impulsion d'une instruction programmée avec l'opérateur new et un constructeur.
Vit, il stocke et échange des informations, rend des services, répond à des messages.
Meurt, lorsqu'il ne sert plus à rien (univers impitoyable).
Son espace est la mémoire informatique (illustrée par le film Matrix)
Son temps peut être bref (quelques milli-secondes) ou très long, dépassant celui de ses concepteurs (comme dans IA, le film)
Exemple de cycle de vie d'un objet Etudiant dans un programme Java :
public class AfficheEtudiant {
static public void main(String[] args) {
int nbarg = args.length;
// création d'un objet Etudiant
Etudiant e = new Etudiant();
// utilisation de l'objet créé
if (nbarg == 3){
// valorisation de certains de ses attributs
e.setNom(args[0]);
e.setPrenom(args[1]);
e.setLogin(args[2]);
}
System.out.println(e.toString());
// fin de son espace d'utilisation, l'objet va disparaître
}// main
}//class
L'objet est créé avec new, et le constructeur sans argument Etudiant() est appelé à ce moment là.
Avant de disparaître un état est obtenu (méthode toString()), puis affiché.
Remarque => Une application orientée objet est composée, pour l'essentiel, d'objets collaborant, comme dans la vie réelle. Par exemple, lorsque je démarre titine, ma voiture, elle (titine est un objet, instance de Voiture, démarrer est une méthode de la classe Voiture) communique avec le démarreur (un autre objet, instance de la classe Démarreur) qui, à son tour envoie des messages au carburateur (instance de Carburateur) etc.
L'utilisateur d'un objet ne connaît généralement que les constructeurs et méthodes marqués public.
Pour utiliser un objet, il faut disposer de sa classe compilée.
Pour créer un objet, on utilise l'opérateur new suivi d'un des constructeurs de sa classe.
Un fois construit, le programme peut faire appel à ses services en appelant certaines de ses méthodes. Par exemple, interroger (get) ou modifier (set) le login d'un objet Etudiant, etc.
Les attributs d'un objet sont d'ordinaire inaccessibles à ses utilisateurs. Ces derniers doivent passer par ce que l'on appelle des getter/setter - des accesseurs en lecture (get) et écriture (set) (parfois appelés interrogateur/modificateurs).
Java est un langage fortement typé : Toute variable doit être déclarée. On ne peut déclarer deux fois la même variable dans une fonction, même si elles diffèrent par leur type.
Les objets sont connus, accessibles, via leurs référents (des variables).
Un objet peut avoir 0 à n référents. "La fille de ma soeur", "ma nièce" (2 référents) désignent la même personne (le même objet). Exemple :
// déclaration d'un variable nommée e
// et création d'un objet Etudiant
Etudiant e = new Etudiant();
// déclaration d'un variable nommée le_meme
// qui référence le même objet que e
Etudiant le_meme = e;
System.out.println(le_meme.getNom());
// Affiche Anonymous
e.setNom("Toto");
System.out.println(le_meme.getNom());
// Affiche Toto
Un référent référence 0 ou 1 objet : "la voiture que je vais acheter" désigne une voiture (une seule) que je ne connais pas encore (aucune instance). Exemple :
// déclaration d'une variable référence
Etudiant e = null;
// e ne référence rien, sa valeur est : null
System.out.println(e.getNom());
// IMPOSSIBLE, e ne référence aucun objet !
e = new Etudiant();
// e référence maintenant un objet en mémoire
System.out.println(e.getNom());
// affiche Anonymous
Une entreprise spécialisée dans la vente de jouets souhaite informatiser la gestion de ses produits. Un jouet est caractérisé par un nom, une matière principale, un prix et une couleur.
Le type de public auquel est destiné un jouet est indiqué par une couleur. Par exemple, bleu pour les bébés, vert pour les enfants (moins de 12 ans), rouge pour les adolescents et noir pour les adultes.
On souhaite construire un programme qui teste la classe Jouet dont voici les spécifications :
Nous vous fournissons la version compilée de la classe Jouet : jouet.jar (nul besoin d'avoir le code source). A partir d'un projet Eclipse, créer un dossier ordinaire (pas un dossier "source") nommé lib et y placer la bibliothèque "jouet.jar" (qui contient Jouet.class). Puis, ajouter ce fichier (jouet.jar) au chemin des classes du projet (clic droit sur jouet.jar, Add To Build Path)
Concevoir un programme qui crée deux objets de la classe Jouet.
Le premier jouet a pour nom "Poupée Barbie" à destination des enfants, coûtant 15 euro, en matière plastique.
Le second jouet a pour nom "Échec lumineux" à destination des adultes, coûtant 45 euro, en verre.
Réaliser un menu (prévoir une boucle) permettant à l'utilisateur de :
Afficher l'état de chacun de ces objets (nom, prix, matière et public concerné) en utilisant leurs accesseurs.
Augmenter/Diminuer de x% le prix de chacun des objets. La valeur du pourcentage est fournie par l'utilisateur.
On prendra soin de convertir la valeur saisie par l'utilisateur dans le bon type (voir exemple de conversions).
Intervertir la couleur des deux jouets : la couleur du premier objet (Barbie) est affectée à la couleur du second (échec) en utilisant leurs accesseurs, et inversement.
Quitter
Exploitation des arguments en ligne de commande.
Modifier votre programme de sorte que s'il reçoit un nombre suffisant d'arguments, il créé un des deux objets (ou les deux) de type Jouet.
Voici le code source de la classe Jouet : Jouet.java.
On vous demande
D'ajouter, juste après les déclarations des attributs, un constructeur par défaut (c'est un constructeur sans argument), le voici :
public Jouet ( ){
// ne fait rien
}
Créer un des deux objets à l'aide de ce constructeur, et afficher son état, comme précédemment.
Une solution Jouet.java, TpJouet.java
Tableau 2. Déterminez les valeurs par défaut allouées aux attributs par Java
| Type | Valeur par défaut |
|---|---|
| String | |
| float |
Ajouter à la classe Jouet une méthode toString() ayant la même signature (même interface) que celle dont dispose la classe Etudiant présentée plus haut dans ce document.
Tester cette méthode dans votre programme.
Une solution _Jouet.java (attention il faudra sauvegarder ce fichier sous le nom de Jouet.java), Tp2Jouet.java
Java propose deux façons de manipuler des objets : par des tableaux (array) ou par des collections (Liste, Dictionnaire, ...).
Caractéristiques principales
Sa capacité est fixée lors de la construction, et ne peut plus être modifiée.
La capacité est le nombre maximal d'éléments que peut contenir un tableau.
Tout tableau dispose d'une propriété length qui est la valeur de sa capacité.
Le type des éléments d'un tableau est fixé lors de sa déclaration.
L'accès à un élément d'un tableau se réalise en utilisant une paire de crochets et un indice qui est sa position dans le tableau.
Utilisation :
Tableau 3. Utilisation d'un tableau
| Désignation/Remarques | Syntaxe | Exemple |
|---|---|---|
Déclaration Par convention, le nom d'une variable de type tableau (ou collection) est au pluriel. Le type des éléments d'un tableau peut être un type primitif ou un type référence (objet). | Type [] variable; |
int [] desPoints; float [] desNotes; String[] desNoms; Etudiant [] desEtudiants; |
Construction d'un objet tableau avec les valeurs par défaut Les éléments d'un tableau sont automatiquement initialisés à leur valeur par défaut (zéro pour les types primitifs numériques, false pour les booléens, null pour les types référence). | variable = new Type[capacité]; |
desPoints = new int[4]; desNotes = new float[40]; desNoms = new String[5]; desEtudiants = new Etudiant[40]; |
Construction d'un objet tableau avec des valeurs Vous remarquerez que dans le cas d'une initialisation donnée, la capacité n'est pas directement renseignée. | variable = new Type[]{ ensemble de valeurs }; |
int [] desPoints = new int[]{-1,-1,-1,-1}; // création d'un tableau de 4 éléments
String [] desNoms = new String[] {"toto", "titi"}; // desNoms.length==2
Jouet [] desJouets = new Jouet[] { new Jouet() }; // desJouets.length==1
|
Remarques
Attention, un erreur courante consiste à croire que l'instruction desEtudiants = new Etudiant[40]; crée 40 objets Etudiant, alors qu'elle ne crée qu'un tableau contenant 40 références à des objets de type Etudiant, chacun étant initialisé à null.
Tableau 4. Accès à un élément d'un tableau
| Accès à un élément en lecture/écriture | Exemple |
|---|---|
Lecture TypeElement variable = tableau[position]; |
int point = desPoints[0]; // accès au premier élément point = desPoints[desPoints.length - 1];// accès au dernier élément |
Écriture tableau[position]= valeur; |
desPoints[0]=20; // affecte la valeur 20 au premier élément desPoints[desPoints.length - 1]=0;//et 0 au dernier élément desNoms[0]="toto"; desEtudiants[0]= new Etudiant(); |
Remarque
Toute tentative d'accès un élément d'un tableau avec un indice hors bornes [0..tableau.length[ déclenchera à l'exécution un exception de type java.lang.ArrayIndexOutOfBoundsException.
Exemple de parcours de l'ensemble des éléments d'un tableau;
class Tab{
static public void main(String[] args){
String[] tab = new String[3];
for (int i=0; i<tab.length; i++) {
System.out.println(tab[i]);
}
}
}
Exemple d'exécution :
$ javac Tab.java $ java Tab null null null $
Remarque : Les éléments du tableau sont de type String (objet), leur valeur par défaut est null.
Tableau 5. Exemple de tableau multidimensionnel
| Désignation/Remarques | Syntaxe | Exemple |
|---|---|---|
| Construction de tableau à deux dimensions | Type [] [] variable; // un tableau à 2 dimensions | // un damier de 20 cases x 20 cases boolean [ ] [ ] lesCases = new boolean[20][20]; // une bataille navale Bateau [ ] [ ] lesCases = new Bateau[nbLignes][nbCol]; |
La classe à utiliser : Compte.class.
Gestion de plusieurs comptes (d'après une idée de Nathalie Gruson)
Créer un fichier TPGestCompte.java.
Définir le tableau lesComptes : il doit pouvoir contenir au moins 10 objets de classe Compte. Vous trouverez ici l'API documenté de la classe Compte.
Affichez un menu avec les options suivantes:
0 : Quitter
1 : Création d'un compte
2 : Consultation des comptes
1=> doit créer un objet Compte à partir du nom du compte saisi par l’utilisateur et l’ajouter à la collection lesComptes.
2 => doit permettre de visualiser les informations de base (nom du compte et solde) de tous les comptes créés + le solde total (somme des soldes des différents comptes).
Complétez votre menu avec les options suivantes :
3 : Dépôt par position
4 : Retrait par position
Vous concevez le dépôt/retrait par position du compte concerné dans la collection : le programme invite l'utilisateur à saisir une position.
Avant et après chaque opération, le solde du compte concerné est affiché.
On considère que la valeur saisie par l'utilisateur est un nombre entier valide. Si cette valeur est hors des bornes attendues, un message d'erreur sera présenté à l'utilisateur.
Complétez votre menu avec les options suivantes (plus difficile) :
5 : Dépôt par nom
6 : Retrait par nom
Vous concevez le dépôt/retrait par nom du compte concerné dans la collection : le programme invite l'utilisateur à saisir un nom de compte.
Avant et après chaque opération, le solde du compte concerné est affiché.
Si la valeur fournie par l'utilisateur ne correspond à aucun compte, un message d'erreur sera présenté à l'utilisateur.
Complétez votre menu avec l’option suivante :
7 : Récapitulatif des opérations
Doit récapituler le nombre et montant des crédits et des débits. Exemple
Compte A1 : 10 dépôts (2500) + 4 retraits (982) Compte E2 : 5 dépôts (500) + 2 retraits (100) Au total : 15 dépôts (3000) + 6 retraits (1082)
Une solution : TPCompte.java
L'exercice précédent a abouti à la réalisation d'une classe TPGestCompte ayant une grosse fonction main mélangeant gestion de l'interface utilisateur avec des détails d'implémentations. Or, C'EST EXACTEMENT CE QU'IL NE FAUT PAS FAIRE !
En effet, en l'état, le programme est difficilement maintenable : les interactions utilisateur (affichage du menu, saisies d'informations, affichage de résultats) sont confondues avec la logique de traitement (créer un compte, rechercher un compte, lister les comptes...).
La solution à ce problème est simple, et consiste à concevoir une classe. On pourra alors réaliser une décomposition fonctionnelle (décomposition des traitements par fonctions), au même titre que nous avons appris à le faire avec PHP. Bien entendu, la syntaxe diffère un peu, mais à peine, comme vous allez le constater.
La première chose à faire lorsque l'on conçoit une classe est de définir ses services, en particulier les services offerts au travers de ses instances.
Par exemple, les services (ce sont des opérations) offerts par des objets de type ArrayList, sont spécifiés dans l'interface List (API List). Les opérations d'ajout d'un élément, de suppression, de recherche etc. sont spécifiées ici.
Nous allons faire de même ici, en déclarant des opérations, sans leur corps, dans une structure appelée interface.
Remarque : Noter que cette déclaration peut être réalisée directement dans une classe ; c'est une solution moins souple car elle lie une seule implémentation à une interface donnée, mais satisfaisante dans bien des cas. Nous vous proposons ici, de spécifier les déclarations d'opérations dans une interface, il ne vous restera plus qu'à concevoir plusieurs classes d'implémentation en exercices.
import java.util.*; interface IFGestCompte { /** retourne la liste des comptes */ public List getListDesComptes(); /** retourne null ou la référence à un compte * dans la liste. */ public Compte getCompte(int position); /** retourne null ou la référence du compte * portant le nom reçu en argument. */ public Compte getCompte(String nom); /** retourne vrai s'il existe dans la liste un compte * ayant comme nom la valeur reçue en argument, * retourne faux sinon. */ public boolean existeCompte(String nom); /** * Ajoute un compte à la liste des comptes. */ public void ajouterCompte(Compte c); /** retourne la liste des comptes, avec un état détaillé * pour chacun d'eux. */ public String details(); }
Nous avons, par l'intermédiaire de l'interface, simplement déclaré un contrat d'utilisation. Cette structure se compile, comme une classe, mais ne peut être instanciée car les opérations n'ont pas de corps !, en d'autres termes elles ne sont pas programmées. C'est aux classes qu'appartient cette responsabilité.
Nous modifions la solution : TPCompte.java, en utilisant une classe qui implémente l'interface IFGestCompte.
import javax.swing.*;
import java.util.List;
public class AppCompte {
static public void main(String[] args) {
IFGestCompte gestComptes = new GestComptesArray();
String menu = "0: Quitter";
menu += "\n1: Création d'un compte";
menu = menu + "\n2: Lister les comptes";
menu = menu + "\n3: Dépôt par position";
menu = menu + "\n4: Retrait par position";
menu = menu + "\n5: Dépôt par nom";
menu = menu + "\n6: Retrait par nom";
menu = menu + "\n7: Récapitulatif des opérations";
menu = menu + "\n.Votre choix SVP :";
String choix;
choix= JOptionPane.showInputDialog(menu);
// choix = Console.lireClavier(menu);
while (!"0".equals(choix)) {
if ("1".equals(choix)){
String unNom = JOptionPane.showInputDialog("Entrez un nom de Compte");
Compte c = new Compte(unNom);
gestComptes.ajouterCompte(c);
}
else if ("2".equals(choix)){
List liste = gestComptes.getListDesComptes();
String res="**** Les comptes ****";
for (int i=0; i<liste.size(); i++){
res += "\n***********\n"+liste.get(i).toString();
}
JOptionPane.showMessageDialog(null, res);
// System.out.println(res);
}
else if ("3".equals(choix)){
String spos =
JOptionPane.showInputDialog("Entrez la position du compte");
// traduire en String in int
int pos = Integer.parseInt(spos);
Compte c = gestComptes.getCompte(pos);
if (c != null){
String smontant =
JOptionPane.showInputDialog("Entrez le montant du dépôt");
double montant = Double.parseDouble(smontant);
c.deposer(montant);
}
}
else if ("4".equals(choix)){
String spos =
JOptionPane.showInputDialog("Entrez la position du compte");
// traduire en String in int
int pos = Integer.parseInt(spos);
Compte c = gestComptes.getCompte(pos);
if (c != null){
String smontant =
JOptionPane.showInputDialog("Entrez le montant du retrait");
double montant = Double.parseDouble(smontant);
c.retirer(montant);
}
}
else if ("5".equals(choix)){
String nom =
JOptionPane.showInputDialog("Entrez le nom du compte");
Compte c = gestComptes.getCompte(nom);
if (c != null){
String smontant =
JOptionPane.showInputDialog("Entrez le montant du dépôt");
double montant = Double.parseDouble(smontant);
c.deposer(montant);
}
}
else if ("6".equals(choix)){
String nom =
JOptionPane.showInputDialog("Entrez le nom du compte");
Compte c = gestComptes.getCompte(nom);
if (c != null){
String smontant =
JOptionPane.showInputDialog("Entrez le montant du retrait");
double montant = Double.parseDouble(smontant);
c.retirer(montant);
}
}
else if ("7".equals(choix)){
JOptionPane.showMessageDialog(null, gestComptes.details());
}
// redemande un choix à l'utilisateur
choix= JOptionPane.showInputDialog(menu);
// choix = Console.lireClavier(menu);
}
// pour forcer la fermeture.
System.exit(0);
}
}
Vous constater que le programme c'est considérablement simplifié (par rapport au précédent : TPCompte.java).
Le programme délégue certaines responsabilités (recherche d'un compte dans la liste, construction de l'état détaillé de chacun des comptes...) à un objet dont la classe est GestComptesArray. Cette classe implémente l'interface IFGestCompte.
L'objectif d'une classe est d'implémenter des services. Elle transforme des opérations en méthodes : une méthode est une réalisation d'opération.
Voici une version à compléter en exercice ! (sur la base de la solution à l'exercice précédent TPCompte.java) :
import java.util.*; public class GestComptesArray implements IFGestCompte { // attributs private Compte [] lesComptes; private int nbCptes; //constructeur public GestComptesArray() { lesComptes = new Compte[10]; nbCptes = 0; } // méthodes /** * Ajoute un compte à la liste. */ public void ajouterCompte(Compte c){ // TODO } /** retourne la liste des comptes */ public List getListDesComptes(){ // TODO return null; } /** retourne null ou la référence à un compte * dans la liste. */ public Compte getCompte(int position){ // TODO return null; } /** retourne null ou la référence du compte * portant le nom reçu en argument. */ public Compte getCompte(String nom) { // TODO return null; } /** retourne vrai s'il existe dans la liste un compte * ayant comme nom la valeur reçue en argument, * retourne faux sinon. */ public boolean existeCompte(String nom){ // TODO return false; } /** retourne la liste des comptes, avec un état détaillé * pour chacun d'eux. */ public String details(){ // TODO return "TODO"; } }
Vous constatez que la classe d'implémentation reprend TOUTES les opérations définies dans l'interface, à cela elle ajoute des attributs privés nécessaire pour réaliser les méthodes, ainsi qu'un constructeur pour initialiser les attributs.
Une classe d'implémentation est donc généralement composée de 3 parties : attributs, constructeurs et méthodes .
Nous avons vu les tableaux. Intéressons nous maintenant aux objets de type List car nous en aurons besoin dans l'exercice qui suit. Les objets de type List sont d'une grande souplesse, particulièrement lorsque nous ne connaissons pas à l'avance le nombre d'éléments que contiendra notre liste.
Caractéristiques principales des collections de type List
Sa capacité varie automatiquement en fonction des besoins.
Le nombre d'éléments dans la collection est fourni par la méthode size().
Par défaut, le type des éléments d'une collection est Object, c'est le type de base de tout objet en Java, mais les collections peuvent être typées à la déclaration.
Avec les versions du jdk < 1.5.0, une collection ne peut contenir d'éléments de type primitif (seulement de type référence), pour cela le développeur utilisera les types "enveloppe" associés (Integer pour les int, Float pour les float etc.).
Les classes de type Collections sont dans le package java.util.*. Il faudra ajouter une clause 'import' à votre programme.
Tableau 6. Déclaration d'une collection de type List
| Syntaxe | Exemple |
|---|---|
| List variable; |
List desPoints; List desNoms; List desJouets; |
Tableau 7. Construction d'une liste
| Syntaxe | Exemple |
|---|---|
| variable = new ArrayList(); |
desPoints = new ArrayList(); desNoms = new ArrayList(); desJouets = new ArrayList(); // ou, en cas de collection typée : List<Jouet> desJouets = new ArrayList()<Jouet>; |
Remarques
Par défaut une liste est vide à la création.
ArrayList et Vector sont des listes standard (mais pas les seules) couramment utilisées.
Tableau 8. Opérations courantes
| Opération | Exemple |
|---|---|
| Ajouter un élément | desPoints.add(new Integer(-1)); desNoms.add("toto"); desJouets.add(new Jouet("Echec truc", 45, "verre", "noir")); |
| Obtenir un élément |
// On obtient le premier élément.
// que l'on doit typer ("caster") car par défaut le type
// des éléments d'une collection est Object.
Integer i = (Integer) desPoints.get(0);
String nom = (String) desNoms.get(0);
Jouet j = (Jouet) desJouets.get(0);
// ou en cas de collection typée
Jouet j = desJouets.get(0);
|
| Obtenir le nombre d'éléments |
// obtient le nombre d'éléments dans la liste (un int)
int n = desJouets.size();
|
| Supprimer un élément |
// supprime de la liste l'élément situé à la position 0
desJouets.remove(0);
|
Remarques
Finalement, les listes sont d'une utilisation bien plus souple que les tableaux. Seul le cast au moment de la récupération d'un objet est verbeux. A noter que depuis le jdk 1.5.0, le développeur peut typer les éléments d'une collection.
Voici un extrait des opérations disponibles avec des objets de type List :
Voir la liste complète des opérations ici : API List
On vous fournit un programme principal (AppCompte.java) qui réalise un certain travail avec un objet de type IFGestCompte. Vous concevez et testez deux classes d'implémentation pour cette interface : GestComptesArray et GestComptesList.
Créer un répertoire et placer y les fichiers IFGestCompte.java, GestComptesArray.java, AppCompte.java et Compte.java.
Compiler ces fichiers, et lancer l'application : java AppCompte.
Tester l'application. Vous constatez qu'elle n'est pas opérationnelle. Votre mission consiste à remplacer les TODO des méthodes de GestComptesArray par des instructions réalisant les opérations. Par exemple :
import java.util.*; public class GestComptesArray implements IFGestCompte { ... /** * Ajoute un compte à la liste. */ public void ajouterCompte(Compte c){ if (nbCptes < lesComptes.length) { lesComptes[nbCptes] = c; nbCptes++; } } ...
Un autre exemple :
...
/** retourne la liste des comptes sous la forme d'un objet de type List*/
public List getListDesComptes(){
List res = new ArrayList();
for (int i=0; i < nbCptes; i++){
res.add(lesComptes[i]);
}
return res;
}
...
A vous de poursuivre ce travail.
Proposer une nouvelle classe d'implémentation, que vous nommez GestComptesList, qui implémente la variable membre privée lesComptes de type List :
import java.util.*; public class GestComptesList implements IFGestCompte { private List lesComptes = new ArrayList(); ... (implémenter les opérations déclarées par IFGestCompte)
Vous la testerez en instanciant un objet de cette classe (GestComptesList au lieu de GestComptesArray) dans AppCompte.java :
import javax.swing.*;
import java.util.List;
public class AppCompte {
static public void main(String[] args) {
IFGestCompte gestComptes = new GestComptesList();
... (comme avant)
Il n'y a rien d'autre à modifier dans la classe AppCompte, car GestComptesArray et GestComptesList offrent exactement les mêmes services car ces deux classes implémentent la même interface.
Bonne programmation !
Éléments de solution : GestComptesArray.java, GestComptesList.java, AppCompte.java.
Nous voulons représenter la notion d'adresse physique d'une personne sous forme d'une classe. Pour cela nous avons retenu 3 propriétés : rue, ville et codePostal de type String, uniquement accessibles en lecture et écriture via des accesseurs (getter/setter).
Proposer une version Java de cette classe (dans un fichier nommé Adresse.java)
Dans un programme de tests (TestAdresse.java), créer un objet Adresse, avec les valeurs suivantes : rue:"03 rue des écoles", ville:"Melun", codePostal:"77000". Vous proposerez deux versions : une qui valorise les attributs à l'aide des setter, et une autre au moment de la construction (à l'aide d'un constructeur à concevoir).
Ajouter une méthode toString à la classe Adresse, et tester cette méthode dans le programme de test.
Dans notre système d'information, une personne est caractérisée par un nom, un prénom et un login. On vous demande de concevoir une classe Personne respectant les règles de gestion suivantes :
Tout programme souhaitant créer un objet Personne, devra fournir au moins un nom et un prénom et au plus un nom, un prénom et un login.
En l'absence de login à la construction d'un objet Personne, celui-ci sera déterminé (calculé) par le constructeur sur la base de la règle suivante : login est une chaîne de caractères en minuscule composée de la première lettre du prénom suivi des premières lettres du nom, le tout ne devant pas dépasser 8 caractères, mais ne pas être inférieur à 4 caractères (à compléter de façon arbitraire le cas échéant).
pré-condition : le prénom et le nom fournit par l'appelant ne sont pas vide (nom.trim().length()>0 et prenom.trim().length()>0).
Exemple : nom=Arsac prénom=Jacques => login=jarsac
Le login d'une personne n'est pas accessible en écriture mais seulement en lecture.
Vous devez :
Concevoir une classe Personne répondant aux spécifications ci-dessus.
Ajouter un méthode toString à la classe Personne.
Concevoir un programme de test (TestPersonne.java) qui créé deux objets de type Personne, sur la base des informations suivantes :
personne 1 : nom=Meyer prénom=Bertrand login=eiffel
personne 2 : nom=Ny prénom=Yvan
Toute personne connue par le système d'information doit disposer au moins une adresse. Cette relation peut se représenter graphiquement par le diagramme UML suivant :
On vous demande de :
Intégrer dans la classe Personne l'attribut lesAdresses présentés dans le diagramme UML ci-dessus.
Ajouter à la classe Personne des méthodes permettant d'ajouter/obtenir/supprimer une adresse à la personne courante. La méthode ajouterAdresse reçoit une adresse à ajouter à la liste des adresses de la personne, les autres méthodes reçoivent en argument une position (devant correspondre à la position d'une adresse dans la liste des adresses détenue par la personne courante).
On souhaite que les programmes qui utilisent des objets de type Personne puissent connaître le nombre d'adresses détenues par chacune de ces objets. Proposez une méthode dans Personne offrant ce service.
Modifier le constructeur de Personne prenant en compte la règle de gestion "...une personne dispose au moins d'une adresse..." (on considère que l'appelant fournit une valeur non null).
Modifier la méthode toString de Personne en conséquence.
Concevoir une application (AppGestPersonnes.java) qui gèrent une liste de personnes, et qui propose à l'utilisateur un "menu" permettant de :
Ajouter une personne
=> nom, prenom et adresse sont des données saisies par l'utilisateur.
Supprimer une personne d'après sa position dans la liste.
=> position saisie par l'utilisateur.
Ajouter une adresse à une personne de la liste dont on connaît la position
Supprimer une adresse (d'après sa position) de la liste des adresses d'une personne de la liste.
=> attention à ne pas violer la règle de gestion spécifiant qu'une personne doit obligatoirement avoir au moins une adresse.
Lister l'ensemble des personnes (avec leurs adresses)
(optionnel) Permettre à l'utilisateur de modifier les caractéristiques d'une personne, d'une adresse d'une personne. Les valeurs actuelles sont proposées comme valeur par défaut lors des saisies utilisateur.
La programmation objet est basée sur quatre concepts fondamentaux que sont l'encapsulation, l'abstraction, le polymorphisme et l'héritage. Jusqu'à présent nous avons utilisé l'abstraction au travers du concept d'interface, conçu par encapsulation en cachant les attributs (private), redéfini plusieurs fois la méthode toString (polymorphisme) et hérité nos classes de la classe Object sans le savoir ! Nous présentons sommairement ces concepts dans cette section, et nous nous attarderons sur la notion d'héritage et de polymorphisme.
Encapsulation
L'encapsulation permet de cacher des détails d'implémentation aux clients de la classe
Comment ? en déclarant privé (private) les propriétés à encapsuler.
Polymorphisme
Le polymorphisme est une technique qui permet de proposer plusieurs versions d'une même opération.
Comment ? par redéfinition ou par surcharge.
Par exemple, la classe GestComptesArray propose deux versions de la méthode getCompte : une recevant un argument de type int (position) et un autre de type String (nom). Ce type de polymorphisme, qui consiste à proposer plusieurs méthodes de même nom mais ayant des arguments différents, s'appelle surcharge (overloading). L'autre forme courante de polymorphisme est la redéfinition (overriding), un mécanisme de redéfinition de méthodes lié à l'héritage (on parlent de polymorphisme d'héritage).
Abstraction
L'abstraction est un moyen de fournir un ensemble de contrats (des opérations sans corps, de simples interfaces d'opérations) qui devra être réalisé par des classes d'implémentation.
Comment ? en concevant une interface.
Une classe qui ne donnerait pas un corps à toutes ses méthodes est qualifiée d'abstraite (abstract), car elle est "incomplète".
Une classe qui ne contient aucun attribut d'instance (non static), qui n'implémente aucune méthode et ne dispose pas de constructeur est qualifiée de classe purement abstraite.
On ne peut pas directement créer des objets d'une classe abstraite ou d'une interface.
Héritage
L'héritage permet de réutiliser une implémentation en redéfinissant certaines méthodes.
Comment ? en concevant une classe qui hérite d'une autre classe.
Toute classe en Java est construite sur le même modèle, défini par autre classe, dite racine. Cette classe s'appelle Object, une convention dans les langages objet. En terme d'héritage l'autoréférence n'est pas permise, la classe racine n'a donc pas de modèle (elle n'a pas de modèle, elles est le modèle).
Prenons un exemple : la classe Jouet et un programme de test :
public class TestHeritageJouet {
static public void main(String[] args) {
Jouet j = new Jouet("Echec truc", 45, "verre", "noir");
System.out.println(j.toString());
}
}
Maintenant compilons et exécutons le programme :
$ javac TestHeritageJouet.java
Ho!... le programme compile alors que je n'ai pas défini de méthode toString dans la classe Jouet !???
Voyons quel est le comportement de cette méthode dont j'ignore l'existence.
$ java TestHeritageJouet
Jouet@f5da06
$
Ah...
Comment se fait-il que l'appel de la méthode toString() soit acceptée ? Tout simplement parce que la classe Jouet hérite de la classe Object et que la méthode toString est définie dans cette classe. En fait écrire :
public class Jouet {
...
}
Equivaut à :
public class Jouet extends Object {
... // comme avant
}
Donc, ne pas (re)définir dans la classe Jouet la méthode toString, c'est accepter son implémentation par défaut dans Object.
![]() | Une définition de l'héritage [Bertrand Meyer] |
|---|---|
L'héritage est un mécanisme permettant de définir une classe par rapport à d'autres en ajoutant toutes leurs caractéristiques aux siennes. |
Certains langages (comme C++, Eiffel) autorise la définition d'une classe à partir de plusieurs autres classes. Ceci s'appelle de l'héritage multiple. Bien que parfois très pratique cette possibilité peut poser problème lorsque des caractéristiques de classes héritées portent le même nom et dans le cas d'héritage répété.
=> Java supporte l'héritage multiple par l'intermédiaire des interfaces uniquement.
![]() | Une définition de l'héritage simple |
|---|---|
On appelle héritage simple, une forme restreinte d'héritage par laquelle une classe ne peut avoir plus d'un parent (une seule super-classe directe). |
=> Java ne reconnait que l'héritage simple entre classes.
UML représente cette relation orientée selon la spécialisation tout en la nommant généralisation... Exemple :
La classe enfant pointe vers ses parents (ancêtres immédiats). Le trait est plein et la pointe de la flèche forme un triangle non rempli.
Ce diagramme montre que Jouet hérite des méthodes equals et toString de Object (vue partielle).
Nous pouvons percevoir la relation classeObject - classesHéritant (par exemple Object - Jouet) comme une relation orientée selon deux points de vue : généralisation et spécialisation.
Généralisation : la classe Object factorise les propriétés communes à toutes ses sous-classes. Termes fréquemment employés :
Object généralise Jouet
Object est un parent de Jouet
Object est un ancêtre de Jouet
Object est une super-classe de Jouet
Spécialisation : la classe Jouet spécialise la classe Object. Termes fréquemment employés :
Jouet hérite de Object
Jouet est un enfant de Object
Jouet est un descendant de Object
Jouet est une sous-classe de Object
UML préconise l'emploi des couples ancêtre/descendant et/ou parent/enfant (UML 1.3 p.94). Les ancêtres d'une classe sont ses parents et leurs ancêtres. Les descendants d'une classe sont ses enfants et leurs descendants.
Les propriété déclarées privé (private), méthodes comme attributs, ne sont connues ni des classes descendantes ni des classes utilisatrices. Par contre les propriétés protégées (protected) sont connues des sous-classes éventuelles, mais pas des classes 'utilisatrices' des objets.
Prenons un exemple simple :
class Client {
private String nom;
private String prenom;
// accesseurs
...
public String toString() {. . . }
}
et intéressons-nous aux comportements possibles de cette classe vis à vis de la méthode toString qu'elle hérite d'Object.
On dénombre cinq comportements clés répartis en trois catégories : Conservateur, Réformateur, Nihiliste.
Conservateur : Respecte la tradition. Maintient la tradition en conservant le comportement hérité. Deux possibilités :
Conserver l'existant sans rien modifier.
=> on reprend à la lettre le comportement hérité : c'est le comportement par défaut des langages objets. Exemple :
public String toString() { return super.toString(); }
// Un simple appel à la méthode héritée. Adhère au comportement du parent.
// C'est le comportement par défaut (en absence de redéfinition).
Conserver l'existant en ajoutant une touche personnelle.
=> on hérite puis on ajoute des instructions.
public String toString() { return super.toString() + this.nom; }
// Appelle la méthode héritée et ajoute une note personnelle.
Réformateur : Renouvelle la tradition : Va de la simple transformation à la métamorphose.
Modéré : On tente de conserver le sens.
=> mais on l'exprime par du code entièrement nouveau (pas d'appel à super.methodeX()).
public String toString() {
return getClass().getName() + " : " this.nom +" " + this.prenom;
} // Redéfinit la méthode héritée dans l'esprit attendu.
Radical : On ne conserve pas le sens.
=> On raconte autre chose. Attention le problème de sens peut révéler une mauvaise de conception.
public String toString() {
return "Je passe incognito et je le dis...";
} // Redéfinit la méthode héritée dans un autre esprit.
Nihiliste (du latin nihil <<rien>>) Gomme la tradition. Supprime toutes traces des caractéristiques comportementales héritées.
On remplace la partie héritée par une expression du vide.
=> Attention, peut révéler un problème de conception.
public String toString() { /* je me tais */ return ""; }
// Décide de ne rien faire et rend donc une chaîne vide.
Les comportement les plus usuels sont : Conservateur et Réformateur conservant le sens hérité. Tout autre choix devrait être justifier sous forme de commentaire dans le code.
Comme pour les méthodes héritées, par défaut le constructeur d'une classe hérite du comportement du constructeur par défaut. Si vous redéfinissez un ou plusieurs constructeurs (ce qui est souvent le cas), ces constructeurs devraient appeler le constructeur hérité en premier. Exemple, nous souhaitons étendre la notion d'adresse physique à celle d'adresse Web :
Voici le code source d'Adresse :
public class Adresse {
/** Attributs */
private String rue;
private String ville;
private String codePostal;
/** Associations */
/** Constructeur */
public Adresse() {
}
public Adresse(String rue, String ville, String cp) {
this.rue=rue;
this.ville=ville;
this.codePostal=cp;
}
public String getRue() {
return this.rue;
}
public void setRue(String rue){
this.rue=rue;
}
public String getVille() {
return this.ville;
}
public void setVille(String ville){
this.ville=ville;
}
public String getCodePostal() {
return this.codePostal;
}
public void setCodePostal(String cp){
this.codePostal=cp;
}
public String toString(){
String res="";
res+="Rue : " + this.rue;
res+=", Ville : " + this.ville;
res+=", Code postal : " + this.codePostal;
return res;
}
}
Un exemple d'adresseWeb
public class AdresseWeb extends Adresse { /** Attributs */ private String mel; private String url; /** Constructeur */ public AdresseWeb(String rue, String ville, String cp, String mel, String url) { super(rue, ville, cp); this.mel=mel; this.url=url; } public String getMel() { return this.mel; } public void setMel(String mel){ this.mel=mel; } public String getUrl() { return this.url; } public void setUrl(String url){ this.url=url; } public String toString(){ String res=""; res+=", Mel : " + this.mel; res+=", URL : " + this.url; return super.toString() + res; } }
Le constructeur de la sous-classe appelle le constructeur de sa super classe en lui passant les valeurs des arguments adéquates :
public AdresseWeb(String rue, String ville, String cp, String mel, String url) {
super(rue, ville, cp);
this.mel=mel;
this.url=url;
}
On remarquera que, dans le constructeur, le mot clé super n'est pas suivi du nom du constructeur hérité (contrairement aux méthodes).
Exemple de construction d'objets :
public class TestAdresse {
static public void main(String[] args){
Adresse a1 = new Adresse("1 allée de la noix", "Grenoble", "38000");
Adresse a2 =
new AdresseWeb("3 rue moulin", "Melun", "77000",
"titi@labas.org","http://www.linux-france.org");
System.out.println(a1);
System.out.println(a2);
}
}
Notez que nous aurions pu également déclarer la variable a2 de type AdresseWeb.
Pour illustrer les concepts objets, nous représenterons un zoo et les animaux qui l'habitent. On souhaite pouvoir interroger le nom d'un animal et de lui demander de dormir.
Comment représenter cette classification dans un langage objet comme Java ?
Déclarons une interface (une classe abstraite pure) :
public interface Animal {
public String getNom();
public void dormir();
}
Puis les différentes classes d'animaux (Tigre, Lion et Ours) qui implémentent cette interface.
Classe Lion :
public class Lion implements Animal { //attribut private String nom; //constructeur public Lion (String nom) { this.nom = nom; } @Override public String getNom() { return this.nom; } @Override public void dormir() { System.out.println("Moi, " +this.nom + ", je dors sur le ventre."); } }
Classe Tigre :
public class Tigre implements Animal { //attribut private String nom; // constructeur public Tigre (String nom) { this.nom = nom; } @Override public String getNom() { return this.nom; } @Override public void dormir() { System.out.println("Moi, " +this.nom + ", je dors sur le dos."); } }
Classe Ours :
public class Ours implements Animal { //attribut private String nom; //constructeur public Ours (String nom) { this.nom = nom; } @Override public String getNom() { return this.nom; } @Override public void dormir() { System.out.println("Moi, " +this.nom + ", je dors dans un arbre."); } }
Déclarons le zoo : Il est constitué d'une collection d'objets (ce sera des objets de type Animal bien-sûr), son seul attribut, d'un constructeur qui crée des animaux (une façon de ne pas construire un zoo vide), et d'une méthode demandant à chacun des animaux du zoo de s'endormir.
package org.vincimelun.zoo;
import java.util.*;
public class Zoo {
private ArrayList<Animal> animaux; // collection d'animaux
public Zoo(){
// création de la collection
this.animaux = new ArrayList<Animal>();
// List est une interface (classe abstraite)
// on ne peut donc pas créer directement
// un objet de ce type. Vector ou ArrayList sont
// des classes qui réalisent List. Ok.
// A ce niveau, la liste est crée, mais vide.
}
/**
* Endormir tous les animaux du zoo
*/
public void endormirLesAnimaux() {
Animal animal;
// déclaration d'une variable locale.
// les indices d'une liste commencent à zéro.
for (int i=0; i < this.animaux.size(); i++) {
animal = animaux.get(i);
animal.dormir();
}
}
/** Permet d'ajouter un animal au zoo
* @param animal l'animal a ajouter
*/
public void ajouteAnimal(Animal animal) {
this.animaux.add(animal);
}
}
Créons une application qui crée un zoo avec quelques animaux, et endort ses animaux :
package org.vincimelun.zoo;
import java.util.Scanner;
public class GestionZoo {
//attribut
private Zoo zoo;
//constructeur
public GestionZoo() {
this.zoo = new Zoo();
zoo.ajouteAnimal(new Lion("seigneur"));
zoo.ajouteAnimal(new Tigre("gros chat"));
zoo.ajouteAnimal(new Ours("balou"));
zoo.ajouteAnimal(new Tigre("sherkan"));
}
//méthode
public void run() {
Scanner clavier = new Scanner(System.in);
String choix;
do {
System.out.println("0=Quitter, 1=Endormir : ");
choix = clavier.next();
if ("1".equals(choix)) {
zoo.endormirLesAnimaux();
}
} while (!"0".equals(choix));
}//run
public static void main(String[] args) {
GestionZoo app = new GestionZoo();
app.run();
}
}
Exemple d'exécution
$ java GestionZoo 0=Quitter, 1=Endormir : 1 Moi, seigneur, je dors sur le ventre. Moi, gros chat, je dors sur le dos. Moi, balou, je dors dans un arbre. Moi, sherkan, je dors sur le dos. 0=Quitter, 1=Endormir : 0 $
La classe Zoo ne dépend pas de classes d'implémentation d' Animal , elle dépend d'une abstraction (seulement de l'interface Animal ).
Nous avons pu ainsi nous concentrer, dans la classe Zoo , essentiellement sur des traitements génériques, comme endormir les animaux. De nouvelles espèces d'animaux pourront être conçues à posteriori , tout en profitant de traitements génériques développés bien avant elles !
Avant de pratiquer les exercices qui vous permettront de vérifier cela par vous même, nous nous arrêtons la notion de static.
Le qualificatif static déonte une approche non objet, car une propriété static n'est pas associé à une instance (un objet) mais à une classe.
Le caractère static d'un propriété (un attribut ou une méthode essentiellement) dénote une propriété directement liée au module de classe dans lequel elle est définie.
Dans ce contexte, une classe joue le rôle de static dans le sens librairie utilitaire. Dit autrement, une propriété static n'est pas liée à une instance, et c'est pourquoi il est toujours préférable de la préfixer explicitement par le nom de sa classe, afoin de souligner son caractère static. Exemple :
... double x = Math.PI * 2; double y = -x; ... double z = Math.abs(x); ...
PI est un attribut de classe (pas un attribut d'instance !), tout comme abs est une fonction utilitaire du module java.lang.Math (le package java.lang étant le package par défaut, inutile de le déclarer).
La documentation de l'API nous confirme que ces propriétés sont bien libéllées static :
Les propriétés static sont ordinairement initialisées par des instructions placées dans un bloc static, équivalent des construteurs d'objet. Les blocs static sont exécutés au chargement de la classe.
En mémoire il n'y a au plus qu'une "instance" d'un module de classe et 0 à n instances de cette classe.
Pour ajouter une nouvelle espèce, il suffit de concevoir une sous-classe de Animal et de définir sa méthode dormir() et getNom() , sans avoir besoin de modifier la méthode endormirLesAnimaux() .
Voici les fichiers : zoo.zip.
Après avoir placer ce fichier (zoo.zip) à la racine de l'espace de travail d'Eclipse, à partir de ce dernier, importer le via import.projet existing into workspace/compressed. Ou alors créer un répertoire de projet et copier les fichiers sources à l'intérieur.
Ajouter une nouvelle espèce, comme indiqué ci-dessus.
Instancier la nouvelle classe (créer un objet) dans la méthode run de GestionZoo , en donnant un nom à l'animal, et ajouter votre animal aux animaux du zoo.
Compiler et exécuter le programme.
Ajouter une méthode nommée exprimeToi() dans l'interface Animal que vous testez dans la classe Zoo .
Cette méthode affichera le cri de l'animal, par exemple "miaou..", pour un gros minet. Vous modifierez les classes implémentant l'interface en conséquence.
On souhaite ajouter une nouvelle espèce : OursPolaire, qui, comme son nom l'indique, représente la classe des Ours Polaire. Un ours polaire est une sorte de Ours. C'est relation est typiquement une relation d'héritage. Notre ours polaire héritera donc du comportement des Ours, mais devra redéfinir au moins un comportement : il n'y a pas (ou trop peu) d'arbre en arctique... la banquise est son lit, et au zoo il se contentera d'un rocher :-(.
On souhaite que l'invocation de la méthode toString d'un animal quelconque rende l'information suivante : nom de sa classe suivi du nom de l'animal. Proposez une solution que vous testez dans Zoo.
Ajouter des traitements de gestion suivant : Affichage du nombre d'animaux du zoo, ajout d'un animal au zoo (l'utilisateur donne la classe et le nom de l'animal), suppression d'un animal du zoo (l'utilisateur donne son nom). Pour cela vous ajouterez les méthodes nbAnimaux() , deleteAnimal(String nom) dans la classe Zoo , et que vous testerez dans la méthode run de GestionZoo .
Bonne programmation !
Travail de synthèse. Vous êtes amené à concevoir une hiérarchie de classes d'après un cahier des charges. Vous tesez votre solution en instanciant des objets et en les placant dans une liste. Le programme réalise ces tâches en interaction avec l'utilisateur.
Dans un second temps (jeu du pendu), vous finalisez un programme (première étape) que vous faites évoluer (seconde étape). Vous devez pour cela rechercher par vous mêmes dans l'API, les possibilité offertes par les objets techniques proposés (Random et StringBuffer).
On souhaite simuler une gestion de parc de véhicules d'une société de BTP. Un véhicule est caractérisé par un numéro d'immatriculation, une date de mise en circulation, un kilométrage, un modèle et une marque.
Parmi les véhicules on distingue les véhicules utilitaires (ayant un poids total à vide, une charge maximale) des véhicules des techniciens commerciaux (attitré à un commercial).
On ne cherchera pas à concevoir une interface, mais une superclasse qui factorise les caractéristiques des véhicules utilitaires et commerciaux.
Proposez une classification que vous illustrez avec un diagramme de classes UML.
Implémentez ces classes en Java. Pour représenter la date de mise en circulation, on utilisera le type java.sql.Date. Cette classe propose une fonction utilitaire permettant de convertir une date en chaîne de caractères en un objet de type java.sql.Date : voir en ligne l'API java à ce sujet.
Réalisez un programme offrant à l'utilisateur la possibilité de créer un véhicule utilitaire, de créer un véhicule attitré à un commercial, de lister l'ensemble des véhicules du parc. Vous réalisez cela le plus simplement possible dans la fonction main d'une classe de tests.
On veut programmer le jeu du pendu en java. Pour cela on désire concevoir une classe JeuPendu qui réalise la logique applicative du jeu. Une première analyse nous a permis d'identifier les opérations suivantes :
public void chargerMots(); charge les mots d'un dictionnaire dans une liste.
public void tirerUnMotAuHasard(); tire un mot de façon 'aléatoire' dans la liste.
public boolean leMotEstDecouvert(); rend vrai si le mot à découvrir est découvert, rend faux sinon.
public boolean analyser(char c); regarde si le caractère reçu en argument fait partie des caractères du mot secret. Si c'est le cas, toutes les occurences de ce caractère sont découvertes dans le mot crypté puis rend true, sinon ne fait rien et rend false.
public String getLeMotCrypte(); retourne le mot crypté
public String getLeMotSecret(); retourne le mot secret en clair.
Voici une première implémentation, à compléter :
package org.vincimelun.pendu;
import java.util.*;
import java.io.*;
public class JeuPendu {
// attributs privés
/** le mot que l'utilisateur doit découvrir */
private String leMotSecret;
/** le mot secret pour l'affichage.
* StringBuffer : un type String modifiable
*/
private StringBuffer leMotCrypte;
/** pour représenter en mémoire la liste des mots */
private List lesMots;
// constructeur
public JeuPendu() throws java.io.IOException {
lesMots = new ArrayList();
chargerMots();
tirerUnMotAuHasard();
}
// méthodes
public void chargerMots() {
ChargeurDeMots chargeur = new ChargeurBidon();
lesMots.add("programmation");
lesMots.add("java");
lesMots.add("informatique");
lesMots.add("langage");
}
/**
* tire "au hasard" un mot de la liste des mots
* et l'affecte à this.leMotSecret puis
* valorise this.leMotSecret en conséquence
* (de même longueur que leMotSecret mais formé avec des '#')
*/
public void tirerUnMotAuHasard() {
Random alea = new Random();
// TODO
}
/**
* rend true si leMotCrypté est découvert,
* rend false sinon
*/
public boolean leMotEstDecouvert() {
return false;
// TODO
}
/**
* regarde si le caractère reçu en argument fait partie
* des caractères du mot secret. Si c'est le cas, toutes
* les occurences de ce caractère sont découvertes dans
* le mot crypté ET la fonction rend true, sinon false.
*/
public boolean analyser(char c) {
boolean res = false;
// TODO
return res;
}
/**
* retourne le mot crypté.
*/
public String getLeMotCrypte() {
return "####";
}
/**
* retourne le mot secret.
*/
public String getLeMotSecret() {
return "TODO";
}
}
Quelques commentaires
Les attributs :
leMotSecret correspond au mot à découvrir.
leMotCrypte de type StringBuffer. Ce mot représente la vue utilisateur du mot à découvrir, il est initialement composé de #
et sera découvert progressivement au cours de la phase du jeu. On utilise ici un StringBuffer car on aura
besoin de modifier son contenu lorsque l'on découvrira certains caractères, ce que ne permet pas les objets de la classe String - non mutable . On utilisera pour cela la méthode setCharAt(indice, nouveau_car) qui permet de modifier un caractère de la chaîne à l'indice spécifié.
lesMots : une liste de type ArrayList. Attention, comme c'est une structure de liste générique, elle travaille avec des objets de base, de type Object. Pour retirer un élément de type String, on utilisera une conversion de type : par exemple :
String unMot = (String) lesMots.get(O);
Extrait le premier objet de la liste (indice zéro), et le cast en String, sous-classe de Object.
Les opérations (méthodes) :
chargerMots : place quelques mots dans une liste (ArrayList) appelée lesMots.
tirerUnMotAuHasard(): utilise une objet de la classe Random, dans le
but d'appeler sa méthode nextInt( un entier ) qui devra retourner un indice valide.
Il suffit alors de "retirer" le mot de la liste : this.leMotSecret = (String) lesMots.get(indice); et à formater le mot crypter avec des # (voir méthode replaceAll de la classe String).
leMotEstDecouvert : rend vrai si aucun '#' ne figure parmi les caractères du leMotCrypte.
analyser(c : caractère) : si le caractère c est inclus dans le mot à découvrir (leMotSecret), montre alors "en clair" ce caractère dans leMotCrypte et rend true, ne fait rien et rend false sinon.
getLeMotCrypte : rend le mot crypte. Attention, cette méthode doit retourner du String, il faudra donc transformer le StringBuffer en String en appelant la
méthode toString() de l'objet this.leMotCrypte.
Voici un exemple de programme principal :
public class JeuPenduTextUI {
static public void main(String[] args){
JeuPendu jeuPendu = new JeuPendu();
while (!jeuPendu.leMotEstDecouvert()) {
String choix =
Console.lireClavier("Entrez un caractère suivi de ENTER : ");
jeuPendu.analyser(choix.charAt(0));
System.out.println(jeuPendu.getLeMotCrypte());
}
}
}
A FAIRE
Implémenter la classe JeuPendu (voir les TODO). Vous utiliserez l'aide en ligne de l'API java 1.4.2 pour prendre connaissance des méthodes de StringBuffer et Random.
Permettre à l'utilisateur de quitter la partie en cours de jeu. Dans ce cas, le programme affichera le mot qui n'a pas été découvert.
Modifier la règle du jeu (JeuPenduTextUI.java) de sorte à ce que le joueur ne dispose que d'un nombre limité d'essais infructueux.
Pour les impatients, voici une version de chargeMots qui charge dans la liste des mots, le contenu d'un fichier texte (motsPendu.txt) composé de plus de 1500 mots (un mot par ligne) :
public void chargerMots() {
try {
BufferedReader in
= new BufferedReader(new FileReader("motsPendu.txt"));
String mot = in.readLine();
while (mot!=null) {
lesMots.add(mot);
// ajoute le mot dans la liste
mot = in.readLine();
// lire la ligne (le mot) suivant dans le fichier
// à la fin du fichier => mot == null
}
in.close();
// fermeture du fichier
}
catch(IOException e) {
// le fichier n'a pas pu être ouvert, on insère quelques mots
lesMots.add("programmation");
lesMots.add("java");
lesMots.add("informatique");
lesMots.add("langage");
}
}
Extraits de sujets d'examens 2000-2002 - STS IG lycée Léonard de Vinci 77000 Melun -
Soit la classe Truc
class Truc {
private String nom;
private double prix;
public void setNom(String nom) {
this.nom=nom;
}
public void setPrix(double prix) {
this.prix=prix;
}
}
Et le programme suivant :
public class Test {
static public void main(String[] args) {
Truc t1 = new Truc();
// dans quel état est le truc
// référence par t1 ? ===> REPONSE A
t1.setNom("bidule");
t1.setPrix(12.3);
// dans quel état est le truc t1 ? ===> REPONSE B
}
}
Justifier vos réponses ( 2 points )
Comment faire pour initialiser un objet à sa création ? Fournir un exemple ( 3 points )
=>une solution ici : exemple de réponses aux questions
On vous demande de concevoir une classe nommée Voiture permettant au programme ci-dessous de fonctionner.
class Application {
public void run() {
Voiture v = new Voiture("Titine", 7);
// création d'une voiture nommée "Titine" de 7 chevaux
System.out.println(v.donneVitesse());
// affiche : 0
v.passeVitesseSuperieure();
// passe la première vitesse
v.passeVitesseSuperieure();
// passe la seconde vitesse
v.passeVitesseSuperieure();
// passe la troisième vitesse
v.retrograde();
// retourne en deuxième
System.out.println(v.donneVitesse());
// affiche : 2
System.out.println(v);
// affiche : Titine, 7 chevaux
}
static public void main(String[] args){
Application app = new Application();
app.run();
}
}
Remarque : Ne concevez que le strict minimum
(attributs, constructeur et méthodes) nécessaire au fonctionnement du programme Application .
On souhaite ajouter un attribut maxVitesse , correspondant au nombre de vitesses disponibles pour une voiture donnée (par exemple 5).
On vous demande d'aménager la classe Voiture en 2 étapes :
Déclarer l'attribut et modifier le constructeur en conséquence. ( 2 points )
Faire en sorte que les méthodes passeVitesseSuperieure() et
retrograde() laissent toujours la voiture dans un état cohérent (pas de vitesse impossible). ( 3 points )
On vous demande d'ajouter à la classe Voiture :
Une méthode nommée estPointMort qui rend la valeur booléenne vrai si la vitesse en cours est nulle (valeur zéro) et faux dans le cas contraire. ( 1 point )
Afin de vérifier le bon fonctionnement de la méthode estPointMort() , on vous demande de compléter la classe TestVoiture , et plus particulièrement de :
Concevoir la méthode testEstPointMort() . Cette méthode devra créer une voiture, puis tester la méthode estPointMort en changeant plusieurs fois l'état de l'objet.
La méthode testEstPointMort affichera OK si la valeur de la méthode est conforme à celle que vous attendez, ERREUR dans le cas contraire.
Remarque 1 : les valeurs booléennes de Java sont représentées par les littéraux true et false .
Remarque 2 : vous serez évalué sur la qualité de votre test, qui devra comporter au moins 3 appels de la fonction estPointMort . ( 3 points )
public class TestVoiture {
/**
* Test la méthode donneVitesseSuperieure
*/
public void testDonneVitesseSuperieure() {
Voiture v = new Voiture("Deuche", 2);
v.passeVitesseSuperieure();
if (v.donneVitesse() == 1) {
System.out.println("OK")
}
else {
System.out.println("ERREUR")
}
etc.
}
/**
* Test la méthode estPointMort :: A FAIRE ::
*/
public void testEstPointMort() {
Voiture v;
}
static public void main(String[] args){
TestVoiture test = new TestVoiture();
test.testEstPointMort();
}
}
=>une solution ici : Voiture.java
Aujourd'hui, il est courant qu'une application de gestion fournisse une interface graphique à ses utilisateurs. Il existe deux types d'interface graphique utilisateur IGU, plus connues sous le sigle GUI pour Graphic User Interface : interface Web et interface de type window. L'une est orientée page et l'autre fenêtre. Ces deux types d'interface ont en commun les caractéristiques suivantes :
Sensible aux événements externes et internes
Construites avec une technologie objet
Permettent plusieurs vecteurs de communication (image, son, texte, couleur, ...)
Avec Java le développeur dispose en standard de deux catégories d'outils (en dehors de HTML) pour construire des interfaces, ce sont awt et swing .
AWT Abstract Window Toolkit
Avantages
Gestion des événements efficace depuis Java 1.1.
Plus rapide que Swing.
Compatible avec les Navigateurs (Netscape et IE dans leur version >= 4 supportent les applets composées avec Java 1.1.)
Inconvénients
API fortement lié aux ressources du SE hôte.
Bibliothèque de composants moins riche que Swing.
Utilisation
Applet (application Java embarquée dans un navigateur).
Application autonome avec GUI simple.
SWING Permet de construire des GUI de haut niveau. Basé sur AWT et sa gestion des événements.
Avantages
Richesse des éléments d'interface.
API d'accessibilité.
Choix du "look and feel".
Dessin 2D/3D.
Gestion des événements sophistiquée.
Inconvénients
Plus lent que l'AWT
Utilisation
Application autonome, client/serveur.
Application intranet et, dans une moindre mesure, internet (applet).
Il existe une alternative à AWT et Swing, c'est un projet initié par IBM, qui prend le meilleur de ces deux technologies : SWT ( Standard Widget Toolkit ) - voir http://www.eclipse.org/swt/ ). Nous nous concentrons ici sur Swing , le plus utilisé du moment (2003).
La construction d'une interface graphique utilisateur (GUI) nécessite environ 6 étapes
Concevoir une rapide maquette à la main, sur papier par exemple, afin de déterminer les principaux éléments de l'interface.
Concevoir une classe puis déclarer, créer et configurer les composants (couleur, texte...).
Placer les composants dans leur conteneur, puis dans le conteneur principal de la fenêtre.
Gérer le positionnement des composants dans leur conteneur (un panneau le plus souvent).
Associer aux composants générateurs d'événements un gestionnaire d'événements (le plus souvent la fenêtre elle-même).
Coder la logique événementielle de traitement.
Une application fenêtrée comporte généralement un menu. Nous vous proposons de construire une fenêtre d'application disposant d'un menu, que vous étendrez en exercice.
Nous choisissons de concevoir une fenêtre avec une barre de menus, munie d'une commande permettant de quitter l'application.
JFrame est une classe de la bibliothèque javax.swing qui représente une fenêtre graphique.
L'idée est de spécialiser la classe de base JFrame en une classe qui contient les composants dont vous avez besoin. Par exemple, nous désirons concevoir une fenêtre ayant une barre de menus.
Nous ajoutons un constructeur et une méthode d'initialisation. Voici une traduction partielle en Java :
import javax.swing.*; public class FenetreMenu extends JFrame { // attributs ... // constructeur public FenetreMenu() { // appel un constructeur de son parent, en passant // une valeur de type chaîne de caractères super ("Fenetre avec une barre de menus"); // effet : donne un titre à la fenêtre // permettre de quitter l'application lorsque l'utilisateur // clique sur la croix en haut à droite de la fenêtre. this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); this.init(); // voir plus bas pour des explications concernant cette méthode. } ... }
On remarquera le mot clé super , qui permet d'appeler un des constructeurs de la classe parent (rappel : un constructeur n'est pas une méthode).
Nous devons maintenant créer et configurer les composants.
Nous pouvons réaliser cela soit directement dans le constructeur, soit dans une méthode d'initialisation spécialement conçue à cet effet ( init ) et appelée dans le corps du constructeur. Afin de ne pas alourdir le constructeur, nous créons la barre de menus et ses composants dans la méthode init . Extrait :
private void init(){
//on a besoin de créer une barre de menus
JMenuBar menuBar;
// et un menu
JMenu menuFichier;
//création de la barre de menus
menuBar = new JMenuBar();
//construisons le premier menu
menuFichier = new JMenu("Fichier");
menuFichier.setMnemonic(KeyEvent.VK_F);
// création de la commande "quitter" (un JMenuItem)
JMenuItem mnItemQuitter = new JMenuItem("Quitter",
KeyEvent.VK_Q);
mnItemQuitter.setActionCommand("Quitter");
...
}
Nous avons choisi d'utiliser un constructeur JMenuItem permettant de spécifier une touche d'accès rapide (un mnemonic ) spécifiée en second paramètre. Vous noterez que la valeur du paramètre est une constante static de la classe KeyEvent , VK voulant dire Virtual Key .
Nous spécifions le nom de l' "action" auquel le composant sera lié . C'est une simple information ( String ) que le composant transmet à ses "écouteurs" lorsqu'il est activé (ce point est détaillé plus bas dans ce document).
Remarque : L'aide en ligne est la principale ressource permettant de connaître les possibilités de configuration offertes par un composant.
La barre de menus contient des menus, et les menus contiennent des commandes ( menu item ).
Ces relations sont sous la responsabilité du programmeur. Comme on peut s'y attendre, les conteneurs disposent d'une méthode nommée add permettant l'ajout de composants.
private void init() {
...
// le menu Fichier contient la commande Quitter
menuFichier. add (mnItemQuitter);
// on peut placer une barre de séparation pour
// séparer des groupes logiques de commandes.
// menu.addSeparator();
// la barre de menus contient le menu Fichier
menuBar. add (menuFichier);
// plus un autre menu bidon, pour l'exemple
menuBar. add (new JMenu("Un autre menu"));
...
}
Deux façons de faire :
Gérer soi-même la position en x, y du composant.
Sous-traiter le positionnement du composant par un gestionnaire de positionnement ( layout ).
Java propose en standard quelques gestionnaires, qui seront étudiés prochainement.
En ce qui concerne la barre de menu, il existe exceptionnellement une méthode, nommée setJMenuBar , dédiée à son placement dans la fenêtre (une barre de menus est traditionnellement située sous la barre de titre) :
private void init() {
...
// fournir à la fenêtre une barre de menus
this. setJMenuBar (menuBar);
...
// donnons une largeur et une hauteur à la fenêtre
this.setSize(300,200);
}
Ce qui donne :
Une fois notre composant créé (avec new ), placé et positionné dans la vue, il ne nous reste qu'à nous occuper de la gestion de ses événements.
Bon nombre de composants sont générateurs d'événements. Par exemple, lorsque l'utilisateur clique sur un bouton, celui-ci est capable de prévenir tout autre objet qui l'écoute. Pour qu'un objet puisse être prévenu de l'événement, il doit s'abonner au composant générateur de l'événement .
La fenêtre s'abonne à la commande du menu de cette façon :
mnItemQuitter. addActionListener (this);
this fait référence à la fenêtre en cours (celle qui sera en exécution). Par cette instruction, on abonne la fenêtre en tant qu' écouteur ( listener ) des événements générés par mnItemQuitter .
Pour que le JMenuItem accepte la fenêtre en tant que gestionnaire de ses événements, la fenêtre doit être une sorte de ActionListener (un écouteur d'événement). Pour cela la fenêtre devra implémenter l' interface ActionListener .
public class FenetreMenu
extends JFrame implements ActionListener {
...
}
... et donc donner corps à la méthode :
public void actionPerformed(ActionEvent evt)
... seule opération déclarée par l'interface ActionListener . C'est l'objet de l'ultime étape.
C'est dans cette dernière étape que le développeur justifie pleinement son rôle...
Il doit programmer le comportement de l'application lorsqu'un événement survient.
Par exemple, ici l'utilisateur souhaite mettre fin à l'application.
...
public void actionPerformed(ActionEvent evt) {
String action = evt.getActionCommand();
if (action.equals("Quitter")) {
System.exit(0);
}
}
...
La méthode actionPerformed est automatiquement appelée lorsque l'utilisateur sélectionne la commande Quitter.
Puisqu'une fenêtre est généralement abonnée à plusieurs composants, la méthode actionPerformed , détermine l'action (méthode) devant être déclenchée. Pour cela, elle interroge la "command action", détenue par le paramètre.
Si la command action vaut "Quitter", alors, nous savons que mnItemQuitter est à l'origine de l'événement et nous mettrons fin à l'application.
import javax.swing.*;
import java.awt.event.*;
public class FenetreMenu extends JFrame implements ActionListener{
// une constante (mot clé final)
// c'est un moyen très pratique d'associer un écouteur d'événement
// à un générateur d'événement.
static final String ACTION_QUITTER = "Quitter";
// constructeur
public FenetreMenu() {
// appel un constructeur de son parent
super("Ma première interface graphique");
// effet : donne un titre à la fenêtre
// l'application s'arrête lorsque la fenêtre est fermée.
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// initialisation de la fenêtre
init();
}
private void init(){
//on a besoin de créer une barre de menus
JMenuBar menuBar;
// et un menu
JMenu menuFichier;
//création dela barre de menus
menuBar = new JMenuBar();
//construisons le premier menu
menuFichier = new JMenu("Fichier");
menuFichier.setMnemonic(KeyEvent.VK_F);
menuFichier.getAccessibleContext().setAccessibleDescription(
"Menu permettant d'accéder à une commande pour quitter");
//création de la commande "quitter"
JMenuItem mnItemQuitter = new JMenuItem(ACTION_QUITTER,
KeyEvent.VK_Q);
mnItemQuitter.getAccessibleContext().setAccessibleDescription(
"Quitter le programme");
//mnItemQuitter.setActionCommand(ACTION_QUITTER);
// le menu Fichier contient la commande Quitter
menuFichier.add(mnItemQuitter);
//menu.addSeparator();
// la barre de menus contient le menu Fichier
menuBar.add(menuFichier);
JMenu autre = new JMenu("Un autre menu");
autre.setMnemonic(KeyEvent.VK_U);
autre.add(new JMenuItem("une commande",KeyEvent.VK_C));
menuBar.add(autre);
// on l'ajoute à la fenêtre
setJMenuBar(menuBar);
// la fenêtre est à l'écoute d'une action sur ce menu
mnItemQuitter.addActionListener(this);
setSize(300,200);
}
public void actionPerformed(ActionEvent evt) {
String action = evt.getActionCommand();
if (action.equals(ACTION_QUITTER)) {
System.exit(0);
}
}
static public void main(String[] args) {
JFrame f = new FenetreMenu();
f.setVisible(true);
}
}// FentreMenu
Nous avons vu, au travers d'un exemple, les 6 étapes nécessaires à la conception d'une interface graphique en Java .
Analyser et concevoir une maquette "papier".
Réutiliser par héritage, puis créer et configurer les composants.
Placer les composants dans un conteneur.
Gérer la position des composants dans le conteneur.
Lier des gestionnaires d'événements aux composants retenus.
Coder la logique en réponse aux événements.
![]() | Composants Swing et Awt |
|---|---|
Les composants En règle générale, vous ne devriez jamais mélanger dans une même interface, des composants AWT et SWING. |
![]() | Action Command |
|---|---|
Nous vous conseillons d'utiliser des CONSTANTES comme valeur des action command , comme par exemple ACTION_QUITTER. static final String ACTION_QUITTER = "Quitter"; Par convention, les constantes sont spécifiées en MAJUSCULE, et sont généralement déclarées static . Il est en effet inutile de dupliquer une constante pour chaque objet. Rappelons qu'une propriété static est liée à la classe elle-même et non à chacun des objets de la classe en particulier. Du coup, tous les objets d'une même classe partagent la même propriété (si celle-ci est déclarée static). C'est pourquoi, cette dernière est très souvent une constante ( final ). |
Vous trouverez chez Sun un tutoriel dédié à l'utilisation des menus. Vous apprendrez comment placer des cases à cocher, des sous-menus, des images dans un menu, c'est ici http://java.sun.com/docs/books/tutorial/uiswing/components/menu.html .
Ajouter à la barre de menus de la fenêtre, un menu nommé Configuration .
Ajouter à ce nouveau menu, les commandes Modifier la hauteur , Modifier la largeur , Modifier la position en x , Modifier la position en y .
Coder la logique de traitement pour chacune des commandes. Une JFrame a un attribut privé nommé width (largeur) et height (auteur). Ces propriétés sont interrogeables par la méthode getSize et modifiable par setSize . Attention, getSize retourne un objet de type Dimension qui dispose de deux attributs publics : width et height . Exemple d'utilisation :
Dimension dim = this.getSize();
String msg = "La largeur d la fenêtre est : " + dim.width ;
javax.swing.JOptionPane.showMessageDialog(null, msg);
Pour modifier la position en X,Y d'un composant, vous pouvez utiliser la méthode setLocation , et accessoirement la méthode getLocation . Exemple d'aide de l'API :
// méthode de la classe java.awt.Component
public Point getLocation()
// Returns:
// an instance of Point representing the top-left corner
// of the component's bounds in the coordinate space
// of the component's parent.
Remarque 1 : Point est une classe qui a deux attributs publics X et Y, de type int .
Remarque 2 : le composant "parent de la fenêtre" (celui qui contient la fenêtre) est dans notre cas l'écran.
On vous demande de concevoir une méthode privée par commande ( JMenuItem ) , ainsi le gestionnaire d'événements ne fera que sélectionner la bonne méthode en fonction de la source de l'événement. Exemple :
public void actionPerformed(ActionEvent evt) {
String action = evt.getActionCommand();
if (action.equals(ACTION_QUITTER) {
System.exit(0);
}
else if (action.equals(ACTION_MODIF_X) {
// une méthode à concevoir dans cette classe
this.setPositionX();
}
// etc.
}
Présenter à l'utilisateur, dans la boîte de dialogue, la valeur actuelle qu'il s'apprête à modifier.
Ajouter une commande permettant à l'utilisateur de changer le titre de la fenêtre.
Ajouter une commande permettant de placer automatiquement la fenêtre au centre de l'écran.
![]() | Astuce |
|---|---|
Voici comment obtenir la taille de l'écran : Dimension screenSize = getToolkit().getScreenSize(); La taille de la fenêtre s'obtient par : Dimension paneSize = getSize(); Il ne vous reste plus qu'à chercher dans l'aide en ligne comment utiliser un objet de type Vous remarquerez que ses champs (attributs) |
Nous nous proposons de concevoir une version graphique du jeu du pendu. Nous ne redéfinirons que la partie interaction avec l'utilisateur, la logique du jeu restant la même.
Objectifs
Utiliser et Comprendre le rôle des gestionnaires de positionnement (layout)
Savoir placer dynamiquement des images dans une IHM
Utilisation des contrôles JButton, JProgressBar, JPanel et JLabel.
Sensibilisation à un modèle en couches (IHM, applicative)
ref : http://java.sun.com/docs/books/tutorial/uiswing/layout/using.html
Un "gestionnaire de positionnement" (Layout manager) est un système qui détermine la taille et la position de composants. Par défaut, chaque conteneur a un layout manager - un objet qui gère la taille et la position des composants à l'intérieur du conteneur. Un composant peut tenter de gérer sa taille et sa position, mais au final, c'est le layout manager qui aura le dernier mot.
Les gestionnaires de positionnement les plus courants sont : BorderLayout , FlowLayout , GridLayout . Il en existe d'autres : BoxLayout , GridBagLayout ...
BorderLayout : Permet de positionner des composants selon un positionnement repéré par des points cardinaux (nord, est, sud, ouest) plus le centre.
Remarque : BorderLayout est le layout par défaut de chaque contentPane (conteneur principal des objets de type JFrame , JApplet et JDialog ).
...
JPanel panelBorderLayout = new JPanel();
panelBorderLayout.setLayout(new BorderLayout());
panelBorderLayout.add(new JButton("Button 1 (NORTH)"), BorderLayout.NORTH);
panelBorderLayout.add(new JButton("2 (CENTER)"), BorderLayout.CENTER);
panelBorderLayout.add(new JButton("Button 3 (WEST)"), BorderLayout.WEST);
panelBorderLayout.add(new JButton("Long-Named Button 4 (SOUTH)"), BorderLayout.SOUTH);
panelBorderLayout.add(new JButton("Button 5 (EAST)"), BorderLayout.EAST);
...
FlowLayout : Place les composants de la gauche vers la droite , et au besoin, continue sur une nouvelle ligne en dessous.
Remarque : C'est le gestionnaire par défaut des JPanel.
...
JPanel panel = new JPanel();
panel.setLayout(new FlowLayout());
panel.add(new JButton("Button 1"));
panel.add(new JButton("2"));
panel.add(new JButton("Button 3"));
panel.add(new JButton("Long-Named Button 4"));
panel.add(new JButton("Button 5"));
...
GridLayout : Taille les composants à une même taille et les range selon une grille exprimée en nombre de lignes (rows ) et de colonnes (columns ).
...
JPanel panel = new JPanel();
// création d'une grille de positionnement de
// 3 lignes sur 2 colonnes
panel.setLayout(new GridLayout(3,2));
panel.add(new JButton("Button 1"));
panel.add(new JButton("2"));
panel.add(new JButton("Button 3"));
panel.add(new JButton("Long-Named Button 4"));
panel.add(new JButton("Button 5"));
...
Remarque : une des deux dimmensions peut être à zéro, dans ce cas, la dimension nulle (ligne ou colonne) pourra accepter un nombre indéterminé d'objets. Par exemple l'instruction de création du gridLoyout de l'exemple ci-dessus pourrait être remplacée par new GridLayout(0,2) (nombre de lignes=0, nombre de colonnes=2).
On peut également de refuser le positionnement automatique des composants. Pour cela on affecte la valeur null au layout et on utilise la méthode setBounds par exemple pour positionner le composant au pixel près (en coordonnées relatives au conteneur direct du composant).
... JPanel panel = new JPanel(); panel.setLayout(null); JButton button1 = new JButton("Bouton en positon (10,10)"); JButton button2 = new JButton("Bouton en positon (200,150)"); panel.add(button1); panel.add(button2); button1.setBounds(10,10,250,30); button2.setBounds(200,150,250,30); ...
Lorsqu'un programme utilise des gestionnaires de positionnement, en cas de changement de dimension du conteneur principal (la fenêtre par exemple), les positions de composants, et souvent leur taille également, seront modifiées.
Bon nombre de composants d'IHM peuvent contenir une image, c'est le cas des menus, des boutons et des listes.
Java propose en standard la classe ImageIcon, qui, comme son nom l'indique, est chargée de représenter en mémoire une image. Un objet ImageIcon peut être instancier via une URL, comme l'indique un de ses constructeurs :
public class ImageIcon ... {
...
/** constructeur
* Création d'un ImageIcon à partir d'une URL spécifiée.
*/
ImageIcon(URL location) { ... }
...
}
Nous utiliserons donc ce constructeur très générique. Exemple de chargement d'une image :
...
java.net.URL iconURL =
ClassLoader.getSystemResource("images/image_1.jpg");
ImageIcon icon = new ImageIcon(iconURL);
...
Notez l'utilisation de la méthode static getSystemResource, une méthode de ClassLoader qui utilise le classpath pour trouver le fichier (nom de l'image), ainsi nous n'avons pas à spécifier le nom complet du chemin menant au fichier (the fully qualified path name).
Le plus simple pour afficher une image est d'utiliser un JLabel. En effet, ImageIcon n'est pas un composant visuel.
C'est donc un JLabel qui fera office de "conteneur" responsable de l'affichage de l'image. Un JLabel dispose d'une méthode setIcon tout à fait adaptée à nos besoins.
Voici comment nous pouvons le configurer pour les besoins de l'application.
...
// création d'un tableau pouvant contenir 8 images
ImageIcon[] images = new ImageIcon[8];
...
java.net.URL iconURL =
ClassLoader.getSystemResource("images/image_1.jpg");
images[0] = new ImageIcon(iconURL);
...
// Création d'un label pour l'affichage d'une image.
iconLabel = new JLabel();
iconLabel.setHorizontalAlignment(JLabel.CENTER);
iconLabel.setVerticalAlignment(JLabel.CENTER);
iconLabel.setText("");
...
// affiche la première image du tableau images
iconLabel.setIcon(images[0]);
...
Voici une maquette de l'IHM du jeu :
Le contentPane de la fenêtre principale est privé de layout, ses deux composantes (deux JPanel) sont positionnées selon des coordonnées fixées à l'avance.
L'interface (le panel central) est divisée en 2 parties : l'une se charge de la vue et l'autre de contrôler la vue.
L'évolution du jeu est gérée par 2 points de contrôle : l'action sur un bouton et la commande Jeu/Nouveau.
Le code source pour bien commencer (à copier dans un nouveau répertoire projet) : JeuPendu.java, JeuPenduGUI.java, Console.java, JeuPenduTextUI.java et le dictionnaire motsPendu.txt.
Les images : images-pendu.tar.
Implémenter les zones et méthodes marquées 'A FAIRE'
Faire en sorte que le joueur ne puisse pas sélectionner plus d'une fois la même lettre.
Modifier le fait de pourvoir paramétrer le nombre de coups perdus admis (entre 5 et 10). Dans ce cas, les premières images de constructions du pendu seront ignorées.
Il existe deux grands types de format de fichier : "texte" et "binaire". Est qualifié de "fichier texte", tout fichier composé exclusivement de caractères dédiés ou compatible avec une impression standard, un affichage en mode console.
Par exemple un code source est un fichier texte, par contre un fichier MS World 2000 n'est pas un fichier texte, car il comporte des caractères non affichables (information sur la police, objets incorporés, et autres informations plus ou moins documentées par l'éditeur)
ATTENTION : On appelle format de fichier, la structure de son contenu , et non la valeur de son extension . Un fichier portant le nom de "document.txt" est certainement un fichier de type texte, pouvant être lu par un éditeur quelconque, mais rien ne le garantit ! L'inverse est également vrai : un fichier nommé "document.jpg" est certainement un fichier de type binaire (ici une image), mais ce n'est qu'une convention.
Le format texte
Contrairement au fichier binaire, le contenu d'un fichier texte est constitué de valeurs correspondant, pour la majorité, à des codes de caractères affichables (plus certains interprétables comme \n (fin de ligne), \t (tabulation)...).
Les fichiers au format texte sont le plus souvent structurés soit par :
des lignes non formatées, séparées par un symbole de fin de ligne.
des lignes formatées CSV ( Comma Separated Value ) une ligne est contituée d'une suite de champs séparés par un caractère spécial (une virgule par exemple) et d'un symbole de fin de ligne.
des balises , c'est le cas des fichiers XML ( eXtensible Markup Language ) - non étudié ici.
des mots clés d'un langage et une grammaire (comme le code source java) - non étudié ici.
Exemple document.txt
[kpu@kpu seance-15]$ cat document.txt Bonjour, futur informaticien, nous vous souhaitons bonne aventure.
Version hexadécimale
[kpu@kpu seance-15]$ hexdump document.txt 0000000 6f42 6a6e 756f 2c72 660a 7475 7275 6920 0000010 666e 726f 616d 6974 6963 6e65 0a2c 6f6e 0000020 7375 7620 756f 2073 6f73 6875 6961 6f74 0000030 736e 0a20 6f62 6e6e 2065 7661 6e65 7574 0000040 6572 0a2e 0000044
Version caractère
[kpu@kpu seance-15]$ hexdump -c document.txt 0000000 B o n j o u r , \n f u t u r i 0000010 n f o r m a t i c i e n , \n n o 0000020 u s v o u s s o u h a i t o 0000030 n s \n b o n n e a v e n t u 0000040 r e . \n 0000044
A titre d'information, il existe un outil bien pratique, nommé file , qui analyse le contenu d'un fichier pour en extraire des informations sur son type. Exemple :
$ file document.txt document.txt: ASCII text $ file zoo.png zoo.png: PNG image data, 446 x 182, 8-bit/color RGB, non-interlaced $ file TestGE.java TestGE.java: UTF-8 Unicode Java program text
Java considère un fichier comme un cas particulier d'un flux ( stream ).
![]() | Flux |
|---|---|
Un flux est une séquence de caractères ou d'octets voyageant d'une origine vers une destinaton. Un programme qui écrit dans un fichier est à l'origine d'un flux (ou producteur ). Un programme qui lit le contenu d'un répertoire, d'un fichier, ou de toute autre ressource est considéré comme une destination (ou consommateur ), qui reçoit progressivement le contenu du flux. |
Java, pour des raisons pratiques, propose deux types de flux : les InputStream/OutPutStream et les Reader/Writer . Les premiers ( Stream ) sont utilisés pour la gestion de fichiers dit binaires, les Reader/writer sont spécialisés pour la gestion de flux de caractères UNICODE (16-bit) et Local (8-bit).
Notez qu'il existe de nombreuses classes de flux spécilisées (plus de 70 !), et certaines sont dans des packages utilitaires comme java.util.zip . Sachez également qu'il existe des passerelles pour passer d'une logique de flux d'octets à celle de flux de caractères (16-bit), ce sont les classes : InputStreamReader pour convertir un InputStream en un Reader et OutputStreamWriter pour convertir un OutputStream en un Writer .
Vous trouverez ici, une traduction d'un extrait d'un tutorial chez Sun , présentant la hiérarchie des classes I/O.
![]() | Connaître les caractéristiques d'un fichier |
|---|---|
Les caractéristiques d'un fichier peuvent être interrogées en utilisant la classe Pour en savoir plus sur cette classe, retrouvez l'API API File et des exemples ici :http://java.developpez.com/faq/java/?page=langage_fichiers |
Les flux de caractères sont associés à des classes spécialisées dans la lecture des données ( reader ) et dans leur écriture ( writer ).
Les principales étapes de gestion d'un fichier sont :
Ouverture du flux
Exploitation (par lecture ou écriture)
Fermeture du flux
1 - Ouverture d'un fichier
// ouvre un fichier en lecture FileReader fr = new FileReader(nomFic); // passe par un buffer pour simplifier la lecture du fichier BufferedReader buf = new BufferedReader(fr);
2 - Exploitatin en lecture séquentielle du fichier
// déclaration d'une chaîne de caractères
// afin de stocker la chaîne courante
// à chaque tour dans la boucle.
String ligne;
// lecture de la première ligne du fichier
ligne = buf.readLine();
// tant que la fin de fichier n'est pas atteinte
while (ligne != null) {
// faire quelque chose avec ligne
// ...
// à la fin du corps de la boucle, lire la ligne suivante
ligne = buf.readLine();
}
3 - Fermeture fichier
fr.close();
1 - Ouverture d'un fichier en écriture
// ouvre un fichier en écriture (1ère version) FileWriter out = new FileWriter(nomFic); /* autre version avec choix du jeu de caractères (ici ISO Latin Alphabet No. 1 - ISO-LATIN-1) OutputStreamWriter out = new OutputStreamWriter(new FileOutputStream(nomFic), "ISO-8859-1"); */ // passe par un buffer pour simplifier l'écriture dans le fichier PrintWriter pw = new PrintWriter(out);
Exemple d'écriture dans le fichier (relativement à la position courante dans le fichier)
// déclaration d'une chaîne de caractères // afin de stocker la chaîne à écrire String ligne = "coucou"; // écriture de la ligne dans le fichier pw.write(ligne);
Exemple de fermeture fichier
pw.close();
Ecrire un programme qui recoit un nom de fichier texte en argument du main et qui affiche :
la première ligne non vide du fichier
la dernière ligne non vide du fichier
le nombre total de lignes (vide ou non).
Exploitation d'un fichier CSV, et transformation en XML : http://www.linux-france.org/prj/edu/archinet/DA/tpXML/
On souhaite gérer un fichier de contacts (contacts.txt)
Une fiche contact est caractérisée par un nom, prénom, adresse rue, code postal, ville, pays, tel et email.
Les caractéristiques d'une fiche ont placées sur la même ligne. S'il y a 2 fiches, alors le fichier contiendra 2 lignes, et ainsi de suite. Exemple :
Durand;Michel;35 rue du lavoir;38000;Grenoble;France;0123456789;mdurand@nullepart.com Valerian;Denise;250 avenue Louise;1050;Bruxelles;Belgique;00 32 (0)2 345 67 89;vale@belgeunefois.com
On souhaite produire une application permettant dans un premier temps de présenter, une à une, les fiches contact du fichier. Voici une maquette :
Première analyse. Nous concevrons trois classes : l'IHM (Interface Homme Machine) - une JFrame - , un contrôleur ( NavigatorContactFic ) qui détient une collection d'objets Contact , et une classe Contact représentant la structure type d'une fiche contact.
La relation NavigatorContactFic --> Contact dénote une liste nommée lesContacts (un objet de type ArrayList ou Vector ). C'est un attribut privé de la classe NavigatorContactFic . Cet attribut est créé et valorisé dans le constructeur de NavigatorContactFic .
Donc, à la création, le fichier est lu et son contenu placé en mémoire sous forme d'une collection d'objets de type Contact.
Pour simplifier, nous considérons provisoirement qu'une fiche est caractérisée par un nom et un prénom (si on sait le faire pour 2 propriétés, on saura le faire pour n=8).
A FAIRE : Concevoir les classes Conact et NavigatorContactFic, de sorte que le programme de test suivant puisse fonctionner :
import java.io.*;
public class Test {
public static void main(String[] a){
try {
NavigatorContactFic nav = new NavigatorContactFic(a[0]);
System.out.println("-- Première fiche");
System.out.println(nav.premier());
System.out.println("-- Dernière fiche");
System.out.println(nav.dernier());
System.out.println("-- Voici les 3 premieres");
System.out.println(nav.premier());
System.out.println(nav.suivant());
System.out.println(nav.suivant());
System.out.println("-------------");
System.out.println("-- Voici les \"3 précédentes\"");
System.out.println(nav.precedent());
System.out.println(nav.precedent());
System.out.println(nav.precedent());
System.out.println("-------------");
}
catch (FileNotFoundException e) {
System.out.println("Fichier introuvable");
}
catch (IOException e) {
System.out.println( e );
}
}
}
Sur le fichier de données suivant ( doc.txt) :
Meyer;Bertrand Goodwill;James Kay;Michael
La commande java Test doc.txt , produit le résultat suivant :
$ java Test doc.txt -- Première fiche Nom : Meyer prenom : Bertrand -- Dernière fiche Nom : Kay prenom : Michael -- Voici les 3 premieres Nom : Meyer prenom : Bertrand Nom : Goodwill prenom : James Nom : Kay prenom : Michael ------------- -- Voici les "3 précédentes" Nom : Goodwill prenom : James Nom : Meyer prenom : Bertrand Nom : Meyer prenom : Bertrand ------------- $
Remarquer que si la fiche courante est la première, la précédente est elle-même (même logique pour la fiche suivante de la dernière fiche).
Modifier la solution proposée, afin qu'elle prenne en compte les huit propriétés énoncées précédemment. Voici un fichier contenant plusieurs fiches : contacts.txt .
Implémenter les méthodes isLast, isFirst , nbContacts et sauvegarde :
isFirst : rend vrai si la fiche courante est la première de la liste, faux sinon.
isLast : rend vrai si la fiche courante est la dernière de la liste, faux sinon.
nbContacts: rend le nombre de contacts dans la collection (initialement le nombre de fiches dans le fichier).
sauvegarde : place le contenu de la liste dans le fichier initial.
Concevoir un application en mode graphique, fidèle à la maquette présentée.
La fonction première d'une boîte de dialogue est de paramétrer un service offert par le système. Cela peut être, par exemple, une fonction d'exportation de données, une sauvegarde, ou une confirmation de suppression.
Java propose en standard des fonctions de boîte de dialogue : tutoriel d'utilisation des boîtes de dialogue de JOptionPane.
Nous présentons ici une utilisation de la classe JDialog.
Nous souhaitons ajouter au programme de gestion de contacts, la possibilité d'ajouter un contact, vous programmerez la modification et la suppression en exercice.
Nous commençons par ajouter une commande accessible par un item de menu.
Et le code associé :
// attributs de classe
static final String ACTION_AJOUTER_CONTACT = "AjouterContact";
...
//... dans init
...
JMenu menu = new JMenu("Contacts");
menu.setMnemonic(KeyEvent.VK_C);
JMenuItem mnAjouterContact = new JMenuItem(ACTION_AJOUTER_CONTACT,
KeyEvent.VK_A);
mnAjouterContact.setActionCommand(ACTION_AJOUTER_CONTACT);
mnAjouterContact.addActionListener(this);
menu.add(mnAjouterContact);
...
menuBar.add(menu);
...
Ensuite, programmons l'événement. L'algorithme est simple, et facilement généralisable :
1: création d'un objet Contact 2: création d'un objet boîte de dialogue. On lui passe la référence à notre objet Contact nouvellement créé. 3: affichage de la boîte de dialogue en MODE MODAL 4: après la fermeture de la boîte de dialogue, on l'interroge afin de savoir s'il est utile de poursuivre l'action. 4.1 : l'utilisateur n'a pas abandonné, on exploite l'objet dont la référence a été passée en argument de la boîte de dialogue. Ceci se passe en deux temps : 4.1.1 : Demande de confirmation 4.1.2 : Exécution ou non exécution de l'opération 4.2 : l'utilisateur a abandonné : aucune action n'est entreprise.
En voici une traduction :
if (action.equals(ACTION_AJOUTER_CONTACT)) {
Contact c = new Contact("", "");
DailogAjouterContact dac = new DailogAjouterContact(this, c);
dac.setVisible(true);
// à ce niveau d'étape, l'utilisateur a fermé la boîte de dialogue
if (!dac.isCancel()) {
int res = JOptionPane.showConfirmDialog(null,
"Confirmez-vous la création ?", "Confirmation",
JOptionPane.YES_NO_OPTION);
if (res == JOptionPane.OK_OPTION) {
JOptionPane.showMessageDialog(this, "L'objet " + c
+ " va être sauvegardé.");
// faire quelque chose
}
}
// else aucune action
}
Nous avons vu qu'elle reçoit en argument un objet Contact. De plus notre boîte de dialogue sera modal, c'est à dire qu'elle focalise les saisies utilisateur de l'application (elle garde le focus). Le constructeur de notre boîte appelera un des constructeurs de JDialog, classe dont elle hérite.
public class DailogAjouterContact extends JDialog implements ActionListener {
private Contact contact;
private boolean cancel;
...
public DailogAjouterContact(JApplication app, Contact c) {
super(app, "Ajouter un contact", true);
// le premier argument référence la fenêtre à qui
// la boîte de dialogue prend le focus.
// le deuxième rag. est le titre de la boîte de dialogue.
// le dernier arg. est un booléen pour activer le comportement
// modal de la fenêtre.
contact = c;
cancel = true;
init();
setSize(300,100);
setLocationRelativeTo(null);
// centre la boîte
}
Nous lui ajoutons quelques JTextField et deux boutons : un pour valider et un autre pour annuler :
... private JTextField nom; private JTextField prenom; private JButton btCancel; private JButton btValider; ...
La création et le positionnement sont réalisés dans le corps de la méthode init:
private void init() {
JPanel panel = new JPanel();
panel.setLayout(new GridLayout(2,0));
panel.add(new JLabel("Nom"));
panel.add(new JLabel("Prénom"));
getContentPane().add(panel, BorderLayout.WEST);
panel = new JPanel();
panel.setLayout(new GridLayout(2,0));
nom = new JTextField("Nom : ");
prenom = new JTextField("Prénom : ");
nom.setText(contact.getNom());
prenom.setText(contact.getPrenom());
panel.add(nom);
panel.add(prenom);
getContentPane().add(panel, BorderLayout.CENTER);
btValider = new JButton("Valider");
btCancel = new JButton("Abandonner");
btValider.setActionCommand(ACTION_VALIDER);
btCancel.setActionCommand(ACTION_CANCEL);
btValider.addActionListener(this);
btCancel.addActionListener(this);
panel = new JPanel();
panel.add(btValider);
panel.add(btCancel);
getContentPane().add(panel, BorderLayout.SOUTH);
}
Ce qui donne :
La propriété cancel étant positionnée à vrai (true) dans le constructeur, la seule chose à laquelle nous devons faire attention est la validation.
La boîte de dialogue ne devrait retourner qu'un objet valide à l'appelant. La valeur de la propriété cancel sera placée à false, QUE SI les données saisies par l'utilisateur sont conformes. C'est alors que nous donnons les valeurs des données saisies (ou proposées) par l'utilisateur aux accesseurs de l'objet Contact pris en charge par la boîte de dialogue.
public void actionPerformed(ActionEvent e) {
String action = e.getActionCommand();
if (action.equals(ACTION_VALIDER)){
if (verification()) {
cancel = false;
contact.setNom(nom.getText());
contact.setPrenom(prenom.getText());
setVisible(false);
}
else {
JOptionPane.showMessageDialog(this, "Erreur de saisie");
}
}
else if (action.equals(ACTION_CANCEL)){
setVisible(false);
}
}
La méthode verification vérifie que le nom et le prénom ne soit pas vide :
/**
* @return true si le nom et prenom sont renseignés
*/
private boolean verification() {
return (!"".equals(nom.getText().trim()) &&
!"".equals(nprenom.getText().trim()));
}
La méthode isCancel ne fait que retourner la valeur de la variable privée cancel:
public boolean isCancel() {
return cancel;
}
Nous venons de présenter un façon simple, mais très facilement personnalisable, de concevoir et de programmer une boîte de dialogue héritant de JDialog.
Nous avons personnalisé le constructeur afin de fournir un objet Contact à la boîte de dialogue. L'action de validation (un bouton) valorise l'objet Contact à partir des composants de l'interface.
Les fichiers : gestionContacts.tgz.
Les programmes Java disposent d'un pont logiciel pour s'interfacer avec un SGBDR, représenté par une API nommé JDBC ( Java Database Connectivity ). Par l'intermédiaire de JDBC, vous pouvez programmer des créations de tables, des insertions et modifications de valeurs et aussi des requêtes, le tout dans un contexte de transactions avec gestion d'exceptions.
L'API JDBC se trouve dans java.sql et les pilotes doivent implémenter l'interface java.sql.Driver.
Il existe quatre types de pilotes jdbc :
Type 1 : Pont jdbc-odbc (livré en standard, idéal comme premier driver sous Windows)
Type 2 : API native + un peu de java.
Type 3 : Comme type 2 mais avec un protocole réseau tout en java.
Type 4 : Protocole natif 100% java.
Les éditeurs de SGBDR proposent leurs propres pilotes JDBC.
L'interaction à un système de gestion de base de données réquiert en général au moins quatre étapes :
Chargement du pilote
Etablissement de la connexion
Exécution d'une requête
Exploitation des résultats
Nous présentons ci-dessous chacune de ces étapes.
On charge généralement le pilote par son nom. Ci-dessous, un exemple de programme chargeant un pilote défini sous forme de chaînes de caractères.
final String driverPostgreSql = "jdbc.postgresql.Driver";
// driver PostgreSql : http://jdbc.postgresql.org/
final String driverOdbc = "sun.jdbc.odbc.JdbcOdbcDriver";
// driver odbc inclus dans le jdk
// (pratique sous Windows pour SQL Server, Access...)
final String driverHsql = "org.hsqldb.jdbcDriver";
// driver Hypersonic SQL
final String driverMySQL = "com.mysql.jdbc.Driver";
// driver MySQL : http://www.mysql.com/products/connector/
String driver = driverHsql;
Class.forName(driver).newInstance();
// Autochargement du driver
Les pilotes JDBC sont à télécharger directement chez les éditeurs de SGBDR. Par exemple :
Pilote JDBC pour PostgreSQL http://jdbc.postgresql.org/
Pilote JDBC pour MySQL http://www.mysql.fr/products/connector/
Puis à rendre accessible dans le chemin des classes (classpath). Sous Eclipse, vous pouvez créer un répertoire bin dans lequel vous placez le ou les
drivers en question (un fichier ayant l'extension .jar) que vous ajouter ensuite à votre buildpath (via un clic droit...)
Une fois le driver chargé en mémoire, nous obtenons (si tout va bien) une connexion via la méthode de classe getConnection() de DriverManager
Connection con = DriverManager.getConnection(URL, "user", "passwd"); // URL : url de connexion de la forme jdbc:sous-protocole:sous-nom // sous-protocole:identification du pilote // sous-nom : informations nécessaires au pilote // pour la connexion (chemin, port, nom) // "passwd" : Mot de passe // "user" : Nom de l'utilisateur référencé par la base
Exemple
//final String driver = "org.hsqldb.jdbcDriver";
// final String url = "jdbc:hsqldb:/home/kpu/hsql/refuge/refuge";
final String driver = "com.mysql.jdbc.Driver";
// driver disponible ici : http://www.mysql.com/products/connector/
final String url = "jdbc:mysql://localhost/refuge";
final String user = "sa";
final String password = "secret";
Connection con = null;
try {
Class.forName(driver).newInstance();
con = DriverManager.getConnection(url, user, password);
...
L'exécution d'une requête SQL s'effectue via un objet de la classe java.sql.Statement . C'est l'objet Connection qui nous fournira une référence d'objet Statement (à ne pas instancier directement ). Exemple :
Statement stat = con.createStatement();
L'accès aux données peut être sensible ou non aux accès concurrents et la navigation dans le modèle peut être possible ou non (forward only).
Statement stat = con.createStatement();
Statement stmt = con.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,
ResultSet.CONCUR_READ_ONLY);
// autre exemple, une source de données insensible aux changements concurrents
Statement stmt = con.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,
ResultSet.CONCUR_READ_ONLY);
On distingue deux types de requêtes : requête d'interrogation et de mise à jour.
Requête d'interrogation avec l'ordre SELECT
La méthode executeQuery( ) de Statement retourne un objet java.sql.ResultSet .
ResultSet rs = stat.executeQuery("SELECT * FROM ANIMAL");
Requête de mise à jour avec les ordres UPDATE, INSERT, DELETE
On utilisera la méthode executeUpdate( ) de Statement .
Cet exemple supprime de la table ENTREPRISES toutes les entreprises de Seine et Marne.
stat.executeUpdate( "DELETE FROM ENTREPRISES WHERE CODEPOST LIKE '77%'");
Le retour d'un ordre executeQuery(...) est un objet de type ResultSet , une collection de lignes constituées de 1 à n attributs (colonnes).
Pour accéder à la première ligne du résultat, il est nécessaire d'appeler la méthode next() , pour passer à la ligne suivante, il suffit d'appeler de nouveau cette méthode, etc.
ResultSet rs = stat.executeQuery("SELECT * FROM ANIMAL");
// Pour accéder à chacun des tuples du résultat de la requête :
while (rs.next()) {
String nom = rs.getString("nom");
java.sql.Date date_nais = rs.getDate("date_nais");
int id = rs.getInt(1);
...
}
Remarque 1 : L'appel à la méthode next() de l'objet Statement est obligatoire avant tout appel aux méthodes permettant d'accéder à une valeur d'un attribut de la ligne courante.
Remarque 2 : Il y a deux façons d'accéder à une valeur d'un attribut (colonne) : 1/ soit par le nom de la colonne , comme par exemple les deux premiers appels de l'exemple. 2/ soit par position, qui commence à la position 1 (et non 0 comme avec les collections), comme le montre le troisième appel.
La méthode executeUpdate( ) de Statement , ne retourne pas un objet java.sql.ResultSet mais retourne le nombre de lignes impactées par l'instruction.
Cet exemple supprime de la table ENTREPRISES toutes les entreprises de Seine et Marne.
int count =
stat.executeUpdate(
"DELETE FROM ENTREPRISES WHERE CODEPOST LIKE '77%'");
System.out.println("Il y a eu " + count + " lignes supprimées.");
Pour des raisons d'efficacité (compilation de la requête côté sgbdr) et de souplesse de construction des requêtes paramétrés (concaténation et placer les valeurs entre quotes ou non), le développeur peut utiliser un objet PreparedStatement pour envoyer des instructions SQL.
L'exemple suivant illustre les deux approches (source : tutoriel jdbc) :
Code Fragment 1:
String updateString = "UPDATE COFFEES SET SALES = 75 " +
"WHERE COF_NAME LIKE 'Colombian'";
stmt.executeUpdate(updateString);
Code Fragment 2:
PreparedStatement updateSales = con.prepareStatement(
"UPDATE COFFEES SET SALES = ? WHERE COF_NAME LIKE ? ");
updateSales.setInt(1, 75);
updateSales.setString(2, "Colombian");
updateSales.executeUpdate():
La deuxième version, bien que plus verbeuse, nous permet de mieux structurer nos instructions et de ne pas gérer la présence ou non de quotes encadrantes.
On constatera l'usage de méthodes setXXX selon le type de l'argument SQL attendu, et que l'appel à executeUpdate se fait sans argument.
De plus, une requête paramétrée est toujours compilée par avance côté SGBDR, ce qui accélère son traitement, surtout en cas d'appels répétés, dans une boucle par exemple.
Le programme ci-dessous utilise une base nommée Refugedb .
Cette base de données contient une table nommée ANIMAL; voici un script de création :
CREATE TABLE ANIMAL (
id INTEGER PRIMARY KEY,
categorie VARCHAR NOT NULL,
nom VARCHAR, race VARCHAR,
sexe CHAR,
date_nais DATE,
id_proprio INTEGER,
present BIT
)
INSERT INTO ANIMAL
VALUES (1,'CRM', 'kiki','berger','M','2000-2-21',21,false)
INSERT INTO ANIMAL
VALUES (2,'CRM','rex','caniche','M','1996-12-2',11,true)
...
Lorsque l'on accède à une base de données, une gestion des exceptions s'avère nécessaire car de multiples problèmes peuvent survenir : le pilote ne peut être chargé (introuvable ?), connexion refusée, requête SQL mal formée... Voici l'exemple complet.
1 import java.sql.*;
2
3 public class TestAnimal {
4 public void test() {
5 final String driver = "com.mysql.jdbc.Driver";
6 final String url = "jdbc:mysql://localhost/Refugedb";
7 final String user = "sa";
8 final String password ="";
9
10 Statement st = null;
11 Connection con = null;
12 ResultSet rs = null;
13 String sql = "";
14 try {
15 Class.forName(driver).newInstance();
16 con = DriverManager.getConnection(url, user, password);
17 st = con.createStatement();
18 sql = "SELECT * FROM ANIMAL";
19 rs = st.executeQuery(sql);
20 System.out.println("ID\tTYPE\tNOM\t\tRACE\t");
21 while (rs.next()) {
22 System.out.print(rs.getInt(1)+"\t");
23 // ATTENTION, les indices commencent à 1.
24 System.out.print(rs.getString(2)+"\t");
25 System.out.print(rs.getString("nom")+"\t\t");
26 System.out.println(rs.getString("race")+"\t");
27 }//while
28 }
29 catch (ClassNotFoundException e) {
30 System.err.println("Classe non trouvée : " + driver );
31 }
32 catch (SQLException e) {
33 System.err.println("SQL erreur : "+
sql + " " + e.getMessage());
34 }
35 catch (Exception e) {
36 System.err.println("Erreur : "+ e);
37 }
38
39 finally {
40 try { if (con != null) { con.close(); } }
41 catch (Exception e) { System.err.println(e); }
42 }
43 }
44 static public void main(String[] arg) {
45 TestAnimal app = new TestAnimal();
46 app.test();
47 }
48 }
Quelques commentaires :
Ligne 15 : Chargement du pilote Hypersonic SQL.
Ligne 16 : Etablissement d'une connexion à la base.
Ligne 17 : Création d'un objet Statement en préparation à l'exécution d'une requête SQL.
Lignes 18 - 27 : Selection de toutes les lignes de la table ANIMAL.
Affichage de quelques attributs (ID, TYPE, NOM et RACE). La condition de poursuite dans le while permet d'avancer à la prochaine ligne et de tester si la fin n'est pas atteinte (rend false alors).
Lignes 29 - 37 : Une gestion des exceptions.
Lignes 39 - 42 : A ne pas oublier, fermeture de la connexion.
Comatibilité entre type Java et SQL, un "x" signifie que la méthode getXXX est compatible avec le type SQL correspondant.
Il est possible, grâce aux méthodes setXXX de mettre à jour des données sans utiliser d'ordres SQL.
Les méthode updateXXX prennent 2 arguments, le premier est la référence à la colonne concernée (index ou nom de colonne) et le second représente la nouvelle valeur à assigner au tuple courant.
...
String nom = rs.getString("nom"); // kiki
rs..updateString("race", nom.toUpperCase());
System.out.print(rs.getString("nom")); // KIKI
...
... [autre modification]
....
rs.updateRow(); // répercution dans la base de données
// ou rs.cancelRowUpdates()
...
La suite sur Sun Guide JDBC...
Avec l'API JDBC, une transaction est pilotée par l'objet Connection.
Par défaut une instruction SQL est exécutée comme une transaction individuelle immédiatement validée dans la base de données.
Ce mode par défaut peut être modifié via la méthode setAutoCommit() afin de regrouper plusieurs instr. SQL, ceci est représenté par le pseudo-code suivant :
Si la transaction est composé d'1 seule instr. SQL Alors
Si laConnection.getAutoCommit() == false Alors
laConnection.setAutoCommit(true)
FSi
Une instr. SQL => une transaction automatique
Sinon
laConnection.setAutoCommit(false)
[série d'instr. SQL]
Si aucune erreur Alors
laConnection.commit()
Sinon
laConnection.rollback()
Fsi
Fsi
Exemple de code Java avec contrôle simple sur exception :
// Permettre le regroupement d'instructions SQL
con.setAutoCommit(false);
try {
Statement stmt = con.createStatement();
stmt.executeUpdate(
"UPDATE COMPTE_A SET SOLDE = (SOLDE - 1000) WHERE IDCLT = 5");
stmt.executeUpdate(
"UPDATE COMPTE_B SET SOLDE = (SOLDE + 1000) WHERE IDCLT = 5");
con.commit();
out.println("Ordre de transfert réussi");
}
catch (Exception e) {
// une erreur est survenue, retour en arrière
try {
con.rollback();
out.println("Ordre de transfert annulé");
}
catch (Exception ignoree) { }
}
En exercice : proposer une autre version Java avec contrôle plus fin qui exploite la valeur de retour de la méthode executeUpdate.
Nous souhaitons faire évoluer notre application de gestion de fiches (de type Client). Nous allons lui permettre de s'interfacer avec un SGBDR.
Comme SGBDR, nous utiliserons HSQLDB, un projet open source.
HSQLDB est un petit système de gestion de bases de données écrit en Java, initialement développé par Thomas Mueller et hsqldb Group .
HSQLDB est très souvent utilisé pour des démonstrations, du maquettage et de petites applications ayant besoin de s'interfacer avec un système de gestion de bases de données relationnel (SGBDR). Sa taille (<300 ko), son prix (gratuit), sa portabilité (Java) et sa rapidité de mise en oeuvre sont ses principaux atouts.
Ajoutons à ces qualités la relation privilégiée de ce système avec le langage Java, et nous avons une solution bien adaptée à un apprentissage de la programmation, portable sous Windows comme sur Linux.
Il existe un projet de portage d'HSQLDB sur une autre plateforme, voir wikipedia
Téléchargez HSQLDB à l'adresse suivante http://hsqldb.sourceforge.net .
Décompressez le fichier dans un répertoire prévu à cet effet (ex: hsqldb).
Pour utiliser hsqldb, vous pouvez :
Administrer une base via l'outil Database Manager livré avec hsqldb.
Utiliser la base via une application cliente écrite en Java.
La base de données que nous allons créer est celle qui correspond à notre fichier Clients .
Nous allons donc créer notre base de données. Pour cela nous utilisons Database Manager, un des outils livrés avec hsqldb (package org.hsqldb.util ).
Lancer l'application
Le répertoire demo contient des fichiers permettant de lancer certains outils. Par exemple le fichier de commandes runManager.bat permet de lancer le Database Manager afin d'interagir avec les bases de données. Ces fichiers de commandes existent aussi pour les environnements Unix, par exemple runManager.sh .
Nous vous invitons à comparer ces deux fichiers, runManager.bat et runManager.sh , leurs différences sont mineures.
Lancer le gestionnaire.
./runManager.sh
Une première fenêtre apparaît :
Elle vous demande :
Le mode dans lequel vous voulez créer ou ouvrir la base de données (Type).
Choisir Standalone , en effet par défaut les données ne sont gérées qu'en mémoire (In-Memory), ce qui signifie que les informations sont perdues à la fermeture de l'application.
En choisissant Standalone, vous utilisez HSQL en mode non partagé. En mode Client/Serveur, nous choisirons Server ou WebServer selon le cas.
Pour la création de la base nous nous contenterons d'une utilisation Standalone (In-Process).
La classe du driver d'hsqldb (Driver)
L'URL de la base de données que vous allez créer ou utiliser
Dans l'url, le nom de la base fait suite à jdbc:hsqldb:file: . Dans notre exemple, la base se nomme dbtest , dans le répertoire
<chemin d'inst. de hsqldb>/data ou /chezmoi/ici/ en cas de chemin absolu.
Le nom de l'utilisateur (User)
Le mot de passe de l'utilisateur (Password)
Vous accédez alors à la fenêtre suivante, permettant de réaliser des opérations SQL sur la base.
Vous avez à votre disposition un fichier ( creercltsdb.sql ) permettant de créer la table client et de la valoriser avec quelques données.
Une fois le fichier rapatrié, ouvrez-le via la commande File->Open Script... de l'application. Le contenu est alors automatiquement placé dans la zone d'édition de Database Manager.
Le fichier creercltsdb.sql contient des ordres de création de tables, ainsi que des insertions. Il ne vous reste plus qu'à déclencher l'interprétation des ordres SQL en cliquant sur le bouton Execute.
Vérifiez le bon déroulement de l'opération en sélectionnant View -> Refresh Tree . La table CLIENTS doit apparaître dans l'arbre de gauche.
... et en exécutant l'ordre de sélection de tous les tuples de la table clients .
Les clients seront l'outil DatabaseManager (dans le rep. demo) et un cours programme en Java.
Scénario de test
Nous lançons hsqldb en mode serveur :
# on se place dans le dossier data (la ou se trouve la base)
cd <chemin d'inst. de hsqldb>/data
# on lance le serveur en fournissant un alias (dbTest)
java -cp ../lib/hsqldb.jar org.hsqldb.Server -database.0 projTest/db -dbname.0 dbTest
A ce niveau, le serveur est lancé et "pointe" sur notre base vide. Nous allons la peupler en utilisant l'outil DatabaseManager.
Lancer Database Manager en mode serveur : jdbc:hsqldb:hsql://127.0.0.1/dbTest
Executer le script : creercltsdb.sql
Créer un répertoire, puis copier le package <chemin d'inst. de hsqldb>/lib/hsqldb.jar à l'intérieur (pour faire simple).
Copier le fichier TestClients.java dans ce répertoire. Extrait :
import java.sql.*;
public class TestClients {
public void test() {
final String driver = "org.hsqldb.jdbcDriver";
final String url = "jdbc:hsqldb:hsql://127.0.0.1/dbTest";
final String user = "sa";
final String password ="";
...
On remarquera que l'on se connecte à la base avec son alias, sur la machine locale (localhost).
Compiler le fichier : javac TestClients.java
Exécuter le programme en spécifiant la bibliothèque hsqldb.jar:
[dans rep de test]$ java -classpath hsqldb.jar:. TestClients max id = 3 ID NOM PRENOM 0 pam lafrite 1 Durand Michel 2 Tagada Nelson 3 Heisenk Martine [dans rep de test]$
OK !
Nous avons vu comment créer et utiliserune base de données pour hsqldb.
Avec Database Manager nous disposons d'un bon outil visuel pour gérer les données d'une base.
![]() | Bon à savoir |
|---|---|
Vous trouverez dans le répertoire Le répertoire hsqldb.jar tient sur une disquette, n'hésitez pas à le transporter chez vous ! (que vous soyez sous Windows ou Linux). |
Nous souhaitons faire évoluer notre application de gestion des ccontacts, en la faisant dépendre non pas d'un fichier mais d'un SGBDR.
Le diagramme de classe ci-dessous montre cette dépendance (à comparer avec le précédent)
Si vous analysez bien la différence entre les deux versions (fichier texte et base de données), vous vous appercevrez qu'elle se situe à deux endroits : au niveau du navigateur de fiches : NavigatorContactFic et NavigatorContactDB et au niveau du Contact (un attribut identifiant id a été ajouté).
Vous remarquerez également - ceci est très important - que ces deux classes ont les mêmes déclarations d'opérations (entête de méthode) .
On effectuera un premier changement sur la version fichier de notre application en ajoutant un attribut id aux caractéristiques d'un contact. La valeur de cet attribut doit être unique pour un contact (pas de doublons).
Donc, du point de vue de l'objet (la classe) qui utilise un navigateur de fiches, seule la construction du navigateur (par new) change. Exemple :
Version avec Fichier texte
...
public JApplication(String titre) throws Exception{
super(titre);
nav = new NavigatorContactFic("document.txt");
init();
contact=nav.courant();
updateView();
}
...
Version avec base de données
...
public JApplication(String titre) throws Exception{
super(titre);
nav =
new NavigatorContactDB("jdbc:hsqldb:file:/chezmoi/ici/labase", "sa","");
init();
contact=nav.courant();
updateView();
}
...
Finalement, pour que ce soit aussi transparent, il faut concevoir une interface commune (un type) entre ces deux classes ( NavigatorContactFic et NavigatorContactDB ). C'est très facile car elles ont les mêmes opérations ! (à quelques aménagements près, comme expliqué plus loin).
Rappelez vous, une interface est une classe déclarant des "méthodes sans leur corps" (des opérations).
import java.io.*;
import java.util.*;
interface NavigatorContact {
public Contact courant();
public boolean isLast();
public boolean isFirst();
public int nbClients();
public Contact suivant();
public Contact precedent();
public Contact premier();
public Contact dernier();
public void ajouter(Contact c);
public void supprimer(Contact c);
public void sauvegarde() throws FileNotFoundException, IOException;
}
Du coup, la déclaration de notre NavigatorContactFic devient :
public class NavigatorContactFic implements NavigatorContact {
...
// comme avant
}
idem pour notre nouveau navigateur :
public class NavigatorContactDB implements NavigatorContact {...}
La méthode sauvegarde aura un corps vide dans NavigatorContactDB , car toutes les opérations mis à jour seront immédiatement répercutées sur la base (via des requêtes SQL)
Dans la classe JApplication, la déclaration du navigateur est :
// avant : private NavigatorContactFic nav;
private NavigatorContact nav;
Bon, il ne vous reste plus qu'à implémenter cela, et "coller les morceaux". Ce n'est pas forcemment un travail facile, réalisable en une séance de TP. Ce sera donc l'objet d'un mini-projet de fin d'année.
Bonne programmation.
Ce projet est à rendre à la rentrée de septembre 2005 pour les étudiants prenant l'option DA, au plus tard pour le 09/septembre/2005.
Le projet sera composé de deux parties : rapport et code
Rapport écrit - format HTML - sur 10 points
Critères d'évaluation
Description de l'organisation de votre travail (les étapes, qui a fait quoi)
Diagramme UML de l'ensemble. Vous commenterez les relations.
Compréhensible (pour une grande partie) par un lecteur non informaticien.
Bilan.
Code source sur 10 points
Critères d'évaluation
Code suffisamment bien commenté
Originalité de vos apports personnels
Architecture de l'ensemble
Respect des conventions : Indentation, choix de nommage, mise en page... Utilisez Eclipse !