Programmation Orientée Objets Le Langage JAVA
Transcription
Programmation Orientée Objets Le Langage JAVA
Module IP1 Programmation Orientée Objets Le Langage JAVA Amaury Habrard Université de Saint-Etienne [email protected] Mis à jour par François-Xavier Dupé Université d’Aix-Marseille [email protected] 2 Table des matières 1 Avant propos 7 2 Introduction : le langage JAVA 2.1 Présentation . . . . . . . . . . . . . . . . . . . . . . . . . . 2.2 Historique du langage . . . . . . . . . . . . . . . . . . . . 2.2.1 Quelques mots clés . . . . . . . . . . . . . . . . . . 2.3 Java et la programmation orientée objet . . . . . . . . . . 2.3.1 Le concept d’objets . . . . . . . . . . . . . . . . . . 2.3.2 Indépendance vis à vis de la plateforme . . . . . . 2.4 Structure d’un programme JAVA . . . . . . . . . . . . . . 2.4.1 Que contient un fichier source ? . . . . . . . . . . . 2.4.2 Execution d’un programme Java . . . . . . . . . . 2.4.3 La machine virtuelle . . . . . . . . . . . . . . . . . 2.4.4 La méthode main . . . . . . . . . . . . . . . . . . . 2.4.5 Utilisation d’arguments dans la fonction main . . . 2.5 Installation et utilisation de Java . . . . . . . . . . . . . . 2.6 Types, expressions et structures de contrôle fondamentales 2.6.1 les types primitifs . . . . . . . . . . . . . . . . . . La notion de type . . . . . . . . . . . . . . . . . . le type entier (int) . . . . . . . . . . . . . . . . . . Les flottants/réels (float ou double) . . . . . . . . Le type caractère (char) . . . . . . . . . . . . . . . Le type booléen (boolean) . . . . . . . . . . . . . . Initialisations et constantes . . . . . . . . . . . . . 2.6.2 Opérateurs et expressions . . . . . . . . . . . . . . Opérateurs arithmétiques . . . . . . . . . . . . . . Opérateurs de comparaison . . . . . . . . . . . . . Opérateurs logiques . . . . . . . . . . . . . . . . . Opérateurs d’affectation . . . . . . . . . . . . . . . Conversion de types . . . . . . . . . . . . . . . . . Opérateur conditionnel . . . . . . . . . . . . . . . Priorité des opérateurs . . . . . . . . . . . . . . . . 2.6.3 Les structures de contrôle . . . . . . . . . . . . . . if . . . . . . . . . . . . . . . . . . . . . . . . . . . . switch . . . . . . . . . . . . . . . . . . . . . . . . . while . . . . . . . . . . . . . . . . . . . . . . . . . . do-while . . . . . . . . . . . . . . . . . . . . . . . . for . . . . . . . . . . . . . . . . . . . . . . . . . . . Les instructions break et continue . . . . . . . . . 2.6.4 Les éléments spécifiques au langage JAVA . . . . . 2.7 Une première introduction au type String . . . . . . . . . 2.8 Règles d’écriture d’un programme JAVA . . . . . . . . . 3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 9 9 10 10 11 11 11 11 12 12 12 13 14 15 15 15 15 16 16 16 16 17 17 18 18 18 19 19 19 19 20 20 21 22 22 24 25 25 26 4 TABLE DES MATIÈRES . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26 27 27 27 27 28 29 29 29 30 3 La notion d’objets : définition et utilisation 3.1 Introduction au développement objets . . . . . . . . . . . 3.2 La notion de classe . . . . . . . . . . . . . . . . . . . . . . 3.2.1 Exemple de classe : . . . . . . . . . . . . . . . . . . 3.2.2 Création d’un objet à l’aide d’une classe . . . . . . 3.2.3 Exemple d’utilisation dans un programme complet 3.3 La notion de constructeur . . . . . . . . . . . . . . . . . . 3.4 Affectation et comparaison d’objets . . . . . . . . . . . . . 3.4.1 La référence null . . . . . . . . . . . . . . . . . . . 3.5 Le ramasse-miettes . . . . . . . . . . . . . . . . . . . . . . 3.6 L’encapsulation de données . . . . . . . . . . . . . . . . . 3.7 Champs et méthodes statiques . . . . . . . . . . . . . . . 3.7.1 Variables statiques . . . . . . . . . . . . . . . . . . 3.7.2 Les méthodes statiques . . . . . . . . . . . . . . . 3.8 Surdéfinition/surcharge de méthodes . . . . . . . . . . . . 3.8.1 Surdéfinition de constructeurs . . . . . . . . . . . . 3.9 La copie d’objets . . . . . . . . . . . . . . . . . . . . . . . 3.10 Autoréférencement : le mot clé this . . . . . . . . . . . . 3.11 Remarques sur la définition de méthodes . . . . . . . . . . 3.11.1 Méthodes de type procédures et fonctions . . . . . 3.11.2 Les arguments . . . . . . . . . . . . . . . . . . . . 3.11.3 Les variables locales . . . . . . . . . . . . . . . . . 3.11.4 La récursivité . . . . . . . . . . . . . . . . . . . . . 3.12 Les paquetages (packages) . . . . . . . . . . . . . . . . . . 3.12.1 Attribution d’une classe à un paquetage . . . . . . 3.12.2 Utilisation d’une classe située dans une paquetage 3.12.3 Droits d’accès . . . . . . . . . . . . . . . . . . . . . 3.13 Les tableaux . . . . . . . . . . . . . . . . . . . . . . . . . 3.13.1 Déclaration et création . . . . . . . . . . . . . . . . 3.13.2 Tableau en argument ou en retour . . . . . . . . . 3.13.3 Les tableaux à plusieurs indices . . . . . . . . . . . 3.14 Exercices de cours . . . . . . . . . . . . . . . . . . . . . . 3.14.1 Compilateur . . . . . . . . . . . . . . . . . . . . . 3.14.2 Qui suis-je ? . . . . . . . . . . . . . . . . . . . . . . 3.14.3 Compilateur 2 . . . . . . . . . . . . . . . . . . . . 3.14.4 Qui suis-je ? . . . . . . . . . . . . . . . . . . . . . . 3.14.5 Compilateur 3 . . . . . . . . . . . . . . . . . . . . 3.14.6 Mots croisés . . . . . . . . . . . . . . . . . . . . . . 3.14.7 Mots croisés . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31 31 34 34 34 35 36 38 38 39 39 41 41 42 44 45 46 47 49 49 49 50 51 51 51 52 53 53 53 56 57 60 60 61 62 63 63 66 67 2.9 2.8.1 Les identificateurs . . . . . . . . . . . . . . 2.8.2 Commentaires . . . . . . . . . . . . . . . . 2.8.3 Mots clés réservés . . . . . . . . . . . . . . 2.8.4 Remarques globales . . . . . . . . . . . . . 2.8.5 Commentaires sur le rendu d’un programme 2.8.6 Liens sur la programmation JAVA . . . . . 2.8.7 Bibliographie . . . . . . . . . . . . . . . . . Exercices de cours . . . . . . . . . . . . . . . . . . 2.9.1 Compilateur . . . . . . . . . . . . . . . . . 2.9.2 Mots croisés . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Java . . . . . . . . . . . . . . . TABLE DES MATIÈRES 4 Les 4.1 4.2 4.3 concepts d’Héritage et de Polymorphisme Présentation et définition . . . . . . . . . . . . Construction et initialisation des objets dérivés Redéfinition, surdéfinition et héritage . . . . . . 4.3.1 Redéfinition . . . . . . . . . . . . . . . . 4.3.2 La surchage . . . . . . . . . . . . . . . . 4.3.3 En résumé . . . . . . . . . . . . . . . . . 4.4 Le polymorphisme . . . . . . . . . . . . . . . . 4.4.1 Tableaux hétérogènes d’objets . . . . . . 4.4.2 Règles du polymorphisme en Java . . . 4.5 La super-classe Object . . . . . . . . . . . . . . 4.6 Accès par membres protégés : protected . . . 4.7 Méthodes et classes finales . . . . . . . . . . . . 4.8 Les classes abstraites . . . . . . . . . . . . . . . 4.9 Les interfaces . . . . . . . . . . . . . . . . . . . 4.9.1 Définition et mise en œuvre . . . . . . . 4.9.2 Intérêt des interfaces . . . . . . . . . . . 4.10 Connaı̂tre la classe et les types d’un objet . . . 4.10.1 L’opérateur instanceof . . . . . . . . . 4.10.2 La class Class . . . . . . . . . . . . . . 4.11 Exercices de Cours . . . . . . . . . . . . . . . . 4.11.1 Classes abstraites - classes concrètes . . 4.11.2 Compilateur . . . . . . . . . . . . . . . 4.11.3 Qui suis-je ? . . . . . . . . . . . . . . . . 4.11.4 Vrai ou Faux ? . . . . . . . . . . . . . . 5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69 69 72 74 74 75 75 75 77 78 78 80 80 80 82 82 83 83 83 84 85 85 86 88 88 5 La gestion d’exceptions 5.1 Présentation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.2 Gestion de plusieurs exceptions et transmission d’informations 5.3 Dérivation et redéclenchement d’exceptions . . . . . . . . . . . 5.4 Le bloc finally . . . . . . . . . . . . . . . . . . . . . . . . . . 5.5 Les exceptions standard . . . . . . . . . . . . . . . . . . . . . . 5.6 Exercices de Cours . . . . . . . . . . . . . . . . . . . . . . . . . 5.6.1 Vrai ou Faux ? . . . . . . . . . . . . . . . . . . . . . . . 5.6.2 Mots croisés . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89 89 90 93 95 96 97 97 98 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99 99 99 99 99 99 100 100 100 100 101 101 101 101 101 101 102 102 6 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Les chaı̂nes de caractères en Java 6.1 Le type String . . . . . . . . . . . . . . . . . . . . . . . 6.1.1 Les chaı̂nes de caractères sont des objets . . . . . 6.1.2 Affichage d’une chaı̂ne . . . . . . . . . . . . . . . 6.1.3 Longueur d’une chaı̂ne . . . . . . . . . . . . . . . 6.1.4 accès aux caractères . . . . . . . . . . . . . . . . 6.1.5 Concaténation : l’opérateur + . . . . . . . . . . . 6.1.6 Recherche dans une chaı̂ne : la méthode indexOf 6.1.7 La comparaison de chaı̂nes . . . . . . . . . . . . Tester l’égalité de deux chaı̂nes . . . . . . . . . . Comparer deux chaı̂nes . . . . . . . . . . . . . . 6.1.8 Modification d’un caractère . . . . . . . . . . . . 6.1.9 Extraction de sous-chaı̂nes . . . . . . . . . . . . . 6.1.10 Passage en majuscules/minuscules . . . . . . . . 6.1.11 Conversion d’une chaı̂ne vers un type primitif . . 6.1.12 Conversion d’un type primitif vers une chaı̂ne . . 6.1.13 Chaı̂nes et tableaux de caractères . . . . . . . . . 6.1.14 Tableau de chaı̂nes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 TABLE DES MATIÈRES 6.2 6.3 La classe StringBuffer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102 La classe StringTokenizer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102 7 Les entrées/sorties 7.1 Les fichiers, la classe File 7.1.1 Les champs . . . . 7.1.2 Les constructeurs . 7.1.3 Les méthodes . . . 7.2 Les flux . . . . . . . . . . 7.3 Lecture/écriture . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105 105 105 105 105 106 107 8 Quelques éléments de l’API 8.1 La classe Math . . . . . . . . . . . . . . . . . . . 8.2 Les classes enveloppes pour les valeurs primitives 8.3 Les collections . . . . . . . . . . . . . . . . . . . . 8.3.1 Définition . . . . . . . . . . . . . . . . . . 8.3.2 ArrayList . . . . . . . . . . . . . . . . . . L’ancienne classe Vector . . . . . . . . . 8.3.3 Les ensembles . . . . . . . . . . . . . . . . 8.3.4 Les algorithmes . . . . . . . . . . . . . . . Recherche de maximum ou minimum . . . Algorithme de tris et mélanges . . . . . . 8.3.5 Les tables associatives (HashMap) . . . . . 8.4 Divers . . . . . . . . . . . . . . . . . . . . . . . . 8.4.1 Eléments disponibles à partir de Java 1.5 Boucle for améliorée . . . . . . . . . . . . 8.4.2 Généricité . . . . . . . . . . . . . . . . . . 8.4.3 Réflection et Manipulation des types . . . 8.5 Les autres éléments de l’API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109 109 109 110 110 110 112 112 114 114 115 116 119 119 119 119 119 121 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Chapitre 1 Avant propos Ce polycopié de cours a pour objectif d’introduire les concepts fondamentaux de la programmation objet à l’aide du langage java. Il est composé de 4 chapitres de cours et de 3 chapitres présentant des éléments de la bibliothèque du langage. A l’issue de chaque chapitre de cours, un ensemble d’exercices originaux sont proposés. Ces exercices sont complémentaires au exercices de travaux dirigés et au travaux pratiques. Ils ont pour objectif de faire travailler les notions vues en cours sous une forme différente et d’insister sur le vocabulaire spécifique à java ou à la programmation objets, il est donc fortement conseillé de chercher sérieusement ces exercices. Voici un bref descriptif des différents chapitres : — Chapitre 2 : chapitre d’introduction présentant le langage dans sa globalité, son historique et les principes de bases pour écrire un premier programme. On y détaille tous les types primitifs, les structures de contrôles et quelques structures fondamentales du langage. On présente également les conventions à suivre lorsque l’on programme en Java. — Chapitre 3 : ce chapitre présente la philosophie du langage : l’objet. On détaille les différentes structures permettant de déclarer et de créer les objets ainsi que les implications de chacune d’elles. — Chapitre 4 : on y introduit des concepts fondamentaux de la programmation objet l’héritage et le polymorphisme. Ils font la force de ce type de programmation en permettant notamment une meilleure lisibilité des programmes et une plus grande facilité de réutilisation de l’existant. — Chapitre 5 : dans ce chapitre la gestion d’erreurs via la notion d’exception est présentée. Ce type de gestion est propre au langage java. — Chapitre 6 : détail de la plupart des classes de la bibliothèque permettant de gérer des chaı̂nes de caractères, type fondamental et traiter de manière spécifique en Java. — Chapitre 7 : présentation des éléments permettant de lire des flux et notamment de lire et d’écrire dans des fichiers. — Chapitre 8 : chapitre présentant une partie de la bibliothèque. On parle de l’accès à des fonctions mathématiques, de l’utilisation de listes ou de tables associatives, de quelques algorithmes utilisables, ansi que la généricité. Ce document en est à sa troisième version, il existe de manière certaine des coquilles, des fautes de frappes, des oublis de mot, des erreurs de code involontaire, des copier/coller non pertinents, . . . Dans le but d’améliorer ce document, merci de signaler toutes ces erreurs. Si vous avez quelques suggestions pour améliorer certaines parties, corriger certaines affirmations, elles sont les bienvenues. Bonne lecture et bon travail ! 7 8 CHAPITRE 1. AVANT PROPOS Chapitre 2 Introduction : le langage JAVA 2.1 Présentation Java est une technologie composée d’un langage de programmation orienté objet et d’un environnement d’exécution. Préalablement nommé Oak, il a été créé par James Gosling et Patrick Naughton chez Sun Microsystems avec le soutien de Bill Joy. Le langage Java fut officiellement présenté le 23 mai 1995 au SunWorld. Java est à la fois un langage de programmation et une plateforme d’exécution. Le langage Java a la particularité principale d’être portable sur plusieurs systèmes d’exploitation tels que Windows, MacOS ou Linux. C’est la plateforme qui garantit la portabilité des applications développées en Java. Le langage reprend en grande partie la syntaxe du langage C++, très utilisé par les informaticiens. Néanmoins, Java a été épuré des concepts les plus subtils du C++ et à la fois les plus déroutants, tels que les pointeurs. Les concepteurs ont privilégié l’approche orientée objet de sorte qu’en Java, tout est objet à l’exception des primitives (nombres entiers, nombres à virgule flottante, etc.). Java permet de développer des applications autonomes mais aussi, et surtout, des applications client-serveur. Côté client, les applets sont à l’origine de la notoriété du langage. C’est surtout côté serveur que Java s’est imposé dans le milieu de l’entreprise grâce aux servlets, le pendant serveur des applets, et plus récemment les JSP (Java Server Pages) qui peuvent se substituer à PHP et ASP. Les applications Java peuvent être exécutées sur tous les systèmes d’exploitation pour lesquels a été développée une plateforme Java, dont le nom technique est JRE (Java Runtime Environment Environnement d’exécution Java). Cette dernière est constituée d’une JVM (Java Virtual Machine - Machine Virtuelle Java), le programme qui interprète le code Java et le convertit en code natif. Mais le JRE est surtout constitué d’une bibliothèque standard à partir de laquelle doivent être développés tous les programmes en Java. C’est la garantie de portabilité qui a fait la réussite de Java dans les architectures client-serveur en facilitant la migration entre serveurs, très difficile pour les gros systèmes. Dans le cadre de ce cours notre objectif sera d’étudier les concepts fondamentaux de la programmation objet à l’aide du langage java. Le but est d’acquérir les bases permettant ensuite de développer des applications plus consistantes. 2.2 Historique du langage — Naissance ≡ 1991 : conception d’un langage applicable à de petits appareils électriques (on parle de code embarqué) par la société Sun Microsystem. La syntaxe est proche du C++, utilisant le concept de machine virtuelle. Le code source est traduit dans un langage universel disposant de fonctionnalités communes à toutes les machines. Ce code intermédiaire 9 10 CHAPITRE 2. INTRODUCTION : LE LANGAGE JAVA — — — — — — — — — — — — — 2.2.1 est dit formé de byte code et est compact et portable sur n’importe quelle machine : il suffit qu’elle dispose d’un programme permettant d’interpréter le langage, on parle de machine virtuelle. Ce projet s’appelait Oak. Eté 1992 : première présentation interne des possibilités de Oak. Un appareil appelé ”Star Seven” permet de visualiser une animation montrant Duke, l’actuelle mascotte de Java. 1994 : développement de HotJava, un navigateur internet entièrement écrit en Java capable d’exécuter des applets écrites en byte code. 1995 : lancement officiel de Java 1.0 1996 : lancement du JDK 1.0 et des versions 1.01 et 1.02 du langage JAVA (250 classes dans la bibliothèque). 1998 : version 1.1 du JDK (500 classes). 1999 : version 1.2 JDK que l’on appelle Java2 (1500 classes). 2000 : version de J2SE (Java 2 Standard Edition) 1.3 (1800 classes). 2002 : version de J2SE (Java 2 Standard Edition) 1.4 (2700 classes), applications web et entreprises. 2003 : version de J2EE (Java 2 Entreprise Edition) 1.4. 2004 : version du J2SE 1.5 également appelé J2SE 5.0 ou Java 5 (≈ 3500 classes). 2006 : version du J2SE 1.6 également appelé J2SE 6.0 ou Java 6 (≈ 3800 classes). 2011 : version du J2SE 1.7 également appelé J2SE 7.0 ou Java 7 (≈ 3900 classes). 2014 : version du J2SE 1.8 également appelé J2SE 8.0 ou Java 8 (≈ 4000 classes). Quelques mots clés — JRE : Java Runtime Environnement logiciel permettant d’exécuter des applications java. — JDK : Java Development Kit logiciel permettant de concevoir et d’exécuter des applications java. — J2SDK : Java 2 Software Development Kit même chose que précédemment. — API : Application Programming Interface (interface de programmation) qui définit la manière dont un composant informatique peut communiquer avec un autre. Dans le cas de Java, ce terme désigne une bibliothèque de classes et de fonctionnalités mises à disposition du programmeur. — J2EE : Java 2 Platform, Enterprise Edition Java 2 Platform, Enterprise Edition est une spécification pour le langage de programmation Java de Sun plus particulièrement destinée aux applications d’entreprise. Dans ce but, toute implémentation de cette spécification contient un ensemble d’extension au cadre d’applications Java standard (J2SE, Java 2 standard edition) afin de faciliter la création d’applications réparties. Voici quelques API présentes dans cette extension : Servlets, JSP, JDBC, JAXB, RMI, . . . — javac programme contenu dans le JDK pour compiler des programmes java. — java nom du langage programme contenu dans le JDK ou JRE pour lancer des programmes java. — javadoc programme contenu dans le JDK pour créer automatiquement une documentation HTML à partir de sources java. — jar programme contenu dans le JDK pour compresser un (ou des programmes java) dans un seul fichier. 2.3 La — — — Java et la programmation orientée objet programmation orientée objets se caractérise par 3 points importants : une programmation structurés, fiabilité des logiciels accrue, facilite la réutilisation du code existant. 2.4. STRUCTURE D’UN PROGRAMME JAVA 2.3.1 11 Le concept d’objets En programmation impérative (par exemple le langage C), un programme est formé de différentes procédures et structures de données généralement indépendantes des procédures. En programmation orientée objets, on met en œuvre différents objets. Chaque objet associe des données et des méthodes agissant exclusivement sur les données de l’objet. — On parle de méthodes plutôt que de procédures. — On utilisant indifféremment le mot champ, donnée ou encore attribut. L’encapsulation de données : il n’est pas possible d’agir directement sur les données d’un objet, il est nécessaire de passer par des méthodes associées à l’objet. Ces méthodes jouent le rôle d’interface obligatoire. L’appel d’une méthode peut être vu comme l’envoi d’un message à un objet. Vu de l’extérieur, un objet se caractérise uniquement par ses spécification (données attributs) et ses méthodes. La notion de classe : — elle généralise la notion de type de donnée — elle permet de décrire un ensemble d’objets ayant une structure de données commune et disposant de mêmes méthodes. — Les objets apparaissent comme des variables d’un type de classe donnée, on parle d’instances de classe. La notion d’héritage. Elle permet de définir une nouvelle classe à partir d’une autre. On réutilise cette dernière en bloc et on lui ajoute de nouvelles fonctionnalités. La conception d’une nouvelle classe, qui hérite (récupère) toutes les propriétés et aptitudes de l’ancienne. Il est ainsi possible de s’appuyer sur des réalisations antérieures parfaitement au point et les spécifier à volonté. Ceci facilite donc la réutilisation de code ou de logiciel déjà existant. La programmation objet facilite énormément la réalisation d’interfaces graphiques. Chaque élément de l’interface est vu comme un objet, par exemple un objet fenêtre, plusieurs objets boutons, un objet ascenseur, etc. 2.3.2 Indépendance vis à vis de la plateforme La portabilité du code. Un programme est portable si un même code source peut être exploité dans des environnements différents moyennant une nouvelle compilation. En Java, il existe la notion de machine virtuelle java - la JVM (pour Java Virtual Machine) - qui peut exécuter le même code sur n’importe quelle plateforme en utilisant un code source sous forme de bytecode : un langage machine spécifique à la plateforme Java. La création d’un programme Java s’effectue en 2 temps : une phase de compilation qui transforme le code source en bytecode. Le code est ensuite interprété sur une machine virtuelle Java 2.4 2.4.1 Structure d’un programme JAVA Que contient un fichier source ? Un fichier source porte l’extension .java et contient la définition de classe. La définition d’une classe est définie par 2 accolades. public class Chien{ //code de la classe JAVA } 12 CHAPITRE 2. INTRODUCTION : LE LANGAGE JAVA Une classe contient une ou plusieurs méthodes. Par exemple dans la classe Chien, la méthode aboyer() va contenir des instructions spécifiant la façon dont un chien aboie. Les méthodes sont obligatoirement déclarées dans une classe. public class Chien{ void aboyer() { // code de la methode aboyer où l’on indique comment // la méthode doit ^ etre exécutée } } 2.4.2 Execution d’un programme Java Source On crée un document source avec une extension .java, par exemple Toto.java 2.4.3 Compilation On compile le(s) fichier(s) source(s) avec le programme javac, par exemple javac Toto.java. Si la compilation a réussi, un fichier .class Toto.class est créé, ce fichier est constitué de byte code Execution L’exécution est lancée en utilisant le programme java avec le nom du fichier principal du programme sans l’extension .class. Par exemple java Toto. La machine virtuelle Un programme s’exécute en lançant la JVM sur le fichier principal (Toto.class). Ce fichier contient du byte code et la JVM traduit ce byte code en code machine compréhensible par la plateforme sous-jacente et exécute le programme. Lorsque la JVM démarre elle cherche la classe spécifiée à l’exécution, puis elle recherche une méthode spéciale – la méthode main – qui ressemble exactement à ceci : public static void main(String [] args) { //le code de la methode main } La JVM exécute ensuite tout ce qui se trouve entre les accolades{ } de la méthode main. 2.4.4 La méthode main Toute application java a au moins une méthode main. Attention, pas une par classe, mais au moins une par application. La méthode main correspond à l’endroit où le programme commence à s’exécuter. Voici un exemple de programme complet : public class PremiereAppli{ public static void main(String [] args) { System.out.println("Bonjour à tous"); System.out.println("Je m’appelle toto"); } } 2.4. STRUCTURE D’UN PROGRAMME JAVA 13 1. Enregistrement dans le fichier PremiereAppli.java 2. Compilation javac PremiereAppli.java 3. Lancement du programme java PremiereAppli 4. Le résultat du programme consiste à afficher les 2 lignes suivantes : Bonjour à tous Je m’appelle toto 2.4.5 Utilisation d’arguments dans la fonction main Les arguments de la méthode main sont placés dans le tableau args, qui est un tableau constitué de chaı̂nes. Lors du lancement d’un programme, les arguments se placent à la suite de l’identificateur de classe du programme lancé et sont séparés par un espace. Exemple : java AfficheArguments Le langage Java Voici le programme correspondant : public class AfficheArguments{ public static void main(String [] args) { for(int i=0;i<args.length;i++) { System.out.println("contenu de args[" + i +"] : "+ args[i]); } } } A l’exécution, ce programme affiche le résultat suivant : contenu de args[0] : Le contenu de args[1] : langage contenu de args[2] : Java N.B. Les arguments sont passés sous la forme de chaı̂nes de caractères, si nous voulons des nombres nous devons convertir les arguments. Exemple. public class Addition2Entiers{ public static void main(String [] args) { int entier1, entier2, resultat; entier1=Integer.parseInt(args[0]); entier2=Integer.parseInt(args[1]); resultat = entier1 + entier2; System.out.println("La somme de "+entier1+" et "+entier2+" est : "+resultat); } } Pour lancer le programme : java Addition2Entiers 1 3. Autre exemple. 14 CHAPITRE 2. INTRODUCTION : LE LANGAGE JAVA public class ConversionFrancsEuros{ public static void main(String [] args) { final float taux = 6.55957f; float somme = 0f, resultat; somme=Float.parseFloat(args[0]); resultat = somme / taux; System.out.println("Le resultat de la convertion de "+somme+" francs est "+resultat+" euros."); } } 2.5 Installation et utilisation de Java Pour installer un outil de développement java, il suffit de récupérer un outil de développement (JDK ou SDK) sur http://www.oracle.com/technetwork/java/javase/downloads/index.html, puis de lancer le programme d’installation. Sous linux vous pouvez l’installer par exemple dans /usr/local pour le rendre accessible à tout le monde, ou sous votre compte. Ensuite nous allons principalement utiliser les programmes situés dans le répertoire bin /Chemin Installation/jdk1.8.0 10/bin (/Chemin Installation correspondant au répertoire d’installation de java), par exemple si vous avez récupéré la version 10 du jdk 1.8 - javac, java, javadoc, jar, . . .). Pour faciliter leur utilisation vous pouvez mettre le répertoire bin dans votre variable d’environnement PATH (cf le cours système). Notez que sous certains systèmes linux/unix, certaines distributions java sont pré-installées, mais ne correspondent pas aux versions officielles fournies par Sun Microsystems. Pour écrire nos programmes nous aurons seulement besoin d’un éditeur (par exemple emacs sous linux, ou tout un autre éditeur si vous le souhaitez) et d’un terminal (xterm). Vous pourrez éventuellement avoir de besoin de configurer une variable d’environnement CLASSPATH qui indique les répertoires où sont situés les programmes java. Ce n’est pas obligatoire, par défaut les programmes sont cherchés dans le répertoire courant et il est possible d’indiquer des répertoires en spécifiant des options aux différents programmes disponibles dans le jdk (voire la documentation officielle indiquée dans les liens ci-dessous). Quand vous serez plus expérimenté (par exemple à partir du 4ème TP), vous pourrez essayé d’autres éditeurs facilitant le développement de programmes java tels qu’eclipse ou NetBeans. A vous de les essayer et éventuellement d’en adopter un. Néanmoins, il est d’abord conseillé d’utiliser un éditeur non spécifique pour commencer, puis de passer à un éditeur spécialisé une fois que l’on maı̂trise les bases du langage. Pour ceux qui souhaitent essayer leurs programmes sous windows, la procédure d’installation est la même. Il peut cependant être nécessaire de créer une variable d’environnement supplémentaire. Voici la procédure à suivre. — Vérifier que la variable d’environnement JAVA HOME est spécifiée. — Si ce n’est pas le cas allez dans Menu Démarrer → Settings → Control Panel → System → Advanced, menu variables d’environnement. Entrez le nom de JAVA HOME puis sa valeur (par exemple C:2sdk1.8.0 10/). — Pour vérifier tapez echo %JAVA HOME dans une console MS-DOS. Vous pouvez utiliser la même méthode pour modifier le contenu de la variable PATH ou configurer CLASSPATH. En guise de terminal vous utiliserez alors une console MS-DOS. 2.6. TYPES, EXPRESSIONS ET STRUCTURES DE CONTRÔLE FONDAMENTALES 2.6 15 Types, expressions et structures de contrôle fondamentales Cette partie a pour objectif de présenter rapidement l’utilisation des types, des expressions et des instructions de contrôle en JAVA. Les éléments abordés ne sont pas forcément toujours caractéristiques du langage JAVA mais sont nécessaires pour l’utilisation de n’importe quel langage de programmation. Lorsque certaines situations sont spécifiques au langage JAVA, nous le précisons. 2.6.1 les types primitifs Ils servent à définir les champs (attributs) de toutes les classes que vous pourrez définir, ainsi que les variables que vous pourrez utiliser dans les différentes méthodes. Attention, les types primitifs ne sont pas des classes. La notion de type La mémoire centrale est un ensemble de ” positions binaires ” appelées. Ces bits sont regroupés en octets, un octet correspondant à 8 bits. Les ordinateurs (actuels) ne savent représenter et traiter que des informations représentées sous forme binaire. Il est donc nécessaire de savoir comment une information a été codée pour pouvoir attribuer une signification à une suite de bits d’un emplacement de la mémoire. Il existe 4 catégories de type primitif en JAVA : — nombres entiers — nombres flottants — caractères — booléens le type entier (int) Il sert à représenter les nombres entiers relatifs. Il existe quatre sortes d’entier permettant de représenter des valeurs différentes. Le tableau suivant décrit chacun de ces types avec le nom du type, la taille occupée en mémoire en octets, la valeur minimale et la valeur maximale possible avec la constante correspondante dans le langage. Nom byte Taille 1 short 2 int 4 long 8 Valeur minimale -128 Byte.MIN VALUE -32768 Short.MIN VALUE -2,147,483,648 Integer.MIN VALUE -9,223,372,036,854,775,808 Long.MIN VALUE Valeur maximale 127 Byte.MAX VALUE 32767 Short.MAX VALUE 2,147,483,647 Integer.MAX VALUE 9,223,372,036,854,775,807 Long.MAX VALUE Les constantes peuvent s’écrire sous forme décimale (548, -78, +5), ou sous forme hexadécimale en précisant la valeur de 0x ou 0x (0x1A 0X12), ou octale en faisant précéder la valeur de 0 (032, 05). Une constante entière est forcément de l’un des types int ou long. On peut forcer une constante à être du type long en faisant suivre sa valeur de la lettre l ou L, exemple 25L. Le compilateur rejettera toute constante ayant une valeur supérieure la capacité du type long. 16 CHAPITRE 2. INTRODUCTION : LE LANGAGE JAVA Les flottants/réels (float ou double) Il en existe deux sortes. Le tableau suivant décrit chacun d’eux avec leur nom, la taille occupée en mémoire en octets, la précision correspondant au nombre de chiffres significatifs pris en compte dans la partie décimale, la valeur minimale et la valeur maximale possible avec la constante correspondante dans le langage. Nom float Taille 4 Précision 7 double 8 15 Valeur absolue minimale −1.40239046 × 10−45 Float.MIN VALUE 4.9506564584124654 × 10− 324 Double.MIN VALUE Valeur absolue maximale 3, 40282347 × 1038 Float.MAX VALUE 1, 797693134862316 × 10308 Double.MAX VALUE Pour les constantes, il existe deux notations : — la notation décimale : 12.43 -0.38 -.38 4. .27 — la notation exponentielle : 4.25E4 ou 4.25e+4 pour 4.25 × 104 54.2e-32 pour 54.2 × 10−32 48e13 pour 48 × 1013 Par défaut toutes les constantes créées sont de type double, pour forcer à avoir un type float on fait suivre la valeur de la lettre f, exemple : 12.5f Le type caractère (char) Les caractères, désignés par le type char, sont codés sur 2 octets, notation d’une constante caractère : ’a’, ’B’, ’é’, ’+’. Exemple char lettre=’a’; Certains caractères avec notation spéciale : — ’\b’ pour le retour arrière (backspace) — ’\t’ pour une tabulation — ’\n’ pour un saut de ligne — ’\f’ pour un saut de page — ’\r’ pour un retour chariot — ’\"’ pour un guillemet — ’\’’ pour une apostrophe — ’\\’ pour \. Le type booléen (boolean) Il sert à représenter une valeur logique de type vrai ou faux, il existe deux valeurs possibles pour un booléen en java : true et false (pour vrai et faux). Exemple boolean est ordonne=false;. Attention, en JAVA, les valeurs logiques ne sont pas représentées par des valeurs mais par un type spécifique : le type boolean, toute utilisation de valeur numérique dans des tests provoquera une erreur ! Initialisations et constantes Variables Exemples : int n=15; également équivalent à int n; n=15;. En JAVA, les déclarations peuvent apparaı̂tre à n’importe quel endroit du programme. D’un point de vue méthodologique, il est souvent conseillé d’effectuer la déclaration de toutes les variables (importantes) ensembles au début puis de les initialiser avant de les utiliser. N.B. Les variables n’ayant pas reçu de valeur ne peuvent pas être utilisées sous peine d’aboutir à une erreur de compilation. Il est donc important de penser à initialiser les variables dès leur création. 2.6. TYPES, EXPRESSIONS ET STRUCTURES DE CONTRÔLE FONDAMENTALES 17 Constantes : le mot clé final Pour indiquer qu’une variable ne peut pas être modifiée pendant l’exécution d’un programme, on peut utiliser le mot clé final : final int nombre de mois=12; Toute modification de la variable nombre de mois, de valeur initiale 12, sera rejetée par le compilateur. 2.6.2 Opérateurs et expressions Opérateurs arithmétiques Il existe cinq opérateurs principaux : — + : addition a+b — - : soustraction a-b — / : division a/b — * : multiplication a*b — % : modulo, a % b calcule le reste de la division de a par b. Le problème de la conversion de type : float x; float res; int n; int p; res=n*x+p; La variable n étant de type int, x de type float, le compilateur va convertir n en float puis faire le calcul. Le résultat final sera de type float, comme le montre la Figure 2.1. n x * int + float p long float * float + float float Figure 2.1 – Evaluation de res=n*x+p; La hiérarchie de conversion permettant de ne pas dénaturer la valeur initiale : int → long → float → double. Note : par défaut, pour les types byte, char, short, si un de ces types apparaı̂t dans une expression, la valeur est d’abord convertie en int. Exemple : short p1=1, p2=1 ,p3=1; float x=2; L’évaluation de p1*p2+p3*x est faite comme le montre la Figure 2.2. p1 * int p2 + int * x * float int * float int float p3 float + float Figure 2.2 – Evaluation de p1*p2+p3*x 18 CHAPITRE 2. INTRODUCTION : LE LANGAGE JAVA Opérateurs de comparaison Ils servent à faire des comparaisons relatives et sont principalement utilisés dans les tests conditionnels. — < : inférieur strictement à — <= : inférieur ou égal à — > : supérieur à — >= : supérieur ou égal à — == : égal à — ! = : différent de. Ces opérateurs peuvent s’utiliser avec des expressions : 2 ∗ a > b + 5, x + y < (a + 2) ∗ 5, . . . Note : ordre sur les caractères 0 00 <0 10 < . . . <0 90 <0 A0 <0 B 0 < . . . <0 Z 0 <0 a0 < . . . <0 z 0 . Opérateurs logiques Ces opérateurs permettent de manipuler des valeurs logiques. — ! : négation — & : ”ET” — ∧ : ”OU” exclusif — | : ”OU” inclusif — && : ”ET” avec court-circuit — || : ”OU” inclusif avec court-circuit Exemples : — (a < b) && (c < d), (a < b) & (c < d) : ces deux expressions prennent la valeur true (vrai) si les deux expressions a < b et c < d sont toutes les deux vraies, la valeur false (faux) dans le cas contraire. — (a < b) || (c < d), (a < b) | (c < d) : prend la valeur true si l’une au moins des deux conditions a < b ou c < d est vraie, la valeur false dans le cas contraire. — (a < b) ∧ (c < d) prend la valeur true si une et une seule des deux conditions a < b et c < d est vraie, la valeur false dans le cas contraire. — !(a < b) prend la valeur true si la condition a < b est fausse, la valeur false dans le cas contraire. Cette expression possède en fait la même valeur que a >= b. — Les deux opérateurs && et || possèdent une propriété intéressante : leur second opérande (celui qui figure à droite de l’opérateur) n’est évalué que si la connaissance de sa valeur est indispensable pour décider si l’expression est vraie ou fausse. Par exemple, si on a l’expression (a < b)&&(c < d), on commence par évaluer (a < b), si le résultat est faux on n’évalue pas c < d puisque le résultat est déjà connu. Les opérateurs ∧ et | évaluent toujours les deux opérandes, il est donc plutôt conseillé d’utiliser les opérateurs && et ||. Opérateurs d’affectation — = : exemples c=b+3; c=i;. L’opérateur possède une associativité de gauche à droite, ainsi dans l’expression i = j = 5; on évalue j=5 d’abord, puis i=j, i et j ont à la fin la même valeur 5. Attention aux problèmes de conversion, supposons que nous ayons une variable de type int n et une variable de type float x, l’expression n=x+5; est rejetée par le compilateur. Il faut en fait écrire n=(int) x + 5;. Il est nécessaire de faire une conversion explicite. Les conversions pour lesquelles il n’y a pas besoin de faire une conversion explicite sont les suivantes : — byte → short → int → long → float → double — char → int → long → float → double — L’incrémentation et la décrémentation. Ces opérations consistent respectivement à augmenter une variable (en général entière) 1 et à diminuer une variable de 1 (i = i-1; n = n+1;). Ces opérations sont définies par les opérateurs ++ et -- : i--; n++; ou --i; ++n;. 2.6. TYPES, EXPRESSIONS ET STRUCTURES DE CONTRÔLE FONDAMENTALES 19 Il existe cependant un différence suivant où est placé l’opérateur lors de l’évaluation d’une expression, s’il est placé avant(on parle d’opérateur préfixé) la variable l’opération est effectuée avant l’évaluation de l’expression, s’il est placé après (on parle d’opérateur postfixé) l’opération est effectuée après. — n= ++i -5; : on affecte d’abord la valeur 6 à i puis la valeur 1 à n. — n= i++ - 5; : on affecte d’abord la valeur 0 à n puis la valeur 6 à i. — Affectation élargie, les instructions suivantes sont équivalentes : — i = i + k; et i+=k; Il existe la même chose avec les opérateurs *, / et -. Conversion de types Lorsque l’on désire convertir un type vers autre qui a une taille de représentation inférieure, les règles suivantes s’appliquent : — entier vers entier (long vers int, short vert byte, . . .) les octets les moins significatifs sont conservés. — double vers float : arrondi au plus proche. — flottant vers entier : il y a d’abord un arrondi au plus proche dans le type long ou int, puis on effectue la conversion en conservant les octets les moins significatifs. Opérateur conditionnel condition ? etape1 : etape2; : si condition est vraie alors etape1 sinon etape2. Exemple : max = a<b ? b :a; Si a < b, alors la variable max reçoit la valeur de la variable b, sinon elle reçoit la valeur de la variable a. Priorité des opérateurs Opérateurs () [] . ++(postfixé) –(postfixé) +(unaire) -(unaire) ++(préfixé) –(préfixé) (unaire) ! cast new /% +<< >> >>> < <= > >= instanceof == != & ∧ — && — ?: = += -= *= /= %= <<= >>= >>>= & = | = ∧ = 2.6.3 associativité g à d d à g g à d g à d g à d g à d g à d g à d g à d g à d g à d g à d g à d d à g Les structures de contrôle Dans cette partie, les crochets [] signifient que ce qu’ils renferment est facultatif. Il servent soit à exprimer le fait que pour lier plusieurs instructions à une structure de contrôle il est nécessaire de mettre ces instructions dans un bloc entre accolades ({}) (si on n’a besoin que d’une seule instruction on n’a pas besoin de bloc) ; soit à indiquer qu’une instruction facultative. Le terme condition correspond à une expression qui peut être évaluée sous forme logique (vrai ou faux). 20 CHAPITRE 2. INTRODUCTION : LE LANGAGE JAVA if L’instruction if (si) est une instruction de choix. if(condition) [{] instruction\_1 [instruction\_2 ... instruction\_n }] [else [{] instruction\_1 [instruction\_2 ... instruction\_n }] Exemple : public classTva { public static void main(String [] args) { double taux_tva=21.6; double ht, ttc, net, taux_remise, remise; ht=200.5; ttc=ht * (1.0 + taux_tva/100.0); if(ttc < 1000.0) taux_remise=0.; else if(ttc < 2000) taux_remise=1.; else if(ttc < 5000){ taux_remise=2.; System.out.println("Message: Prix ttc entre 2000 et 5000"); }else{ taux_remise=5.; System.out.println("Message: Prix ttc superieur a 5000"); } remise = ttc * taux_remise/100; net = ttc - remise; System.out.println("Prix ttc: "+ttc); System.out.println("Remise: "+remise); System.out.println("Net a payer: "+net); } } switch L’instruction switch (branchement) est une instruction de choix, permettant de tester plusieurs valeurs d’une expression. L’expression peut être de type byte, short, char ou int. Syntaxe : 2.6. TYPES, EXPRESSIONS ET STRUCTURES DE CONTRÔLE FONDAMENTALES 21 switch(expression) { case constante_1: [suite d’instructions1] case constante_2: [suite d’instructions2] case constante_n: [suite d’instructionsn] [default: suite d’instructions] } Exemple : public class ExSwitch{ public static void main(String [] args) { int n; n=Integer.parseInt(args[0]); switch(n) { case 0: System.out.println("zero"); break; case 1: System.out.println("un"); break; case 3: System.out.println("trois"); default: System.out.println("Rien"); System.out.println("Fin"); } System.out.println("Fin du programme"); } } while Il s’agit d’une boucle tant que qui exécute un ensemble d’instructions tant qu’une condition est vraie. Syntaxe : while(condition) [{] instruction_1 [instruction_2 ... instruction_n }] Exemple public class Boucle{ public static void main(String [] args) { int x=1; System.out.println("Avant la boucle"); 22 CHAPITRE 2. INTRODUCTION : LE LANGAGE JAVA while(x<4) { System.out.println("Dans la boucle, la valeur de x est "+x); x=x+1; } System.out.println("Après la boucle"); } } do-while Il s’agit d’une boucle faire-tant que similaire à la boucle while sauf que la condition est évaluée après chaque parcours de boucle. La boucle do-while est exécutée au moins une fois, alors que la boucle while peut ne jamais être exécutée. Syntaxe : do [{] instruction_1 [instruction_2 ... instruction_n }] while(condition); N.B. Il y a un point virgule à la fin de l’instruction ! Exemple : public class Boucle2{ public static void main (String [] args){ int x=1; System.out.println("Avant la boucle"); do{ System.out.println("Dans la boule, la valeur de x est "+x); x=x+1; }while(x<4); System.out.println("Après la boucle"); } } for L’instruction for est une boucle (pour) dont la syntaxe est divisée en trois expressions. Syntaxe : for([initialisation] ; [condition] ; [incrémentation]) [{] instruction_1 [instruction_2 ... instruction_n }] — initialisation est une déclaration ou une suite d’expressions quelconques séparées par des virgules, cette partie est évaluée une seule fois avant d’entrer dans la boucle. — condition est une expression booléenne (logique) quelconque, cette partie conditionne la poursuite de la boucle et est évaluée avant chaque parcours. — incrémentation est une suite d’expressions quelconques séparées par des virgules, cette partie est évaluée à la fin de chaque parcours. 2.6. TYPES, EXPRESSIONS ET STRUCTURES DE CONTRÔLE FONDAMENTALES Exemple classique : public class ExFor1 { public static void main (String args []) { int i; for(i=1; i<=5; i++) { System.out.println("bonjour"); System.out.println(i + "fois"); } } } Remarque : l’exemple précédent est équivalent à public class ExFor2 { public static void main (String args []) { int i; i=1; while(i<=5) { System.out.println("bonjour"); System.out.println(i+"fois"); i++; } } } Autre exemple : public class ExFor3 { public static void main(String args []) { int i,j; for(i=1, j=3;i<=5; i++, j+=i) { System.out.println("i= " +i+ "j= " +j); } } } Ce dernier exemple pourrait également être écrit de la manière suivante : public class ExFor4 { public static void main(String args []) { for(int i=1, j=3;i<=5; i++, j+=i) { System.out.println("i= " +i+ "j= " +j); 23 24 CHAPITRE 2. INTRODUCTION : LE LANGAGE JAVA } } } Les instructions break et continue Ces instructions s’emploient principalement au sein de boucles. L’instruction break (casser) sert à interrompre le déroulement d’une boucle en passant à l’instruction située après la boucle. L’exécution de cette instruction est conditionnée par un choix. Exemple : public class ExBreak { public static void main (String args []) { int i; for(i=1;i<=10;i++) { System.out.println("debut tour"+i); System.out.println("bonjour"); if(i==3) break; System.out.println("fin tour"+i); } System.out.println("apres ma boucle") } } Le résultat du programme précédent est le suivant : debut tour 1 bonjour fin tour 1 debut tour bonjour fin tour 2 debut tour 3 bonjour apres la boucle En cas de boucles imbriquées, l’instruction break fait sortir de la boucle la plus interne. L’instruction continue permet de passer directement au tour de boucle suivant. Exemple. public class ExContinue { public static void main (String args[]) { int i; for(i=1; i<=5; i++) { System.out.println("debut tour"+i); if (i<4) continue; System.out.println("fin tour"+i); } System.out.println("apres la boucle"); 2.7. UNE PREMIÈRE INTRODUCTION AU TYPE STRING 25 } } Exemple d’exécution : debut tour 1 debut tour 2 debut tour 3 debut tour 4 fin tour 4 debut tour 5 fin tour 5 apres la boucle 2.6.4 Les éléments spécifiques au langage JAVA Parmi les points abordés ci-dessus, quatre sont plutôt spécifiques au langage JAVA. — Le mot clé final qui permet de déclarer des constantes à l’aide de variables. — Le type byte qui permet de représenter des entiers entre -128 et 127. — Le type boolean pour représenter les valeurs true (vrai) et false (faux). Tous les tests conditionnels doivent être compatibles avec le type boolean. Mettre une valeur entière dans un test provoque une erreur de compilation en JAVA (par exemple if(1)... est interdit en JAVA). — Les conversions de type doivent être explicites en JAVA chaque que l’on veut convertir un type général vers une représentation plus petite. Exemple : int n=2; float x=1.0; ... n=(int) x; Un autre élément est traité de manière spécifique en JAVA : le type chaı̂ne de caractères (String). Une introduction est proposée dans la section suivante. 2.7 Une première introduction au type String En JAVA, les chaı̂nes de caractères sont définies par un type spécifique : le type String. Nous présentons ici une courte introduction, nous reviendrons sur ce type plus tard. Déclaration String chaineSalutation = "bonjour"; Une chaı̂ne de caractère constante se déclare toujours entre guillemets "... ". Connaı̂tre la longueur d’une chaı̂ne int l = chaineSalutation.length(); Accès à un caractère char cara1 = chaineSalutation.charAt(0); char cara1 = chaineSalutation.charAt(2); La variable cara1 contient le caractère b, la variable cara2 contient le caractère n. 26 CHAPITRE 2. INTRODUCTION : LE LANGAGE JAVA Concaténation : l’opérateur + String ch1 = "Bonjour"; String ch2 = " à tous"; String ch = ch1 + ch2; La variable ch contient la chaı̂ne ”Bonjour à tous”. Impression d’une chaı̂ne de caractères System.out.println(chaineSalutation); System.out.println(ch1+ch2); System.out.println(ch); Comparaison de chaı̂nes La méthode equals qui teste l’égalité de deux chaı̂nes de caractères : ch1.equals(ch2) ou ch1.equals("Bonjour"). La méthode compareTo pour comparer deux chaı̂nes de caractères dans l’ordre lexicographique (alphabétique) : ch1.compareTo(ch2) — renvoie un entier strictement négatif si ch1 est située avant ch2 dans l’ordre lexicographique — renvoie un entier strictement positif si ch1 est située après ch2 dans l’ordre lexicographique — 0 si ch1 contient la même chaı̂ne que ch2. 2.8 Règles d’écriture d’un programme JAVA Ce document a pour objectif de synthétiser les règles d’écriture généralement utilisées pour le développement d’applications en JAVA. 2.8.1 Les identificateurs Les identificateurs sont des suites de caractères servant à désigner les entités manipulées par un programme (variables, méthodes, classes, objets, . . .). En JAVA, un identificateur est formé de lettres et de chiffres ou du caractère . Le premier caractère est forcément une lettre A-Z, a-z ou éventuellement . Il n’existe aucune limitation sur le nombre de caractères. Exemples : ligne, valeur 5, total, 56, ma variable 1, i, MaClasse, ... Attention, on distingue les minuscules des majuscules (ligne 6= Ligne). Certaines conventions sont traditionnellement utilisées : — Les noms de variables et de méthodes sont écrits en minuscule sauf s’ils sont formés de la juxtaposition de plusieurs mots, auquel cas chaque sous-mot, sauf le premier, comporte une majuscule à la première lettre. Exemples : valeur, nombreValeur, tauxEmprunt, calculNombreReponsesExactes, getX2, ... — Les noms de classe suivent la même règle, mais leur première lettre est écrite en majuscule. Exemples : PremierProgramme, Clavier, CalculMoyenne, ... Remarque : cette convention permet de distinguer le fait que System est une classe et que out n’en est pas une (System.out.println). — Des identificateurs successifs doivent être séparés par un espace ou un saut de ligne quand il n’y a pas de séparateur ou de syntaxe particulière. Exemple : x = a + 5; y += 4; Une virgule est utilisée pour faire plusieurs déclarations d’un même type. Exemple : int x, y; float n, compteur, total, valeur; 2.8. RÈGLES D’ÉCRITURE D’UN PROGRAMME JAVA 2.8.2 27 Commentaires Il existe trois types de commentaires en JAVA. — les commentaires commençant par /∗ et se terminant par ∗/, exemple : /* Ceci est un commentaire usuel*/ — les commentaires de fin de ligne //, exemple : int valeur; // valeur avec commentaire de fin de ligne — les commentaires de documentation commençant par / ∗ ∗ et finissant par ∗/. C’est un cas particulier des commentaires usuels puisqu’ils commencent de manière légèrement différente par rapport à ces derniers. Leur intérêt est de pouvoir être extrait automatiquement pour faire de la documentation avec, par exemple, le programme javadoc. 2.8.3 Mots clés réservés Les mots suivant sont des mots clés réservés pour le langage et ne peuvent être utilisés abstract catch default final if interface private static throw volatile 2.8.4 boolean char do finally implements long protected super throws while break class double float import native public switch transient byte const else for instanceof new return synchronized try case continue extends goto int package short this void Remarques globales — — — — — — Les instructions se terminent par un point virgule ;. Les blocs de code sont définis entre accolades {}. On déclare une variable avec un nom et un type : int x;. Un opérateur d’affectation est avec un seul signe : =. Un opérateur d’égalité est avec deux signes : ==. Lorsque l’on écrit du code, il est impératif de respecter les règles d’indentation : les instructions à l’intérieur d’un bloc doivent être décalées à droite, c’est également lorsque l’on utilise une structure de contrôle. La définition de méthodes et d’attributs de classes doivent être indentés par rapport à la définition de la classe elle-même. — Pensez à commenter votre code et à bien décrire ce que font vos fonctions et à quoi servent vos classe ! — ... 2.8.5 Commentaires sur le rendu d’un programme Java Pour rendre un projet vous devez construire une archive (zip, tar.gz) contenant les différents éléments de votre projet. Une archive correctement constituée devrait contenir : — une bonne documentation comprenant — un Fichier README contenant des informations pour lancer le projet, l’installer, les prérequis éventuels et qui documente le contenu de l’archive (liste des fichiers et différents répertoires et ce qu’il y a dedans). — un manuel utilisateur indiquant comment utiliser le logiciel — un manuel technique sur la conception du projet (notamment à destination des développeurs) contenant - par exemple - des schémas UML (au moins une description du code constituant le projet), les points à améliorer, les bugs, les difficultés, les choix .... Pour des 28 CHAPITRE 2. INTRODUCTION : LE LANGAGE JAVA projets java, la documentation technique peut idéalement être complétée par une javadoc (important pour continuer le développement plus tard). — une architecture correcte : idéalement à la racine on a le README et que des sousrépertoires contenant la documentation, le code exécutable, le code source, les drivers, plus d’autres choses tout le temps dans un sous-répertoire spécifique et documenté dans le README. Les fichiers sources sont séparés des exécutables, soit dans 2 répertoires différents, soit - idéalement - dans un répertoire contenance uniquement les sources et les exécutables sont stockés dans une archive .jar On mettra dans des répertoires spécifiques des fichiers de log, de sauvegarde, les images, ... — Un code de bonne qualité. Le code doit respecter les conventions de nommage java, avoir des noms de variables explicites, être indenté correctement, et bien commenté. On peut distinguer des commentaires généraux décrivant le contenu d’une classe et son objectif, et des commentaires décrivant une méthode et ce qu’elle fait (on les retrouve dans la javadoc). Ensuite des commentaires internes qui se situent à l’intérieur des méthodes pour expliquer des des attributs d’une classe, des variables de méthode , des algorithmes, des choses techniques .... Le code importé d’ailleurs doit être signalé dans la documention technique par exemple et la provenance doit être indiquée. On peut même signaler pourquoi on a eu besoin de ce code. — L’archive DOIT être nettoyée et ne pas posséder de fichiers inutiles pour l’utilisateur (fichiers de log, de sauvegardes, de tests, inutilisés, ...) Ces remarques sont d’ordre générales mais il est pertinent d’essayer de les respecter. Le nom de l’archive doit correspondre au nom du projet, lorsque c’est un travail à rendre à un enseignant il est de bon ton d’ajouter le nom de la personne envoyant le projet. En général, lorsque que l’on décompresse l’archive, celle-ci doit créer un dossier contenant tous les éléments. Lorsque que vous envoyez une archive par courrier électronique, il est parfois conseillé d’indiquer dans le message le nom de l’archive envoyée (sa taille notamment si elle est volumineuse). Il est également judicieux de commencer votre message par une formule de salutation (du genre bonjour) et de terminer par une formule de politesse (Bonne réception, cordialement, ...) et veillez à ne pas oublier de mettre votre nom à la fin du message ! Pensez également à mettre un sujet pertinent au message et à vous mettre en copie (cc) notamment lorsque ce sont des projets notés à rendre. Je vous conseille aussi pour votre activité professionnelle, dans la mesure du possible, d’utiliser une adresse électronique qui ne soit pas trop fantaisiste. 2.8.6 Liens sur la programmation JAVA Voici quelques liens qui pourront vous permettre de compléter vos connaissances en JAVA. Il existe de nombreux liens, en voici une liste non exhaustive pour commencer vos bookmarks personnels. — http://www.oracle.com/technetwork/java/api-141528.html : informations sur les api standards de java, par versions. — http://www.oracle.com/technetwork/articles/java/index-141532.html documentation sur les différentes versions de java. — http://ibiblio.org/javafaq : des nouvelles quotidiennes sur java, un cours en HTML, de nombreux liens — http://www.java-channel.org : site modifié quotidiennement, contenant des liens vers d’autres sites. — http://www.javaworld.com : nouvelles et articles techniques — http://java.developpez.com/faq/java/ : FAQ en français — http://www.eclipse.org : un environnement de développement (IDE - Integrated Development Environment) qui facilite l’écriture de programmes java. 2.9. EXERCICES DE COURS 2.8.7 29 Bibliographie Voici quelques références bibliographiques, ce cours est inspiré de certaines d’entre elles, ce sont des choix non exhaustifs et subjectifs. — Programmer en Java. Claude Delannoy. Editions Eyrolles. — Java Tête la première. Kathy Sierra et Bert Bates. Editions O’Reilly. — Java in a nutshell - manuel de référence. David Flanagan. Editions O’Reilly. — The Java Programming Language Second Edition. Ken Arnold, James Gosling. Addison Wesley, 1998. — Java par la Pratique. Patrick Niemeyer, Joshua Peck. O’Reilly. 2.9 2.9.1 Exercices de cours Compilateur Voici le contenu de trois fichiers JAVA, indiquez pour chacun d’eux si ces fichiers peuvent se compiler ou dans le cas contraire indiquez comment les corriger. Dites finalement ce que font chacun de ces programmes. 1. public class Exercice1b{ public static void main(String [] args) { int x=1; while(x<10){ if(x>3) { System.out.println("grand x"); } } } } 2. public static void main (String [] args) { int x=5; while(x>1) { x=x-1; if(x<3) { System.out.println("petit x"); } } } 3. public class Exercice1b{ int x=5; while(x>1) { x=x-1; if(x<3){ System.out.println("petit x"); } } } Solution : 30 CHAPITRE 2. INTRODUCTION : LE LANGAGE JAVA 1. Ce fichier compile et s’exécute, mais il faut ajouter une ligne au programme dans la boucle while sinon il boucle sans fin. On peut, par exemple, ajouter x = x + 1; juste après le while et avant le if. 2. Ce fichier ne compile pas, il manque une déclaration de classe pour mettre la méthode main dedans, par exemple public class Test{ et il faudra rajouter une accolade } à la fin. 3. Ce fichier ne compile pas, une classe doit avoir au moins une méthode (par forcément une méthode main). 2.9.2 Mots croisés 1 2 4 3 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 HORIZONTALEMENT VERTICALEMENT 4 Commande pour executer un programme java (-) 1 Patrons de classes (-) 2 Pas entier (-) 8 Entier (-) 3 Tout le monde peut y accéder (-) 9 Préfixe (-) 5 Tant que (-) 10 Acronyme d’un fabricant de puces (-) 6 Transformer en bytecode (-) 11 Pour afficher quelque chose (-) 7 Inconstance (-) 15 A beaucoup de caractères (-) 9 Vaut zéro ou un (-) 16 Annoncer une nouvelle classe ou une méthode (-) 12 Modificateur de main (-) 17 Elément de programme (-) 13 Ma méthode principale (-) 19 Elle a une ligne (-) 14 Définit un comportement (-) 18 Machine fonctionnant au bytecode (-) Chapitre 3 La notion d’objets : définition et utilisation 3.1 Introduction au développement objets Dans une entreprise, un projet est soumis avec les spécifications suivantes : 3 formes doivent s’afficher sur une interface graphique : un cercle, un carré et un triangle. Lorsque l’utilisateur cliquera sur un bouton, la forme pivotera de 360 degrés dans le sens des aiguilles d’une montre (tour complet sur elle-même), et le système jouera un son d’un fichier WAV (format de son) spécifique à cette forme. 2 prototypes sont proposés pour réaliser le programme : une en programmation impérative et une en programmation objets avec une classe pour chaque forme. Programmation impérative Programmation Objets tourner(typeForme){ //faire tourner la forme //de 360 degrés } jouerSon(typeForme){ //utiliser typeForme //pour chercher //quel son WAV jouer //puis le jouer } Carre tourner(){ //code pour //faire tourner //un carré } Cercle tourner(){ //code pour //faire tourner //un cercle } Triangle tourner(){ //code pour //faire tourner //un triangle } jouerSon(){ //code pour //jouer le son //d’un carré } jouerSon(){ //code pour //jouer le son //d’un cercle } jouerSon(){ //code pour //jouer le son //d’un triangle } En programmation objet, on va créer ”un objet” par type de forme demandée, et on associera à chaque forme ses fonctionnalités propres (tourner, jouer un son). En programmation impérative, on utilisera des procédures génériques et les choix des opérations à effectuer se feront à l’aide de tests (par exemples des if). Les 2 solutions répondent au problème, mais la modélisation objet est plus rigoureuse et offre de meilleures garanties en cas de modifications futures. Justement, supposons que les spécifications soint ensuite modifiées par le commanditaire du projet : 31 32 CHAPITRE 3. LA NOTION D’OBJETS : DÉFINITION ET UTILISATION Il y aura en plus une forme d’amibe à l’écran, au milieu des autres. Lorsque l’utilisateur cliquera sur l’amibe elle tournera comme les autres formes, mais jouera un son .AIF Ceci implique des modifications dans le code Programmation impérative tourner(typeForme){ //faire tourner la forme //de 360 degrés } Programmation Objets Amibe tourner(){ //code pour //faire tourner //l’amibe } jouerSon(typeForme){ //Si la forme n’est pas //une amibe //utiliser typeForme //pour chercher //quel son WAV jouer //puis le jouer //sinon //jouer le son AIF //de l’amibe } jouerSon(){ //code pour //jouer le son //de l’amibe } En programmation impérative on va être obligé de modifier le code, ce qui peut provoquer l’ajout d’erreurs (bugs). En programmation objet, on va rajouter un nouvel objet de forme amibe et lui associer ses comportements propres. On n’a pas à modifier le code existant déjà validé. Problème : le centre de rotation diffère des autres. (a) Une amibe. (b) Les centres de rotation ne sont pas les mêmes. Ceci implique une nouvelle modification pour tourner les formes. 3.1. INTRODUCTION AU DÉVELOPPEMENT OBJETS 33 Programmation impérative tourner(typeForme, centreX, centreY){ //si la forme n’est pas //une amibe //calculer le centre //sur la base d’un //rectangle puis //faire tourner la //forme de 360 degrés //sinon //utiliser centreX et //centreY comme centre //de rotation puis faire //tourner la forme } Programmation Objets Amibe int centreX; int centreY; tourner(){ //faire tourner //l’amibe en utilisant //centreX et centreY } jouerSon(typeForme){ //Si la forme n’est pas //une amibe //utiliser typeForme //pour chercher //quel son WAV jouer //puis le jouer //sinon //jouer le son AIF //de l’amibe } jouerSon(){ //code pour //jouer le son //de l’amibe } En programmation objets on n’a pas besoin de modifier le code des autres formes (carré, triangle, cercle). On peut même aller plus loin et créer une classe contenant les parties communes, puis des sous-classes contenant les parties spécifiques. Si la classe Forme contient une fonctionnalité donnée toutes ses sous-classes en héritent automatiquement. La classe Triangle, par exemple, suit les directives de la classe forme, pour la classe Amibe on a modifié les parties de code nécessaires. Forme Super−classe tourner(){. //code } jouerSon(){ //code } Héritage Carre Triangle Cercle Amibe Sous−classes tourner(){ //code de //l’amibe } jouerSon(){ //code de // l’amibe } Figure 3.1 – Conception générale à l’aide d’une hiérarchie de classes. 34 CHAPITRE 3. LA NOTION D’OBJETS : DÉFINITION ET UTILISATION 3.2 La notion de classe Les classes ne sont pas des objets, mais servent à les construire. Elles sont des patrons d’objets, indiquant à la JVM comment produire un objet d’un type donné. Chaque objet peut affecter ses propres valeurs aux variables d’instances définies dans la classe à partir de laquelle il a été créé. Analogie : un objet peut être comparé à des fiches d’un carnet d’adresses, la classe correspond à la structure d’une fiche (nom, téléphone, adresse, . . .), les objets correspondent aux personnes stockées dans les fiches. 3.2.1 Exemple de classe : /** *Classe de définition d’un point dans le plan */ public class Point { private int x; //abscisse private int y; //ordonnée public void initialise(int abs,int ord) { x=abs; y=ord; } public void deplace(int dx,int dy) { x+=dx; y+=dy; } public void affiche() { System.out.println("Je suis un point de coordonnées "+x+" "+y); } } De part la définition de cette classe Point, nous pouvons noter deux choses : — 2 variables d’instances sont définies x et y. Le mot private indique que l’on ne peut avoir accès à ces variables qu’à l’intérieur de la classe. (On appelle parfois ces variables champs ou attributs) — 3 méthodes sont définies initialise, deplace et affiche. Le mot public indique que ces méthodes sont utilisables depuis un programme quelconque. Ces 3 méthodes ne possèdent pas de valeur de retour, dans ce cas on utilise le mot clé void dans la déclaration. Notons, de plus, que la méthode initialise – par exemple – possède 2 arguments abs et ord, tous deux de type entier. (Note : il est possible d’avoir des méthodes private dans une classe, dans ce cas elles ne pourront être utilisées que dans la classe) 3.2.2 Création d’un objet à l’aide d’une classe On déclare un objet de type Point en définissant une variable de type Point de la manière suivante : 3.2. LA NOTION DE CLASSE 35 Point a; ceci implique la création d’une référence de type Point nommée a, à ce stade aucun objet n’a été créé. Pour créer un objet, il faut utiliser l’opérateur new : Point a = new Point(); a ? ? x y Un objet point a été créé, mais x et y n’ont pas reçu de valeur, par défaut ces variables sont initialisées à zéro. Ensuite on peut initialiser les valeurs de l’objet en utilisant la méthode initialise : a.initialise(3,5); a 3.2.3 3 5 x y Exemple d’utilisation dans un programme complet public class TestPoint{ public static void main(String [] args) { Point a; Point b; a = new Point(); b = new Point(); a.initialise(3,5); a.affiche(); a.deplace(2,0); a.affiche(); b.initialise(6,8); b.affiche(); } } Si on l’exécute, ce programme produira le résultat suivant : Je suis un point de coordonnées 3 5 Je suis un point de coordonnées 5 5 Je suis un point de coordonnées 6 8 Remarques : Pour utiliser ce programme, il faut créer 2 classes, on enregistre la classe Point dans un fichier Point.java et la classe TestPoint dans un fichier TestPoint.java. En règle générale, on sépare la fonction d’utilisation (c’est-à-dire le main) dans une classe à part de manière à faciliter la réutilisation des classes déjà construites. Pour utiliser notre programme il faut : 1. compiler le fichier Point.java ; 2. compiler le fichier TestPoint.java, il ne sera possible d’utiliser le programme que si le fichier Point.class existe ; 3. lancer le programme TestPoint. 36 CHAPITRE 3. LA NOTION D’OBJETS : DÉFINITION ET UTILISATION Java impose au minimum que : — Un fichier source peut contenir une ou plusieurs classes, mais exactement une seule doit être publique. — La classe contenant la méthode main doit obligatoirement être publique. — Une classe n’ayant aucun attribut d’accès (public/private) est accessible à toutes les classes du même paquetage, et donc a fortiori du même fichier source. (Nous verrons les paquetages plus tard). 3.3 La notion de constructeur Un constructeur permet d’automatiser le mécanisme d’utilisation d’un objet en évitant d’appeler une méthode spécifique (comme la méthode initialise de la classe Point). Un constructeur est une méthode sans valeur de retour, portant le même nom que la classe pouvant définir tout un tas d’actions utiles au bon fonctionnement d’un objet. Le constructeur peut disposer d’un nombre quelconque d’arguments. Exemple public class Point { private int x; private int y; public Point(int abs,int ord) //constructeur { x=abs; y=ord; } public void deplace(int dx,int dy) { x+=dx; y+=dy; } public void affiche() { System.out.println("Je suis un point de coordonnées "+x+" "+y); } } Nous créons ensuite un point via Point a = new Point(1,3); Voici quelques lignes de code qui pourraient être mises dans une méthode main pour tester le programme. Point a; Point b = new Point(6,8); a = new Point(3,5); a.affiche(); a.deplace(2,0); a.affiche(); b.affiche(); 3.3. LA NOTION DE CONSTRUCTEUR 37 Attention : dans notre nouvelle classe, l’utilisation de a = new Point(); n’est plus possible et provoque une erreur. Si l’on désire conserver un constructeur sans arguments, il faut écrire 2 constructeurs. Lors de la construction d’un objet, il se passe, dans l’ordre : 1. initialisation par défaut des champs de l’objet 2. initialisation explicite des champs donnée lors de leur déclaration 3. exécution du corps du constructeur. Liste des initialisations par défaut des champs d’un objet en fonction de leur type. Ces valeurs correspondent en général à une valeur nulle : — boolean → false — char → null — entier → 0 — flottant → 0.0 (ou 0.f) — objet quelconque → null Cas des champs déclarés avec l’attribut final. Ces champs ne doivent être déclarés qu’un seule fois. Toute tentative de modification ultérieure aboutira à une erreur de compilation. Un champ final doit être initialisé au plus tard par le constructeur. Il n’est pas permis de compter sur l’initialisation par défaut d’un tel champ. Exemple de programme récapitulatif public class Init{ public static void main(String [] args) { A a = new A(); a.affiche(); } } class A { private np; //initialisation de type 1 private int n=20, p=10; //initialisation de type 2 public A() { np = np * p; //ou np=n*n: initialisation de type 3 n = 5; } public void affiche() { System.out.println("n="+n+", p="+p+", np="+np); } } Cependant, il est préférable d’effectuer les initialisations par le constructeur pour que l’utilisateur n’ait pas à s’interroger sur l’ordre des différentes opérations. public A() { n=20, p=10; np = n * p; 38 CHAPITRE 3. LA NOTION D’OBJETS : DÉFINITION ET UTILISATION n=5; } 3.4 Affectation et comparaison d’objets Nous venons de voir qu’il existe des variables de type ”classe” (Point a;) destinées à contenir des références sur des objets. Une référence est simplement une autre valeur de variable possible. Pour ces variables, les affectations portent sur les références et non les objets. Point a,b; a = new Point(3,5); b = new Point(2,0); Exemple a 3 5 x y b 2 0 x y a 3 5 x y b 2 0 x y Si maintenant nous ajoutons : a = b; On recopie en fait dans a la référence de b, a et b désignent alors le même objet et non la même valeur. Second exemple : Point a,b,c; a = new Point(1,10); b = new Point(2,20); c = a; a = b; b = c; Il y a 2 objets et 3 variables de type Point. Le schéma suivant représente le résultat final du code ci-dessous. a 1 10 x y 2 20 x y b c 3.4.1 La référence null Si on effectue une déclaration sans initialisation, par exemple Point p;, par défaut p est initialisé à null (rien). On ne peut pas avoir accès aux champs de l’objet ni utiliser ses méthodes. Dans ce cas, une erreur (exception) de type NullPointerException sera levée à l’utilisation. 3.5. LE RAMASSE-MIETTES 39 null p On peut cependant tester la non nullité d’une référence à un objet. if(p!=null) { p.affiche(); } 3.5 Le ramasse-miettes Nous avons vu que grâce à l’opérateur new, la JVM alloue un emplacement mémoire à l’objet et l’initialise. Il n’existe pas de mécanisme de destruction d’objets. Il existe par contre un mécanisme de gestion automatique de la mémoire connu sous le nom de ramasse-miettes (en anglais garbage collector). Son principe est le suivant : — A tout instant, la JVM connaı̂t le nombre de références à un objet donné (tout est géré par références en Java). — Lorsqu’il n’existe plus aucune référence sur un objet, on est certain que le programme ne pourra plus y accéder. Il est donc possible de libérer l’emplacement correspondant. Cependant, pour des raisons d’efficacité ( ? ?), Java n’impose pas que ce soit fait immédiatement. On dit que l’objet est candidat au ramasse-miettes. Note : La zone mémoire utilisée pour allouer de l’espace à de nouveaux objets s’appelle le tas. Tous les objets résident sur le tas. Avant qu’un objet ne soit candidat au ramasse-miettes, Java appelle la méthode finalize, qui est une méthode permettant d’effectuer des opérations lors de la destruction de l’objet. 3.6 L’encapsulation de données L’encapsulation de données est une convention de manipulation des variables d’instances en Java. Il est plus que fortement conseillé de suivre cette démarche, elle s’avère même obligatoire pour l’écriture d’un code correct et lisible dans certaines structures. En général, une bonne programmation objet protège les données relatives à un objet : les variables définies dans une classe. Pour cela, on déclare ces champs private, et on utilise des méthodes déclarées public pour y accéder. Par exemple, pour la classe Point, on rajoutera les méthodes suivantes public int getX(){return x;} public int getY(){return y;} public void setX(int abs){ x = abs;} public void setY(int ord){ y = ord;} public void setPosition(int abs, int ord) { x = abs; 40 CHAPITRE 3. LA NOTION D’OBJETS : DÉFINITION ET UTILISATION y = ord; } Suivant les cas, il peut-être préférable d’avoir une seule méthode de changement de variables lorsque que la modification d’une variable peut en affecter une autre. Autre exemple : public class BonChien{ private int taille; public BonChien() { taille = 20; } public int getTaille() { return taille; } public void setTaille(int t) { taille = t; } public void aboyer() { if(taille > 60) { System.out.println("Grr ! Grr !"); }else if(taille > 20){ System.out.println("Ouaf ! Ouaf !"); }else{ System.out.println("Kai ! Kai !"); } } } public class TestBonChien{ public static void main(String [] args) { BonChien un = new BonChien(); BonChien deux = new BonChien(); un.setTaille(70); deux.setTaille(8); System.out.println("Taille de un : "+un.getTaille()); un.aboyer(); System.out.println("Taille de deux : "+deux.getTaille()); deux.aboyer(); } } 3.7. CHAMPS ET MÉTHODES STATIQUES 3.7 41 Champs et méthodes statiques En Java on peut définir des champs qui n’existent qu’en un seul exemplaire pour toutes les instances de la classe, au lieu d’exister dans chacune des instances. Il s’agit en quelque sorte de données globales partagées par toutes les instances d’une même classe. On parle de champs (ou variables) de classe ou de champs (ou variables) statiques, nous verrons plus tard qu’il existe aussi des méthodes statiques. 3.7.1 Variables statiques Exemple classique class A { public int n; public float y; public A() { n = 3; y = 5.5; } } Chaque objet de type A possède ses propres champs n et y. Si on crée 2 objets, leur occupation en mémoire peut se représenter de la manière suivante : A a1 A a2 a2.n a2.y = new A(); = new A(); -= 1; -= 5.0; a1 3 5.5 a1.n a1.y a2 2 0.5 a2.n a2.y Avec un champ statique class B { public static int n = 2; public float y; public B() { y = 5.5; } } B b1 = new B(); B b2 = new B(); b2.y -= 5.0; b1 b1.n ou B.n 5.5 b1.y 2 b2.n ou B.n 0.5 b2.y b2 42 CHAPITRE 3. LA NOTION D’OBJETS : DÉFINITION ET UTILISATION b1.n et b2.n désignent le même champ, il est cependant préférable de s’y référer par B.n. n est une variable statique de la classe B. Exemple : public class ExObjet { private static long nb = 0; public ExObjet() { System.out.print("++ creation de l’objet ExObjet numero "+nb+" "); nb++; System.out.println("il y a maintenant "+nb+" objet(s)"); } } public class TestExObjet { public static void main(String { ExObjet a,b,c; System.out.println("Avant la a = new ExObjet(); System.out.println("Après la System.out.println("Avant la b = new ExObjet(); System.out.println("Après la c = new ExObjet(); System.out.println("Après la } } args []) creation de l’objet a"); creation de l’objet a"); creation de l’objet b"); creation de l’objet b"); creation du dernier objet c"); Résultat du programme Avant la creation de l’objet a ++ creation de l’objet ExObjet numero 0 il y a maintenant 1 objet(s) Après la creation de l’objet a Avant la creation de l’objet b ++ creation de l’objet ExObjet numero 1 il y a maintenant 2 objet(s) Après la creation de l’objet b ++ creation de l’objet ExObjet numero 2 il y a maintenant 3 objet(s) Après la creation du dernier objet c Remarque lorsque l’on compte le nombre d’objets, on ne prend pas en compte les éventuels objets détruits par le ramasse-miettes (il faudrait utiliser la méthode statique finalize). Les champs statiques peuvent être initialisés avant l’appel à un constructeur. Il peut même ne pas y avoir de constructeur, dans ce cas la seule initialisation possible est une initialisation explicite lors de la déclaration. 3.7.2 Les méthodes statiques Les méthodes statiques jouent un rôle indépendant d’un objet quelconque. Elles peuvent être appelés sans qu’un objet de la classe n’ait été créé. 3.7. CHAMPS ET MÉTHODES STATIQUES 43 Exemple public class B2 { private float x; private static int n; public static void f() //méthode de classe ou statique { //ici on ne peut pas accéder à x, mais on peut accéder à n } } Exemple class ExObjet2 { private static long nb = 0; public ExObjet2() { System.out.print("++ creation de l’objet ExObjet2 numero "+nb+" "); nb++; System.out.println("il y a maintenant "+nb+" objet"+pluriel()); } public static long getNb() { return nb; } public static String pluriel() { if(nb>1){ return "s"; } return ""; } } public class TestExObjet2 { public static void main(String [] args) { ExObjet2 a,b,c; System.out.println("Avant la creation de l’objet ExObjet2.pluriel() + ":" + ExObjet2.getNb()); a = new ExObjet2(); System.out.println("Après la creation de l’objet ExObjet2.pluriel() + ":"+ExObjet2.getNb()); System.out.println("Avant la creation de l’objet ExObjet2.pluriel() + ":"+ExObjet2.getNb()); b = new ExObjet2(); System.out.println("Après la creation de l’objet a, NB objet" + a, NB objet" + b, NB objet" + b, NB objet" + 44 CHAPITRE 3. LA NOTION D’OBJETS : DÉFINITION ET UTILISATION ExObjet2.pluriel() + ":"+ExObjet2.getNb()); c = new ExObjet2(); System.out.println("Après la creation du dernier objet c, NB objet" + ExObjet2.pluriel() + ":"+ExObjet2.getNb()); } } Résultat du programme Avant la creation de l’objet a, NB objet: 0 ++ creation de l’objet ExObjet numero 0 il y a maintenant 1 objet Après la creation de l’objet a, NB objet: 1 Avant la creation de l’objet b, NB objet: 1 ++ creation de l’objet ExObjet numero 1 il y a maintenant 2 objets Après la creation de l’objet b, NB objets: 2 ++ creation de l’objet ExObjet numero 2 il y a maintenant 3 objets Après la creation du dernier objet c, NB objets: 3 Les méthodes statiques s’avèrent utiles — pour permettre aux objets de disposer d’informations ”collectives”, — fournir des services n’ayant une signification que pour la classe elle-même (par exemple pour l’identification d’une classe), — fonctionnalités n’étant pas liées à un objet quelconque (par exemple la méthode pluriel, ou des méthodes mathématiques comme le calcul d’un cosinus ou d’une racine carré avec la classe Math). 3.8 Surdéfinition/surcharge de méthodes On parle de surcharge lorsqu’un même symbole possède plusieurs significations différentes choisies en fonction du contexte d’utilisation. Par exemple, l’opérateur + peut, suivant les cas, correspondre à une somme d’entiers, de flottants ou une concaténation de chaı̂nes de caractères. Ceci s’applique également en Java aux méthodes. Plusieurs méthodes peuvent porter le même nom pour peu que le nombre et le type de leurs arguments permettent au compilateur d’effectuer son choix. Exemple introductif : une classe Point avec 3 méthodes deplace public class Point { private int x,y; public Point(int abs, int ord) //constructeur { x = abs; y = ord; } public void deplace(int dx, int dy) { x += dx; y += dy; } public void deplace(int dz) 3.8. SURDÉFINITION/SURCHARGE DE MÉTHODES 45 { x += dz; } public void deplace(short dz) { y += dz; } } public class TestSurdef { public static void main(String [] args) { Point a = new Point(1,2); a.deplace(1,3); //appel de deplace(int ,int ) a.deplace(2); //appel de deplace(int) short p = 3; a.deplace(p); //appel de deplace(short) byte b = 2; a.deplace(b); //appel de deplace(short) } } Si jamais il y a une ambiguı̈té lors de l’appel, il y aura erreur de compilation. Remarques importantes : — Le type de la valeur de retour n’intervient pas dans le choix de la méthode surchargée. — On peut surcharger des méthodes statiques. — L’attribut final n’a aucune influence sur le choix d’une méthode surchargée. public void deplace (int dx) { ... } public void deplace (final int dx){ ... } //erreur de compilation !!! 3.8.1 Surdéfinition de constructeurs Exemple public class Point { private int x,y; public Point() { x = 0; y = 0; } public Point(int a) { x = y = a; } public Point(int abs, int ord) { 46 CHAPITRE 3. LA NOTION D’OBJETS : DÉFINITION ET UTILISATION x = abs; y = ord; } public Point(Point a) { x = a.getX(); y = a.getY(); } public int getX() { return x; } public int getY() { return y; } } Utilisation Point a = new Point(1,3); Point d = new Point(2); Point e = new Point(d); 3.9 La copie d’objets L’affectation de variables d’un type objet quelconque se limite à la recopie de référence et ne provoque pas la recopie de la valeur des objet. En général pour copier efficacement un objet, on crée une méthode spécifique. public class Point { private int x,y; . . . public Point copie() { Point p = new Point(x,y); return p; } } Utilisation Point a = new Point(1,2); Point b = a.copie(); Cette démarche est utilisable tant que la classe concernée ne comporte pas des variables qui sont de type objet (par exemple lorsque dans une classe Point on a un champ qui est aussi une variable de type Point). Il faut alors décider si la copie soit porter sur les objets référencés ou juste sur les références. 3.10. AUTORÉFÉRENCEMENT : LE MOT CLÉ THIS 47 — Copie superficielle d’un objet : on copie la valeur de tous les champs, y compris ceux qui sont d’un type classe. — Copie profonde : on recopie la valeur des champs d’un type primitif, mais pour les champs de type classe on crée une nouvelle référence à un nouvel objet de même type et de même valeur. Exemple : permutation des valeurs de 2 objets public class Point { private int x,y; . . . public void permute(Point a) { Point c = new Point(0,0); c.setX(a.getX()); c.setY(a.getY()); a.setX(x); a.setY(y); x = c.getX(); y = c.getY(); } } a 3 5 x y b 2 0 x y Point a = new Point(3,5); Point b = new Point(0,2); a 2 0 x y 2 0 x y c a.permute(b) b 3 5 x y A la fin de la méthode permute, le point référencé par c devient candidat au ramassemiettes. 3.10 Autoréférencement : le mot clé this Il permet de faire référence à l’instance de l’objet courant dans sa globalité au sein d’une méthode. Exemple : Méthode coı̈ncide pour savoir si un point coı̈ncide avec un autre. 48 CHAPITRE 3. LA NOTION D’OBJETS : DÉFINITION ET UTILISATION public class Point { private int x,y; . . . public boolean coincide(Point pt) { return ((pt.getX() == this.x) && (pt.getY() == this.y)); } } Autre exemple : Voici le constructeur classique que nous avons écrit : public Point(int abs,int ord) { x = abs; y = ord; } Ce constructeur peut se ré-écrire de la manière suivante en utilisant un auto-référencement : public Point(int x,int y) { this.x = x; this.y = y; } Le mot clé this permet d’employer des noms d’arguments identiques à des noms de champs (ce qui peut éviter l’utilisation de nouveaux identificateurs). Il est également possible de s’en servir pour appeler un autre constructeur au sein de la même classe. public class Point { private int x,y; public Point(int x,int y) { this.x = x; this.y = y; System.out.println("Constructeur avec 2 arguments "+x+" et "+y); } public Point() { this(0,0); //appel du constructeur precedent Point(0,0) System.out.println("Constructeur sans argument"); } } ATTENTION : l’appel de this(0,0) doit obligatoirement être la première instruction du constructeur. Les instructions : 3.11. REMARQUES SUR LA DÉFINITION DE MÉTHODES 49 Point a = new Point(1,2); Point b = new Point(); provoquent le résultat suivant : Constructeur avec 2 arguments 1 et 2 Constructeur avec 2 arguments 0 et 0 Constructeur sans argument 3.11 Remarques sur la définition de méthodes 3.11.1 Méthodes de type procédures et fonctions Si une méthode ne fournit aucun résultat, le mot clé void figure dans son en-tête à la place du type de valeur de retour. Une telle méthode sera appelée une procédure. Si une méthode fournit un résultat, elle porte alors le nom de fonction. Le type de sa valeur de retour doit être indiqué dans son en-tête, l’instruction return doit également être utilisée pour retourner le résultat. Exemple : une méthode distance qui calcule la distance du point par rapport à l’origine (0, 0) que l’on pourrait ajouter à la classe Point. public class Point { private int x,y; . . . public double distance() { double d; d = Math.sqrt(x*x + y*y); return d; } } Utilisation de cette méthode : Point a = new Point(2,5); double r; r = a.distance(); System.out.println("La distance par rapport à l’origine est de "+r); Il est également possible de ne pas utiliser la valeur de retour, dans ce cas on appelle juste la méthode : a.distance(); 3.11.2 Les arguments Ils figurent dans l’en-tête de la définition d’une méthode et peuvent être utilisés dans le corps de la méthode. 50 CHAPITRE 3. LA NOTION D’OBJETS : DÉFINITION ET UTILISATION public void f(int arg1,int arg2,int arg3) { ... } On peut théoriquement changer la valeur des arguments, mais c’est assez déconseillé. void f(final int n,double x) { ... x = 2.5 //correct mais déconseillé } Il également déconseillé de modifier la valeur des arguments comme le montre la méthode précédente. Dans le cas d’arguments de type primitif, les modifications à l’intérieur de la méthode ne sont plus prises en compte à la sortie de la méthode. On conseille en général de créer une variable locale supplémentaire et de copier la valeur de l’argument dedans avant de l’utiliser. Notez que dans la méthode précédente, toute modification de la valeur de n provoque une erreur de compilation. 3.11.3 Les variables locales void f(int n) { float x; //variable locale à f float n; //INTERDIT en Java ... } void g() { double x; //variable locale à g, indépendante de la variable de f ... } L’emplacement mémoire d’une variable locale est alloué au moment ou l’on entre dans la méthode, il est libéré lorsque l’on en sort. N.B. Les variables locales n’ont pas d’initialisation par défaut. Il est possible de créer des variables locales à un bloc (par exemple dans une boucle for) qui ne seront valides que dans ce bloc. public void f() { int i; ... for(i=0;i<5;i++) { int p; //p n’est connu que dans le bloc du for ... } ... } 3.12. LES PAQUETAGES (PACKAGES) 3.11.4 51 La récursivité La récursivité de méthodes peut prendre 2 formes : — directe : une méthode comporte dans sa définition au moins un appel à elle-même, — croisée : l’appel d’une méthode entraı̂ne l’appel d’une autre méthode qui a son tour appelle la méthode initiale. La récursivité peut s’appliquer aussi bien aux méthodes statiques qu’aux autres méthodes. Exemple : Calcul de la factorielle d’un nombre par une méthode statique (pour n = 8 le résultat est 40320). public class Util { public static long fact(long n) { if(n>1) return (fact(n-1) * n); return 1; //notez que si n<0 fact n’est en fait pas définie } } public class TestFact { public static void main(String [] args) { int n = Integer.parseInt(args[0]); System.out.println("La factorielle de "+ n +" est "+ Util.fact(n)); } } Note : A chaque appel de fact, il y a une allocation mémoire pour les variables locales, la valeur de retour et l’argument. Chaque nouvel appel de fact entraı̂ne donc une telle allocation sans que les emplacements précédents n’aient été libérés (empilement d’espace mémoire). Ce n’est qu’à la première instruction return qu’il y a un dépilement des arguments. 3.12 Les paquetages (packages) Une paquetage est un regroupement logique, sous un identificateur commun, d’un ensemble de classes. La notion de paquetage se rapproche de la notion de bibliothèque (ou librairie) que l’on trouve dans d’autres langages. Elle facilite le développement et la cohabitation de logiciels. 3.12.1 Attribution d’une classe à un paquetage Un paquetage est caractérisé par un nom qui est soit un simple identificateur soit une suite d’identificateur séparés par des points. Exemple : MesClasses Utilitaires.Mathematiques Utilitaires.Tris L’utilisation du point . implique une hiérarchie logique de package. Ainsi, dans l’exemple précédent, les packages Mathématiques et Tris sont en dessous du package Utilitaires. 52 CHAPITRE 3. LA NOTION D’OBJETS : DÉFINITION ET UTILISATION Utilitaires Mathematiques Tris L’attribution à un package se fait au niveau du fichier source. Toutes les classes d’un même fichier appartiendront toujours à un même package. On place en début de fichier une instruction de la forme : package nompaquetage; La plupart des environnements imposent des contraintes quant à la localisation des fichiers correspondant à un package. En particulier un paquetage de nom X.Y.Z se trouvera toujours dans un sous-répertoire de nom X.Y.Z (les répertoires de niveau hiérarchique supérieur peuvent être quelconques). Deux paquetages X.Y.Z et X.Y.U n’ont pas l’obligation d’être dans le même répertoire. Avec le JDK (ou SDK) de Sun la recherche d’un package se fait dans les répertoires indiqués dans la variable d’environnement CLASSPATH. S’il n’y a pas de paquetage indiqué dans un fichier source, le compilateur considère que ceux-ci appartiennent au paquetage par défaut. Si l’on ne souhaite pas utiliser la variable d’environnement CLASSPATH, il est possible de spécifier les chemins où se trouvent les paquetages à l’aide de l’option -classpath des commandes java et javac. Par exemple si une classe Toto utilise des packages ou des classes situées dans les répertoires ~/MesPackages et /usr/local/packages standard, on utilisera les commandes suivantes : javac -classpath ~/MesPackages;/usr/local/packages_standard Toto.java java -classpath ~/MesPackages;/usr/local/packages_standard Toto Ceci implique si le package affaires de toto est utilisé, le dossier contenant les éléments de ce package doit être situé soit dans ~/MesPackages/affaires_de_toto ou dans /usr/local/packages standard/affaires de toto ; évidemment une seule des 2 solutions est valable. 3.12.2 Utilisation d’une classe située dans une paquetage Il faut : — citer le nom du paquetage avec le nom de la classe, — utiliser une instruction import en indiquant soit une classe particulière d’un paquetage, soit un paquetage entier. Avec une citation de nom de classe. Si vous avez attribué la classe Point au package MesClasses, vous pouvez l’utiliser en la nommant MesClasses.Point, exemple : MesClasses.Point p = new MesClasses.Point(2,5); ... p.affiche(); //le nom de la classe n’est plus requis ici En important une classe avec l’instruction import import MesClasses.Point, MesClasses.Cercle; Il est ensuite possible d’utiliser les classes Point et Cercle sans avoir à mentionner leurs noms. 3.13. LES TABLEAUX 53 En important un package en entier. import MesClasses.*; Toutes les classes du package MesClasses et les classes des packages hiérarchiquement en dessous sont alors directement utilisables. Notez que si vous tapez uniquement import MesClasses; les classes du package MesClasses seront directement utilisables, mais pas les packages contenus dans MesClasses. Il existe plusieurs paquetages standard fournis avec Java, par exemple le package Math ou encore le package java.lang qui est automatiquement ajouté par le compilateur. 3.12.3 Droits d’accès — Avec le mot clé public, une classe est accessible à partir de toutes les autres classes (éventuellement via une instruction import). — Sans le mot clé public, une classe n’est accessible qu’aux classes du même paquetage. Tant que l’on travaille avec le paquetage par défaut, l’absence du mot public n’a guère d’importance. Note : le mot clé private n’a pas de sens au niveau d’une classe. 3.13 Les tableaux En programmation, un tableau désigne un ensemble d’éléments de même type identifiés par un nom unique. Chacun des éléments étant ensuite repéré par un indice précisant sa position au sein de l’exemple. En Java, les tableaux sont considérés comme des objets, les tableaux à plusieurs indices s’obtiennent par composition de tableaux. 3.13.1 Déclaration et création Imaginons que nous voulions créer un tableau d’entiers, deux déclarations sont possibles : int t[]; int []t; La différence entre les 2 déclarations a une importance lorsque l’on souhaite déclarer plusieurs tableaux : int [] t1,t2; //déclaration de 2 tableaux d’entiers int t1[], t2[]; //meme chose int t1[], n, t2[]; //2 tableaux t1 et t2, n est un entier Point a, tp[], b; //a et b sont de type Point, tp est un tableau // d’objets Point N.B. : une déclaration de tableau ne doit pas préciser de dimension. Pour créer un tableau on utilise l’opérateur new comme pour des objets classiques. Voici un exemple montrant comment créer un tableau de 10 entiers : int t[] = new int[10]; Il est également possible de fournir une liste d’expression entre accolades. Voici un exemple de création d’un tableau d’entiers à 5 éléments. int t[] = {1, 2, 7, 10, 0}; 54 CHAPITRE 3. LA NOTION D’OBJETS : DÉFINITION ET UTILISATION L’instruction précédente équivaut aux instructions suivantes : int [] t[0] = t[1] = t[2] = t[3] = t[4] = t = new int[5]; 1; 2; 7; 10; 0; Attention. Une fois un tableau créé, on ne peut plus modifier sa taille. Par contre, on peut créer un nouveau tableau et - si besoin - recopier les valeurs du tableau initial. La taille d’un tableau est toujours positive. La taille d’un tableau Il est possible d’avoir accès à la taille d’un tableau à l’aide du champ length. int t[] = new int[6]; System.out.println("taille de t "+t.length); //affiche 6 t = new int[2]; System.out.println("taille de t "+t.length); //affiche 2 Ici length est vu comme un champ et non comme une méthode. Accès aux éléments d’un tableau Un élément est désigné en plaçant entre crochets sa position après le nom du tableau. Une position est désignée par une expression entière comprise entre 0 (le premier élément du tableau) et la taille du tableau-1 (dernier élément du tableau). Exemple : calcul d’une moyenne de notes public class TestCalculMoyenne { public static void main(String [] args) { int i, nbNotes, nbSupMoy; double somme, moyenne, notes[]; if(args.length == 0) { System.exit(0); } notes = new double[args.length]; nbNotes = args.length; for(i=0; i< nbNotes; i++) { notes[i] = Double.parseDouble(args[i]); } somme=0.0; for(i=0; i< nbNotes; i++) somme += notes[i] ; moyenne = somme / (double) nbNotes; 3.13. LES TABLEAUX 55 System.out.println("Moyenne des notes :"+moyenne); for(i=0, nbSupMoy=0; i < nbNotes; i++) { if(notes[i] > moyenne) nbSupMoy++; } System.out.println(nbSupMoy + " notes sont supérieures à cette moyenne"); } } Affectation de tableaux. Exemple. int [] t1 = new int[3]; for(int i=0; i<t1.length; i++) t1[i] = i; int [] t2 = new int[2]; for(int i=0; i<t2.length; i++) t2[i] = 10+ i; La situation peut-être schématisée comme ceci : t1 0 1 2 t2 10 11 Si maintenant on exécute : t1 = t2; //la référence contenue dans t2 est recopiée dans t1 Nous aboutissons alors à cette situation : t1 0 1 2 t2 10 11 Maintenant si l’on exécute les instructions suivantes : t1[1] = 5; System.out.println(t2[1]); //affiche 5 !!! L’ancien objet référencé par t1 devient candidat au ramasse-miettes. Lors de l’affectation de références de tableau, il n’y a aucune recopie des valeurs des éléments du tableau. 56 CHAPITRE 3. LA NOTION D’OBJETS : DÉFINITION ET UTILISATION Exemple de tableau d’objets public class Point { private int x,y; public Point(int x,int y) { this.x = x; this.y = y; } public void affiche() { System.out.println("Point : "+x+", "+y); } } public class TabPoint { public static void main(String [] args) { Point [] tp; tp = new Point[3]; tp[0] = new Point(1,2); tp[1] = new Point(4,5); tp[2] = new Point(8,9); for(int i=0; i<tp.length; i++) tp[i].affiche(); } } Résultat : Point : 1, 2 Point : 4, 5 Point : 8, 9 3.13.2 Tableau en argument ou en retour Lorsque l’on transmet un nom de tableau en argument d’une méthode, on transmet en fait une copie de la référence au tableau. La méthode agit directement sur le tableau concerné et non sur une copie. Voici un exemple de manipulations de tableau par des méthodes statiques. class Util { static void raz(int t[]) { for(int i=0; i<t.length; i++) t[i] = 0; } static void affiche (int t[]) { 3.13. LES TABLEAUX 57 for(int i=0; i<t.length; i++) System.out.print(t[i] + " "); System.out.println(); } } public class TabArg { public static void main(String [] args) { int t[] = {1, 3, 5, 7}; System.out.print("t avant : "); Util.affiche(t); Util.raz(t); System.out.print("t apres : "); Util.affiche(t); } } Résultat. t avant: 1 3 5 7 t apres 0 0 0 0 La même chose s’applique à un tableau fourni en valeur de retour. Par exemple, la méthode suivante fourni un tableau formé des n premiers entiers : public static int[] suite(int n) { int [] res = new int[n]; for(int i=0; i<t.length; i++) res[i]=i+1; return res; } Un appel à suite fournira une référence à un tableau dont on pourra éventuellement modifier la valeur des éléments. 3.13.3 Les tableaux à plusieurs indices De nombreux langages disposent de la notion de tableau à plusieurs indices. Par exemple, un tableau à deux indices permet de représenter une matrice mathématique. Java ne dispose pas d’une telle notion. Néanmoins, il permet de la simuler en créant des tableaux de tableaux dont les éléments sont eux-mêmes des tableaux. Cette possibilité s’avère en fait très riche puisqu’elle peut permettre de créer des tableaux irréguliers (par exemple lorsque les différentes lignes ont un nombre de colonnes qui varie). Premier exemple. Ces trois déclarations sont équivalentes pour un tableau à 2 dimensions : int t [] []; int [] t []; int [] [] t; Elles déclarent une référence à un tableau, dans lequel chaque élément est lui-même une référence à un tableau d’entiers. Notez que pour l’instant aucun tableau n’existe encore. Considérons la déclaration : 58 CHAPITRE 3. LA NOTION D’OBJETS : DÉFINITION ET UTILISATION int t [] [] = { new int [3], new int [2] }; t comporte deux éléments de sorte que le premier soit un tableau de 3 entiers et que le deuxième soit un tableau de 2 entiers. On aboutit à la situation suivante (les éléments des tableaux d’entiers sont comme d’habitude initialisés à 0) : 0 0 0 t 0 0 On constate alors que : — la notation t[0] désigne la référence au premier tableau de 3 entiers, — la notation t[0][1] désigne le deuxième élément de ce tableau (pour rappel les indices commencent à 0), — la notation t[1] désigne la référence au deuxième tableau de 2 entiers, — la notation t[0][i-1] désigne le ième élément de ce tableau pour i compris entre 1 et 2, — l’expression t.length vaut 2, — l’expression t[0].length vaut 3, — l’expression t[1].length vaut 2. Second exemple. int t [] []; t = new int [2] []; int [] t1 = new int[3]; int [] t2 = new int[2]; t[0] = t1; t[1] = t2; La situation peut s’illustrer comme ceci : t1 0 0 0 t t2 Troisième exemple. class Util { static void raz(int t[] []) { int i,j; 0 0 3.13. LES TABLEAUX 59 for(i=0;i<t.length;i++) for(j=0;j<t[i].length;j++) t[i][j]=0; } static void affiche(int t[] []) { int i,j; for(i=0;i<t.length;i++) { System.out.println("ligne de rang "+i+" = "); for(j=0;j<t[i].length;j++) System.out.println(t[i][j] + " "); System.out.println(); } } } public class TestTab2 { public static void main(String arg[]) { int t [][] = {{1,2,3}, {11, 12}, {21, 22, 23, 24} } System.out.println("t avant raz :"); Util.affiche(t); Util.raz(t); System.out.println("t apres raz :"); Util.affiche(t); } } Résultat. t avant raz ligne de rang ligne de rang ligne de rang t apres raz ligne de rang ligne de rang ligne de rang 0= 1 2 3 1= 11 12 2= 21 22 23 24 0= 0 0 0 1= 0 0 2= 0 0 0 0 Cas des tableaux réguliers. Si on souhaite créer une matrice de N L lignes et N C colonnes on peut toujours procéder comme ceci : int t[][] = new int [NL] []; int i; for(i=0; i<NL; i++) t[i] = new int [NC]; On peut également écrire en Java : int t[][] = new int [NL] [NC]; 60 CHAPITRE 3. LA NOTION D’OBJETS : DÉFINITION ET UTILISATION 3.14 Exercices de cours 3.14.1 Compilateur Voici le contenu de deux fichiers JAVA, indiquez pour chacun d’eux si ces fichiers peuvent se compiler ou dans le cas contraire indiquez comment les corriger. Dites finalement ce que font chacun de ces programmes. 1. Premier fichier : class TapeDeck { private boolean canRecord = false; void playTape() { System.out.println("lecture de la bande"); } void recordTape() { System.out.println("enregistrement de la bande"); } public boolean getCanRecord() { return canRecord; } public void setCanRecord(boolean canRecord) { this.canRecord=canRecord; } } public class TapeDeckTestDrive{ public static void main(String [] args) { t.setCanRecord(true); t.playTape(); if(t.getCanRecord()==true){ t.recordTape(); } } } 2. Deuxième Fichier : class DVDPlayer{ private boolean canRecord=false; void recordDVD() { System.out.println("enregistrement du DVD"); } 3.14. EXERCICES DE COURS 61 public boolean getCanRecord() { return canRecord; } public void setCanRecord(boolean canRecord) { this.canRecord=canRecord; } } public class DVDPlayerTestDrive{ public static void main(String [] args){ DVDPlayer d = new DVDPlayer(); d.setCanRecord(true); d.playDVD(); if(d.getCanRecord() == true){ d.recordDVD(); } } } Réponses : 1. Le fichier ne compile pas, il manque la création d’une instance TapeDeck, il faut donc créer un objet. Pour cela il faut rajouter juste après la déclaration de la méthode main, et avant la ligne t.canRecord = true; la ligne TapeDeck t = new TapeDeck(); 2. Le fichier ne compile pas, il manque la méthode playDVD() dans la classe DVDPlayer, on pourra par exemple rajouter les lignes suivantes juste avant la fin de la classe : void playDVD() { System.out.println("lecture du DVD"); } Remarque importante : pour simplifier l’exercice et pouvoir le faire dès le début, il n’y a pas de droits d’accès indiqués pour les variables d’instances, elles sont donc publiques par défaut. Ceci n’est pas une bonne pratique : il vaut mieux déclarer les variables privées et utiliser l’encapsulation. A vous donc d’améliorer la correction de l’exercice ! 3.14.2 Qui suis-je ? Associer à chaque ligne ci-dessous un ou plusieurs mots parmi : Classe, Méthode, Objet, Variable d’instance. 1. Je résulte de la compilation d’un fichier .java : 2. Mes variables d’instances peuvent être différentes de celles de mes copains : 3. Je me comporte comme un patron : 4. J’aime faire des choses : 5. Je peux avoir plusieurs méthodes : 6. Je représente un “état” : 7. J’ai des comportements : 8. On me trouve dans des objets : 9. Je réside sur le tas : 62 CHAPITRE 3. LA NOTION D’OBJETS : DÉFINITION ET UTILISATION 10. Je sers à créer des instances d’objets : 11. Mon état peut changer : 12. Je déclare des méthodes : 13. Je peux changer lors de l’exécution : Note : classes et objets ont tous deux un état et un comportement. Ils sont définis dans la classe mais on dit aussi que c’est l’objet qui les “possède”. 3.14.3 Compilateur 2 Voici trois programmes, indiquez si ces programmes compilent ; si la réponse est oui dites ce qu’ils font, sinon comment les corrigez vous ? 1. public class XCopie{ public static void main(String [] args) { int orig = 42; XCopie x = new XCopie(); int y = x.go(orig); System.out.println(orig + " " + y); } int go(int arg) { arg = arg * 2; return arg; } } 2. class Horloge{ private String heure; void setHeure(String t){ heure = t; } void getHeure(){ return heure; } } public class TesteHorloge{ public static void main(String [] args) { Horloge c = new Horloge(); c.setHeure("1245"); String h = c.getHeure(); System.out.println("heure: " + h); } } 3. Ce programme compile, mais que fait-il ? class Resultat{ public static void main(String []args){ Resultat r = new Resultat(); r.go(); 3.14. EXERCICES DE COURS 63 } void go(){ int y=7; for(int x=1; x < 8 ; x++){ y++; if(x > 4){ ++y; System.out.print(y+" "); } if(y > 14) { System.out.println(" x = " + x); break; } } } } Réponses : 1. La classe compile et s’exécute, le résultat est 42 84 2. Le fichier ne compile pas, il manque le type de retour String pour la méthode getHeure. 3. java Resultat 13 15 x = 6 3.14.4 Qui suis-je ? Associer à chaque ligne ci-dessous un ou plusieurs mots parmi : variable d’instance, argument, retour, méthode get, méthode set, encapsulation, public, private, passage par valeur, méthode. 1. Une classe peut en avoir un nombre quelconque : 2. Une méthode ne peut en avoir qu’un : 3. Peut être converti implicitement : 4. Je préfère mes variables d’instances private : 5. Signifie réellement “faire une copie” : 6. Seules les méthodes set devraient les modifier : 7. Une méthode peut en avoir plusieurs : 8. Je retourne quelque chose par définition : 9. Ne m’utilisez pas avec des variables d’instance : 10. Je peux avoir plusieurs arguments : 11. Par définition, j’accepte un seul argument : 12. Ils aident à créer l’encapsulation : 13. Je vole toujours en solo : 3.14.5 Compilateur 3 Voici deux programmes, indiquez si ces programmes compilent ; si la réponse est oui dites ce qu’ils font, sinon comment les corrigez vous ? (Pour des raisons de simplicité, l’encapsulation des données a été omise dans cet exercice, nous rappelons cependant que cet aspect est primordial en programmation objet) 64 CHAPITRE 3. LA NOTION D’OBJETS : DÉFINITION ET UTILISATION 1. class Livres{ private String titre; private String auteur; public String getTitre() { return titre; } public void setTitre(String titre) { this.titre=titre; } public String getAuteur() { return auteur; } public void setAuteur(String auteur) { this.auteur=auteur; } } class TestLivres{ public static void main(String [] args) { Livres [] mesLivres = new Livres[3]; int x=0; mesLivres[0].setTitre("Panique à Java"); mesLivres[1].setTitre("Java et moi"); mesLivres[2].setTitre("Cuisinez en Java"); mesLivres[0].setAuteur("Monique"); mesLivres[1].setAuteur("Michel"); mesLivres[2].setAuteur("Sophie"); while(x<3) { System.out.print(mesLivres[x].getTitre()); System.out.print("par"); System.out.print(mesLivres[x].getAuteur()); x=x+1; } } } 2. class Hobbits{ String nom; public static void main(String [] args) { Hobbits [] h = new Hobbits[3]; 3.14. EXERCICES DE COURS 65 int z = -1; while(z < 2) { z=z+1; h[z]= new Hobbits(); h[z].nom="bilbo"; if(z==1){ h[z].nom="frodon"; } if( z == 2) { h[z].nom="sam"; } System.out.print(h[z].nom + " est un "); System.out.println("bon nom de Hobbit "); } } } Réponses : 1. Il manque la création des objets livres, juste après int x=0; il faut rajouter mesLivres[0] = new Livres(); mesLivres[1] = new Livres(); mesLivres[2] = new Livres(); 2. Cette classe compile et fonctionne, nous rappelons que les indices de tableaux commencent à zéro. 66 CHAPITRE 3. LA NOTION D’OBJETS : DÉFINITION ET UTILISATION 3.14.6 Mots croisés 1 2 3 4 5 6 8 7 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 HORIZONTALEMENT VERTICALEMENT 1 Mettre en oeuvre (-) 2 Type d’incrémentation (-) 4 Teste d’abord (-) 3 Elle fait le travail (-) 5 32 bits (-) 4 Spécifie l’action dans un for (-) 7 Réponse d’une méthode (-) 5 Fixe la valeur de départ (-) 8 Avant le vrai code (-) 6 Méthode qui modifie (-) 11 Changement de type (-) 9 Contraire d’incrément (-) 12 Executrice (-) 10 Répétition (-) 13 Fait partie d’un tableau (-) 12 Package pour les E/S (-) 14 Peut être locale (-) 15 Faits pour être transmis (-) 16 Package contenant les classes essentielles (-) 18 Méthode qui accède (-) 17 A une méthode parseInt (-) 20 Classe qui calcule (-) 19 Pour générer des nombres au hasard (-) 22 Plus petit qu’un short (-) 21 Impossible à convertir (-) 23 Type de boucle (-) 24 Certaines réalités le sont (-) 24 Editeur préféré des puristes (-) 25 Plus petit qu’un int (-) 26 Interface de développement d’applications (-) 3.14. EXERCICES DE COURS 3.14.7 67 Mots croisés Certains mots sont introduits dans la section 8.3.2 sur les ArrayList. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 HORIZONTALEMENT VERTICALEMENT 1 Ils n’ont pas de comportement (-) 2 Implémente un comportement (-) 6 Tous les objets en dérivent (-) 3 Retourne une position (-) 7 Premier indice (-) 4 Retourne un nombre d’éléments (-) 10 Ajoute un objet (-) 5 Grand décimal (-) 11 Loup y es-tu ? (-) 8 Groupe de classes (-) 14 Mutateur (-) 9 Pour savoir si une liste est vide (-) 16 Croı̂t dynamiquement (-) 12 Sorte (-) 18 Homologue de 4 (vertical) (-) 13 Position d’un élément (-) 20 Inconstante (-) 15 Pas réel (-) 22 Sinon (-) 16 Acronyme de la bibliothèque (-) 23 Unité adressable (-) 17 Commun aux courses et aux tableaux (-) 19 Récupère une valeur (-) 21 Article (rien à voir avec Java) (-) 68 CHAPITRE 3. LA NOTION D’OBJETS : DÉFINITION ET UTILISATION Chapitre 4 Les concepts d’Héritage et de Polymorphisme 4.1 Présentation et définition L’héritage permet de spécifier une classe générale donnée en sous-classe. Premier exemple. Voici une première hiérarchie de classe. Super classe Medecin travailleALHopital traiterPatient() MedecinDeFamille Chirurgien faitDesVisites traiterPatient() faireUneIncision() donnerConseil() En Java, cette hiérarchie s’écrira comme ceci : public class Medecin { boolean travailleALHopital; void traiterPatient() { //traiter le patient } } public class MedecinDeFamille extends Medecin { boolean faitDesVisites; void donnerConseil() { //donner un simple conseil } 69 Sous classes 70 CHAPITRE 4. LES CONCEPTS D’HÉRITAGE ET DE POLYMORPHISME } public class Chirurgien extends Medecin { void traiterPatient() { //traitement d’un chirurgien } void faireUneIncision() { //action spécifique du chirurgien } } Autre exemple : retour à la classe Point. public class Point { private int x,y; public Point(int x,int y) { this.x = x; this.y = y; } public Point() { this(0,0); } public void deplace(int dx,int dy) { x += dx; y += dy; } public void modifie(int x,int y) { this.x = x; this.y = y; } public void affiche() { System.out.println("Je suis en "+x+" "+y); } } Imaginons que nous ayons besoin de manipuler des points colorés de manière à : — garder les même fonctionnalités de Point, — ajouter le traitement d’une couleur, — redéfinir l’affichage d’un point avec sa couleur. 4.1. PRÉSENTATION ET DÉFINITION 71 public class PointCouleur extends Point { private byte couleur; public void colorie(byte couleur) { this.couleur = couleur; } public void modifie(int x,int y,byte couleur) { super.modifie(x,y); this.couleur = couleur; } public void afficheCouleur() { super.affiche(); System.out.println(" de couleur" + couleur); } } PointCouleur est un Point, Point est la classe de base ou superclasse, PointCouleur est la sousclasse ou classe dérivée. Un objet de type PointCouleur peut alors faire appel aux méthodes publiques de PointCouleur et aux méthodes publiques de Point. Un objet d’une classe dérivée peut accéder directement aux membres public de sa classe de base, mais pas aux membres privés. A retenir : — Une classe dérivée n’accède pas aux membres privés (champs et méthodes) de sa superclasse. Par exemple, les points x et y ne sont pas accessibles directement de la classe PointCouleur. — Une classe dérivée a accès aux membres publiques de sa superclasse. Pour accéder à une méthode public de la superclasse on utilise le mot clé super. — Un objet d’une classe dérivée accède aux membres publics de sa classe de base exactement comme s’ils étaient définis directement dans la classe dérivée elle-même. Exemple d’utilisation des 2 classes précédentes. PointCouleur pc1 = new PointCouleur(); pc1.modifie(3,5); pc1.colorie((byte) 3); pc1.affiche(); pc1.afficheCouleur(); PointCouleur pc2 = new PointCouleur(); pc2.modifie(5,8,(byte)2); pc2.afficheCouleur(); pc2.deplace(1,-3); pc2.afficheCouleur(); Ces lignes de code provoquent le résultat suivant : Je suis en 3 5 Je suis en 3 5 de couleur 3 72 CHAPITRE 4. LES CONCEPTS D’HÉRITAGE ET DE POLYMORPHISME Je suis en 5 8 de couleur 2 Je suis en 6 5 de couleur 2 4.2 Construction et initialisation des objets dérivés Quand il n’y a pas de constructeur, un pseudo-constructeur par défaut est appelé, nous nous intéressons maintenant au constructeur de la classe dérivée. En Java, le constructeur de la classe dérivée doit prendre en charge l’intégralité de la construction de l’objet. Pour l’initialiser certains champs private de la classe de base, on peut soit utiliser des méthodes de modifications publiques, soit appeler le constructeur de la super-classe. Attention. Si un constructeur d’une classe dérivée appelle un constructeur d’une classe de base, il doit obligatoirement s’agir de la première instruction du constructeur (même principe que pour l’utilisation du this). La référence à un constructeur est désignée par super. public class Point { private int x,y; public Point(int x,int y) { this.x = x; this.y = y; } ... } public class PointCouleur extends Point { public PointCouleur(int x,int y,byte couleur) { super(x,y); //appel du constructeur de la classe Point this.couleur = couleur; } ... } Attention. — Les mots clés this et super ne peuvent pas être utilisés en même temps. — Lorsqu’une classe dérive d’une classe qui dérive elle aussi d’une autre classe, l’appel par super ne concerne que le constructeur de la classe de base de niveau immédiatement supérieur. 4.2. CONSTRUCTION ET INITIALISATION DES OBJETS DÉRIVÉS 73 super() Remarques importantes sur la définition d’un constructeur. — Si la super-classe ne possède pas de constructeur, il est possible d’appeler le constructeur par défaut à partir de la classe dérivée via super();. Cet appel peut paraı̂tre superflu, mais ne nuit pas. Ceci est pratique lorsque l’on construit une classe dérivée sans connaı̂tre les détails de la classe de base : on s’assure que les différents éléments de la super-classe seront correctement initialisés. Ceci justifie également le principe de toujours mettre un constructeur par défaut sans argument dans une classe. — Si la classe dérivée ne possède pas de constructeur, le constructeur par défaut de la classe sera appelée et par conséquent le constructeur par défaut de la super-classe. Si ce constructeur n’existe pas (s’il y a que des constructeurs avec arguments qui sont définis), il y a une erreur de compilation. Par exemple ; class A { public A(int n) { ... } ... } class B extends A { // pas de constructeur } Cet exemple provoque une erreur car il n’y a pas de constructeur sans argument dans A. La construction d’un objet B dérivé d’un objet A entraı̂ne 6 étapes : 1. Allocation mémoire pour un objet de type B (y compris les champs définis dans la superclasse A). 2. Initialisation par défaut de tous les champs de B (aussi bien ceux hérités de A que ceux définis dans B) aux valeurs nulles classiques. 3. Initialisation explicite des champs hérités de A. 4. Exécution du corps du constructeur de A. 5. Initialisation explicite des champs propres à B. 6. Exécution du constructeur de B. Notez également qu’il est possible d’avoir plusieurs dérivations successives : 74 CHAPITRE 4. LES CONCEPTS D’HÉRITAGE ET DE POLYMORPHISME A B D 4.3 4.3.1 E C F G Redéfinition, surdéfinition et héritage Redéfinition La redéfinition consiste à re-écrire une méthode définit dans la super-classe et à changer son comportement. Le nombre et le type des arguments ainsi que la valeur de retour doivent être exactement les mêmes. public class Point { private int x,y; ... public void affiche() { System.out.println("Je suis un point de coordonnées "+x+" "+y); } } public class PointCouleur { private byte couleur; ... public void affiche() { super.affiche(); System.out.println(" } } de couleur "+couleur); Lorsque l’on utilise la méthode affiche d’un objet de type PointCouleur, la méthode définie dans la classe PointCouleur est appliquée, sinon pour un objet de type Point uniquement c’est la méthode définie dans la classe Point. Imaginons que nous ayons une hiérarchie de classe avec plusieurs dérivations successives. Dans la classe la plus “élevée” une méthode f et définie et cette méthode est redéfinie dans certaines de ses sous-classes. 4.4. LE POLYMORPHISME 75 A définition de f redéfinition de f B C redéfinition de f D E F Voici la liste des méthodes f qui seront appelées en fonction du type de l’objet considéré : — pour A, la méthodes f de A, — pour B, la méthodes f de A, — pour C, la méthodes f de C, — pour D, la méthodes f de D, — pour E, la méthodes f de A, — pour F, la méthodes f de C. Attention. Les droits d’accès des méthodes redéfinies ne doivent pas être diminués : une méthode publique dans la super-classe ne peut pas être redéfinie privée dans la classe dérivée, l’inverse est cependant possible. 4.3.2 La surchage La surcharge ou surdéfinition consiste à modifier le prototype d’une méthode existante, en changeant le nombre d’arguments et/ou le type des arguments. Nous avons déjà vu cette notion auparavant, dans le cadre de l’héritage, la recherche d’une méthode acceptable se fait en “remontant” les dérivations successives. 4.3.3 En résumé — Si une méthode possède la même signature dans une classe dérivée que dans une classe parente : — les types des valeurs de retour doivent être exactement les mêmes, — les droits d’accès de la méthode de la classe dérivée ne doivent pas être moins élevés que dans la classe parente, — la clause throws de la méthode dérivée ne doit pas mentionner des exceptions non indiquées dans la clause throws de la méthode de la classe parente. (Nous verrons ceci plus tard). Si ces 3 conditions sont réunies, nous avons une redéfinition sinon il y a une erreur. — Si la signature de la méthode (nombre/type des arguments) n’est pas la même on a une surcharge (ou surdéfinition). On rappelle que le type de la valeur de retour n’est pas pris en compte dans le cas d’une surcharge. Notez qu’il est possible de dupliquer des champs lors d’un processus d’héritage, dans ce cas seul le champ de la classe dérivée n’est visible de “l’extérieur”. Ceci est à éviter. 4.4 Le polymorphisme Le polymorphisme permet de manipuler des objets sans connaı̂tre tout à fait leur type. C’est un principe extrêmement puissant en programmation orientée objet, qui complète l’héritage. Exemple de base : public class Point { 76 CHAPITRE 4. LES CONCEPTS D’HÉRITAGE ET DE POLYMORPHISME private int x,y; public Point(int x,int y){ ... } public Point(){ ... } public void affiche(){ ... } ... } public class PointCouleur { private byte couleur; public PointCouleur(int x,int y,byte couleur){ ... } public PointCouleur(){ ... } public void affiche(){ ... } ... } Tout élément de type PointCouleur est également de type Point, il est possible d’utiliser cette caractéristique commune. Objet de type Point Point p; p = new Point(3,5); (Point) p 3 5 x y 4 8 2 x y couleur ... p = new PointCouleur(4,8,(byte) 2); (Point) p Objet de type PointCouleur Autre exemple. Point p = new Point(3,5); p.affiche(); //methode affiche de la classe Point p = new PointCouleur(4,8,(byte)2); p.affiche(); //méthode affiche de la classe PointCouleur Le choix de la méthode à appliquer se fait automatiquement en fonction du type effectif de l’objet et non pas en fonction du type de la variable qui référence l’objet. Ce choix porte le nom de liaison dynamique ou ligature dynamique. Le polymorphisme se traduit par : — la compatibilité par affectation entre un type de classe et un type ascendant, — la liaison dynamique des méthodes : comportement adapté à chaque objet. 4.4. LE POLYMORPHISME 4.4.1 77 Tableaux hétérogènes d’objets Exemple : Point [] tabPts = new Point[4]; tabPts[0] tabPts[1] tabPts[2] tabPts[3] = = = = new new new new Point(0,2); PointCouleur(1,5,(byte) 2); PointCouleur(2,3,(byte) 4); Point(1,2); for(int i=0 ; i<tabPts.length ; i++) { tabPts.affiche(); } Ce bout de code provoque le résultat suivant : Je suis un point Je suis un point de couleur 2 Je suis un point de couleur 4 Je suis un point de coordonnées 0 2 de coordonnées 1 5 de coordonnées 2 3 de coordonnées 1 2 Cette situation s’adapte également à une hiérarchie de classe plus complexe. A B D E C F G Autre exemple : cette fois on redéfinit pas de méthode affiche dans la classe PointCouleur public class Point { private int x,y; public Point(int x,int y) { this.x = x; this.y = y; } public void affiche() { identifie(); System.out.println(" de coordonnées "+x+" "+y); } 78 CHAPITRE 4. LES CONCEPTS D’HÉRITAGE ET DE POLYMORPHISME public void identifie() { System.out.print("Je suis un point"); } ... } public class PointCouleur extends Point { private byte couleur; public PointCouleur(int x,int y,byte couleur) { super(x,y); this.couleur = couleur; } public void identifie() { System.out.print("Je suis un point couleur de couleur "+couleur+", "); } } Si on reprend le code utilisé sur les tableaux on obtient le résultat suivant : Je Je Je Je suis suis suis suis 4.4.2 un un un un point point point point de coordonnées 0 2 couleur de couleur 2, de coordonnées 1 5 couleur de couleur 4, de coordonnées 2 3 de coordonnées 1 2 Règles du polymorphisme en Java Compatibilité. Il existe une conversion implicite d’une référence à un objet de classe T à un objet d’une classe parente de T. Liaison dynamique. Lors d’un appel d’une méthode a.f() où a est supposé être de type d’une classe T, le choix de f est déterminé : — A la compilation : on détermine dans la classe T ou ses ascendants la signature de la meilleure méthode f() convenant à l’appel, ce qui défini du même coup le type de la valeur de retour. — A l’exécution : on cherche la méthode f de syntaxe et de type de retour voulu à partir de la classe correspondante au type effectif de l’objet référencé par a (de type T ou ascendant). Si cette classe ne comporte pas de méthode appropriée on remonte le plus possible jusqu’à ce qu’on en trouve une. 4.5 La super-classe Object En Java, toute classe définie hérite de la classe Object. Une variable de type Object peut être utilisée pour référencer un objet de type quelconque. Exemple Point p = new Point(1,3); Object o; 4.5. LA SUPER-CLASSE OBJECT 79 o = p; ((Point)o).affiche(); Point p1 = (Point) o; p1.affiche(); Attention. L’instruction o.affiche(); provoque une erreur car il n’y a pas de méthode affiche définie dans la classe Object. Il faut alors faire une conversion (cast) explicite comme le montre l’exemple ci-dessus. La classe Object possède quelques méthodes, dont notamment : — la méthode public String toString() qui renvoie, par défaut, une chaı̂ne de caractères contenant le nom de la classe et l’adresse mémoire de l’objet (adresse précédée de @). Point a = new Point(1,2); System.out.println(a.toString()); //affiche Point@fc1aedf System.out.println(a); //équivalent La méthode toString définie par défaut le chaı̂ne à afficher lorsque l’on veut afficher un objet. Plutôt que de définir une méthode d’affichage on redéfinie la méthode toString dans la classe souhaitée puis on affiche directement l’objet. public class Point { private int x,y; public Point(...){...} ... public String toString() { System.out.println("Je suis un point de coordonnées "+x+" "+y); } } ... Point p = new Point(0,2); ... System.out.println(p); — La méthode public boolean equals(Object o) se content de comparer les adresses mémoire des objets référencés. Object o1 = new Point(1,2); Object o2 = new Point(1,2); o1.equals(o2); //renvoie false comme réponse o1.equals(o1); //renvoie true comme réponse Il est également possible de rédéfinir/surcharger la méthode equals dans ses propres classes : public class Point { private int x,y; public Point(...){...} ... public boolean equals(Point p) { return ((p.getX()==x) && (p.getY()==y)); } } 80 CHAPITRE 4. LES CONCEPTS D’HÉRITAGE ET DE POLYMORPHISME 4.6 Accès par membres protégés : protected En plus des droits d’accès public et private et du droit par défaut lié à la notion de paquetage, il existe un autre droit d’accès : protected. Un membre protected est accessible à des classes du même package ainsi qu’à leur classe dérivées. En pratique, ce droit d’accès est assez peu utilisé. 4.7 Méthodes et classes finales Une méthode déclarée final ne peut pas être redéfinie dans une classe dérivée. Une classe déclarée final ne peut plus être dérivée. 4.8 Les classes abstraites Une classe abstraite est une classe qui ne permet pas d’instancier des objets, elle ne peut servir que de classe de base pour une dérivation. Exemple public abstract class A { public void f() { ... // méthode complètement sécifiée dans A } //méthode abstraite pas définie dans A public abstract void g(int n); } Une classe abstraite possède au moins une méthode abstraite, utilisation du mot clé abstract. Il est possible de créer une référence de type A (A a;), mais on ne peut pas créer d’objet de type A (A a = new A(); provoque une erreur). En revanche, on crée une classe B qui dérive de A en définissant g. public class B extends A { public void g(int n) { ... } } Ensuite on peut créer des objets de type B (B b = new B();). Une classe dérivée d’une classe abstraite n’est pas obligée de redéfinir toutes les méthodes abstraites de sa classe de base. Dans ce cas c’est une classe abstraite elle aussi, il faut alors penser à utiliser le mot clé abstract dans sa définition. Une méthode abstraite est toujours publique. Intérêt. — Permet de spécifier toutes les fonctionnalités que l’on souhaite disposer dans les classes dérivées. — Permet d’exploiter le polymorphisme en étant sûr que certaines méthodes existes. 4.8. LES CLASSES ABSTRAITES Exemple. abstract class Affichable { abstract public void affiche(); } class Entier extends Affichable { private int valeur; public Entier(int n) { valeur = n; } public void affiche() { System.out.println("Je suis un entier de valeur "+valeur); } } class Flottant extends Affichable { private float valeur; public Flottant(float x) { valeur = x; } public void affiche() { System.out.println("Je suis un flottant de valeur "+valeur); } } public class Test { public static void main(String [] args) { Affichable [] tab; int i; /*compteur*/ tab = new Affichable[3]; tab[0] = new Entier(25); tab[1] = new Flottant(1.25f); tab[2] = new Entier(42); for(i=0 ; i<3 ; i++) tab[i].affiche(); } } Si on exécute le code contenu dans la méthode main, on obtient le résultat suivant : Je suis un entier de valeur 25 Je suis un flottant de valeur 1.25 Je suis un entier de valeur 42 81 82 CHAPITRE 4. LES CONCEPTS D’HÉRITAGE ET DE POLYMORPHISME 4.9 Les interfaces Une interface est une notion correspondant à une classe abstraite où aucune méthode n’est implémentée. 4.9.1 Définition et mise en œuvre public interface I { void f(int n); //public et abstract sont facultatifs void g(); } public class A implements I { //A doit redéfinir f et g } — Une même classe peut implémenter plusieurs interfaces. public interface I2 { int h(); } class B implements I,I2 { //redéfinition de f et g de I et h de I2 } — Les interfaces peuvent contenir des constantes de type static final. Ces constantes seront donc accessibles en dehors d’une classe implémentant l’interface. — Les interfaces peuvent se dériver, mais les classes dérivées obtenues sont aussi des interfaces. interface I1 { static final int MAXI = 20; void f(int n); } interface I2 extends I1 { void g(); } class A implements I1 { //redéfinit f, on a accès à MAXI directement //par exemple if(i < MAXI) ... } class B extends A implements I2 { //redéfinition de g } 4.10. CONNAÎTRE LA CLASSE ET LES TYPES D’UN OBJET 83 On peut ainsi écrire les choses suivante d’où on veut : I1 i = new A(); I1 i2 = new B(); I2 b = new B(); System.out.println("Constante "+I1.MAXI); 4.9.2 Intérêt des interfaces — Une classe peut implémenter plusieurs interfaces, alors qu’une classe ne peut dériver que d’une seule classe (éventuelle abstraite). — La notion d’interface se superpose à la notion de dérivation et ne s’y substitue pas. — On peut utiliser des variables de type d’interfaces. — Les interfaces peuvent se dériver. Les interfaces permettent notamment de fournir une solution au problème de l’héritage multiple. Graveur graver() GraveurCD GraveurDVD graver() graver() Combo Problème de l’héritage multiple : quelle méthode graver s’exécute sur le combo ? En Java il n’y a pas d’héritage multiple : on ne peut hériter que d’une seule classe. On contourne alors le problème en utilisant des interfaces. Dans l’exemple du dessus, une solution pourrait être de créer une classe Combo qui hérite de Graveur et qui implémente l’interface GraveurCD possédant une méthode graverCD et l’interface graverDVD possédant une méthode graverDVD. 4.10 Connaı̂tre la classe et les types d’un objet 4.10.1 L’opérateur instanceof C’est un opérateur booléen qui renvoie true lorsque qu’un objet peut être converti en type défini par une classe. Cette classe peut correspondre à une classe (abstraite ou non) située au dessus de la classe de la référence de l’objet dans la hiérarchie d’héritage ou une interface implémentée par l’une des classes mères ou la classe propre de l’objet. 84 CHAPITRE 4. LES CONCEPTS D’HÉRITAGE ET DE POLYMORPHISME Exemple d’utilisation if(objetA instanceof classeB) { //renvoie true si objetA peut-etre converti dans //le type classeB } Un autre exemple, dans le cas de redéfinition de equals public boolean equals(Object o) { if (o instanceof MyClass) { MyClass mc = (MyClass) o; if (mc == null) // ne peut pas etre nul ici return false; else return ... // il faudrait comparer les champs de mc } return false; } 4.10.2 La class Class Les instances de cette classe (qui n’est pas sous-classable, c’est-à-dire dont une spécification est final) sont utilisées dans une application en cours d’exécution dans une JVM pour représenter les différentes classes ou interfaces utilisées par l’application. Il en va de même des tableaux et des types primitifs du langage. Cette classe n’a pas de constructeurs : les instances sont construites automatiquement par la JVM lors du chargement des classes. Si on se réfère par exemple à la classe Object la méthode getClass permet d’accéder à la classe (référence sur Class) associée et à partir d’une telle référence des informations relatives à cette classe peuvent être obtenues par l’intermédiaire des méthodes de cette classe. Parmi celles-ci citons : — Class[ ] getClasses( ) : renvoie un tableau contenant toutes les classes publiques et interfaces ; — String getName( ) : renvoie sous forme de chaı̂ne le nom de l’entité (classe, interface, tableau ou type primitif) correspondante ; — Class getSuperclass( ) : renvoie une référence sur la sur-classe ; — boolean isInstance(Object obj) : renvoie true si la référence est compatible avec la classe courante, ce qui est similaire à l’utilisation de l’opérateur instanceof. Voici un petit exemple d’utilisation : class AAAA { int a; } class BBBB { } class CCCC extends AAAA{ } class Class1{ public static void main(String[ ] arg){ AAAA a = new AAAA( ); CCCC c = new CCCC( ); int[ ] t = new int[10]; Object[ ] tabObj = new Object[5]; BBBB[ ] tabB = new BBBB[33]; 4.11. EXERCICES DE COURS System.out.println("classe System.out.println("classe System.out.println("classe System.out.println("classe System.out.println("classe 85 de de de de de a : " + a.getClass( ).getName( )); c : " + c.getClass( ).getName( )); t : " + t.getClass( ).getName( )); tabObj : " + tabObj.getClass( ).getName( )); tabB : " + tabB.getClass( ).getName( )); } } Voici le résultat classe classe classe classe classe de de de de de a : AAAA c : CCCC t : [I tabObj : [Ljava.lang.Object; tabB : [LBBBB; D’autres utilisations de cette classe sont possibles, notamment pour la création et l’utilisation de classes dynamiques. Ceci sort du cadre de ce cours, mais pour plus d’informations vous pouvez consulter la documentation de Sun et jeter un coup d’oeil à la dernière partie du dernier chapitre. 4.11 Exercices de Cours 4.11.1 Classes abstraites - classes concrètes L’idée de cet exercice est de trouver une application concrète à toute définition abstraite. Plusieurs classes sont listées dans la colonne du milieu. L’objectif est d’imaginer des applications dans lesquelles la classe listée pourrait être abstraite et des applications où elle pourrait être concrète. Quelques exemples sont fournis pour aider à démarrer. Par exemple, la classe arbre serait abstraite dans un programme pépinière où les différences entre un chêne et un peuplier sont importantes. Mais dans un programme de simulation de golf, la classe arbre peut être concrète (par exemple une sous-classe d’obstacle), parce qu’il n’y a aucun besoin de différencier les arbres. 86 CHAPITRE 4. LES CONCEPTS D’HÉRITAGE ET DE POLYMORPHISME Concrète simulation de parcours de golf application de photo satellite Classe Arbre Abstraite application de pépinière Maison application d’architecte Ville JoueurDeFootball Chaise Client Commande Livre Magasin Fournisseur ClubDeGolf Carburateur Four 4.11.2 Compilateur Voici un bout de programme Java incomplet : public class MonstreTest{ public static void main(String [] args) { Monstre [] ma = new Monstre[3]; ma[0] = new Vampire(); ma[1] = new Dragon(); ma[2] = new Monstre(); for(int x = 0; x < 3; x++){ ma[x].fairePeur(x); } } } class Monstre{ // -- méthode A -} class Vampire extends Monstre{ // -- méthode B -} class Dragon extends Monstre{ application d’entraı̂nement 4.11. EXERCICES DE COURS 87 boolean fairePeur(int degre){ System.out.println("Souffler du feu"); return true; } } Si l’on suppose que le programme produit le résultat ci-dessous : >java MonstreTest mordre ? souffler du feu arrrgh Quelles paires de méthodes A et B peuvent être utilisées dans les classes Monstre et Dragon (respectivement) pour produire le résultat ci-dessus ? 1. A: boolean fairePeur(int d){ System.out.println("arrrgh"); return true; } B: boolean fairePeur(int x){ System.out.println("mordre ?"); return false; } 2. A: boolean fairePeur(int d){ System.out.println("arrrgh"); return true; } B: int fairePeur(int f){ System.out.println("mordre ?"); return 1; } 3. A: boolean fairePeur(int d){ System.out.println("arrrgh"); return false; } B: boolean effrayer(int x){ System.out.println("mordre ?"); return true; } 4. A: boolean fairePeur(int z){ System.out.println("arrrgh"); return true; } B: boolean fairePeur(byte b){ System.out.println("mordre ?"); return true; } Réponse : 1. Cet ensemble fonctionne, false est une valeur de retour acceptable. 2. Cet ensemble ne compile pas. La méthode fairePeur() de Vampire n’est ni une redéfinition légale, ni une surcharge légale de la méthode fairePeur() de Monstre. Une redéfinition de méthode ne peut pas modifier les arguments ni le type de retour, et ne changer QUE le type ne suffit pas à rendre une surcharge valide. 88 CHAPITRE 4. LES CONCEPTS D’HÉRITAGE ET DE POLYMORPHISME 3. Cet ensemble compile mais ne produit pas le bon résultat parce que la méthode fairePeur() de la classe Monstre n’est pas redéfinie dans la classe Vampire. 4. Cet ensemble compile mais ne produit pas le bon résultat parce que la méthode fairePeur() de Vampire accepte un byte alors qu’il faut un int. Les ensembles 3 et 4 produisent en fait le résultat suivant : 4.11.3 Qui suis-je ? Associer à chaque ligne ci-dessous un ou plusieurs mots parmi : superclasse, sous-classe, tableau, ArrayList (cf section 8.3.2), objet, tableau polymorphe, argument polymorphe, interface, méthode abstraite, classe abstraite. — Je suis incapable de grandir : — Je dis quoi faire, pas comment le faire : — J’accepte le mélange de chiens et de chats : — J’autorise les références hétérogènes : — Je peux dire si je suis vide : — Ma vie est une existence plus spécialisée : — Mon contenu est toujours homogène : — Je peux rester floue sur certains détails : — Je peux recevoir des choses que je n’ai jamais vues : — Les autres me regardent avec respect : — Je profite du travail d’une autre : — Faites ce que je dis, pas ce que je fais : — Je peux avoir un aspect différent pour différentes personnes : — J’ai oublié d’où vous veniez : — Rien de nouveau : 4.11.4 Vrai ou Faux ? Pour répondre à certaines affirmations, il peut être utile de consulter les sections 8.1,8.2 1. Pour utiliser la classe Math il faut d’abord créer une instance. 2. On peut marquer un constructeur avec le mot clé static. 3. Les variables statiques n’ont pas accès à l’état des variables d’instance de l’objet this. 4. C’est une bonne pratique d’appeler une méthode statique avec une variable de référence. 5. Les variables statiques peuvent servir à compter les instances d’une classe. 6. Les constructeurs sont appelés avant que des variables statiques ne soient initialisées. 7. MAX SIZE serait un bon nom pour une variable statique finale. 8. Un bloc initialisateur statique s’exécute avant un constructeur de la classe. 9. Si une classe est finale, toutes ses méthodes doivent être finales. 10. Une méthode finale ne peut être redéfinie que si la classe est étendue. 11. Il n’y a pas de classe enveloppe pour les booléens. 12. On utilise une classe enveloppe pour traiter une valeur primitive comme un objet. 13. Les méthodes parseXXX retournent toujours une chaı̂ne de caractères. Chapitre 5 La gestion d’exceptions 5.1 Présentation La gestion d’exceptions permet : — de détecter une anomalie et de la traiter indépendamment de sa détection, — de séparer la gestion des anomalies du reste du code. Une anomalie peut être due, par exemple, à des données incorrectes, à un fin de fichier prématurée, à un événement non prévu par le programmeur. Exemple. On dispose d’une classe Point pour laquelle on ne veut pas gérer des données négatives. Nous lançons une exception lorsque les coordonnées sont négatives par l’instruction throw. Pour commencer nous créons un objet donc le type sert à identifier l’exception concernée. Une classe d’exception se définit en héritant de la classe Exception déjà définie dans l’API java. public class ErreurCoordonnees extends Exception { } Ensuite, pour “lancer” une exception nous utiliserons : throw new ErreurCoordonnees(); Avant cela il faut indiquer le type d’exceptions qui peuvent être rencontrées dans les méthodes de la classe Point. On ajoute alors throws ErreurCoordonnees dans la déclaration des méthodes qui peuvent rencontrer ces exceptions. Voici un exemple : public class Point { private int x,y; public Point(int x,iny y) throws ErreurCoordonnees { if(x<0 || y<0) throw new ErreurCoordonnes(); this.x = x; this.y = y; } public void affiche() { System.out.println("Je suis un point de coordonnées "+x+" "+y); 89 90 CHAPITRE 5. LA GESTION D’EXCEPTIONS } } Ensuite pour récupérer une exception lancée par une méthode, on utilise les instructions try et catch : public class TestExcept { public static void main(String args[]) { try{ //Instructions qui peuvent voir une exception se déclencher Point a = new Point(1,4); a.affiche(); a = new Point(-3,5); a.affiche(); } catch(ErreurCoordonnees e) { //gestion de l’exception System.err.println("Erreur de construction"); System.exit(-1); } } } Résultat. Je suis un point de coordonnées 1 4 Erreur de construction Attention : le bloc catch doit suivre immédiatement le bloc try. 5.2 Gestion de plusieurs exceptions et transmission d’informations Exemple. class ErreurCoord extends Exception { public int abs,ord; public ErreurCoord(int abs,int ord) { this.abs = abs; this.ord = ord; } public int getAbs() { return abs; } public int getOrd() { return ord; 5.2. GESTION DE PLUSIEURS EXCEPTIONS ET TRANSMISSION D’INFORMATIONS 91 } } class ErreurCoord extends Exception { private int depx,depy; public ErreurDepl(int depx,int depy) { this.depx = depx; this.depy = depy; } public int getDepx() { return depx; } public int getDepy() { return depy; } } public class Point { private int x,y; public Point(int x,iny y) throws ErreurCoordonnees { if(x<0 || y<0) throw new ErreurCoordonnes(x,y); this.x = x; this.y = y; } public void deplace(int dx,int dy) throws ErreurDepl { if((x+dx<0) || (y+dy<0)) throw new ErreurDepl(dx,dy); x+=dx; y+=dy; } public void affiche() { System.out.println("Je suis un point de coordonnées "+x+" "+y); } } 92 CHAPITRE 5. LA GESTION D’EXCEPTIONS public class TestExcept { public static void main(String args[]) { try{ //Instructions qui peuvent voir une exception se déclencher Point a = new Point(1,4); a.affiche(); a.deplace(-3,5); a = new Point(-3,5); a.affiche(); } catch(ErreurCoord e) { //gestion de l’exception System.err.println("Erreur de construction"+e.getAbs()+" "+e.getOrd()); System.exit(-1); } catch(ErreurDepl e) { System.err.println("Erreur de deplacement "+e.getDepx()+" "+e.getDepy()); System.exit(-1); } //l’execution se poursuit ici s’il n’y a pas d’erreurs System.out.println("Fin du programme"); } } Résultat. Je suis un point de coordonnées 1 4 Erreur de deplacement -3 5 Transmission de messages On peut transmettre un message au gestionnaire d’exception en utilisant un constructeur sans argument et la méthode getMessage héritée de la classe Exception. Par exemple, avec la classe Point : public class ErreurCoord { public ErreurCoord(String msg) { super(msg); } } public class Point { ... public Point(int x,int y) { if(x<0 || y<0) throw new ErreurCoord("Erreur dans le constructeur de Point avec "+x+" "+y); this.x=x; this.y=y; } 5.3. DÉRIVATION ET REDÉCLENCHEMENT D’EXCEPTIONS 93 ... } public class TextExcept3 { public static void main(String args[]) { try{ ... } catch (ErreurCoord e) { System.out.println(e.getMessage()); System.exit(-1); } ... } } 5.3 Dérivation et redéclenchement d’exceptions Il est possible de dériver des classes d’exceptions. class ErreurPoint extends Exception { ... } class ErreurCoord extends Exception { ... } class ErreurDepl extends Exception { ... } Lorsque l’on souhaite récupérer les exceptions il faut le faire en ordre inverse par rapport à l’ordre défini par la hiérarchie. try{ ... } catch(ErreurCoord e) { ... } catch(ErreurDepl e) { ... } catch(ErreurPoint e) { ... } Redéclenchement d’exceptions. d’une autre exception. try{ ... } catch(MonException e) { throw new Except(); Il est possible de déclencher une exception dans le traitement 94 CHAPITRE 5. LA GESTION D’EXCEPTIONS } Il est possible de redéclencher une exception pour la transmettre à un niveau englobant. try{ ... } catch(MonException e) { ... throw e; } Exemple complet. class ErreurCoord extends Exception { } class ErreurBidon extends Exception { } public class Point { private int x,y; public Point(int x,int y) throws ErreurCoor { if(x<0 || y<0) throw new ErreurCoord(); this.x = x; this.y = y; } public void f() throws ErreurCoord,ErreurBidon { try{ Point p = new Point(-3,2); } catch(ErreurCoord e) { System.err.println("ErreurCoord dans le catch de f"); throw new ErreurBidon(); } } } public class Test { public static void main(String [] args) { try{ Point a = new Point(1,3); a.f(); } catch(ErreurCoord e) { System.err.println("ErreurCoord dans le catch de main"); 5.4. LE BLOC FINALLY 95 } catch(ErreurBidon e) { System.err.println("ErreurBidon dans le catch de main"); } System.err.println("Après le bloc try-catch de main"); } } Résultat. ErreurCoord dans le catch de f ErreurBidon dans le catch de main Après le bloc try-catch de main 5.4 Le bloc finally Il est possible, en Java, d’introduire à la suite d’un bloc try, un bloc particulier d’instructions qui seront toujours exécutées : — soit après la fin normale du bloc try, si aucune exception n’a été déclenchée, — soit après le gestionnaire d’exception (à condition que ce dernier n’est pas provoqué l’arrêt de l’exécution). Ce bloc est introduit par le mot clé finally et doit obligatoirement être placé après le dernier gestionnaire. try{ ... } catch(Ex e) { ... } finally{ ... } Ici le même résultat aurait pu être obtenu en supprimant tout simplement le mot clé finally et en mettant les instructions de ce bloc à la suite du gestionnaire. Ce n’est pas la même chose dans ce cas : void f() throws MonException { try{ ... } finally{ ... } } Si une exception se produit dans f, on exécutera d’abord les instructions du bloc finally avant de se brancher sur le gestionnaire d’exceptions. De manière générale, le bloc finally peut s’avérer intéressant dans le cadre de l’acquisition de ressources, c’est-à-dire tout ce qui nécessite une action pour la bonne bonne poursuite des 96 CHAPITRE 5. LA GESTION D’EXCEPTIONS opérations (fermer un fichier, créer un objet, enlever un verrou, ...). Autre cas. try{ ... if(...) break; ... } finally{ ... } //suite ... Si la commande break est exécutée, on exécute d’abord le bloc finally avant de passer à la suite. Autre exemple possible. try{ ... return 0; } finally{ ... return -1; } Ici la méthode semble devoir exécuter une instruction return 0;, mais il faut quand même exécuter le bloc finally contenant à son tour return -1;. Dans ce cas c’est la dernière valeur qui sera renvoyée (-1). 5.5 Les exceptions standard Java fournit de nombreuses classe prédéfinies dérivées de la classe Exception qui sont utilisées par certaines méthodes standard. Par exemple : IOException et ses dérivées sont utilisées par les méthodes d’entrée/sortie. D’autres exceptions sont utilisées par la JVM lors de situation anormales comme un indice de tableau hors limites, une taille de tableau négative, une opération de lecture à la fin d’un fichier, ... Il existe 2 catégories d’exception : — les exceptions explicites (ou sous contrôle) correspondant à celles que nous venons d’étudier. Elles doivent être traitées dans une méthode ou bien être mentionnées par la clause throws. — les exceptions implicites (ou hors contrôle) n’ont pas à être mentionnées dans une clause throw et on n’est pas obliger de les traiter (mais il est quand même possible de le faire). Exemple de traitement. public class TestExcept { public static void main(String [] args) { try { //on ne fait pas de vérification sur les arguments pour alléger le code int t[]; 5.6. EXERCICES DE COURS 97 int n,i; n = Integer.parseInt(args[0]); System.out.print("taille voulue "+n); t = new int[n]; i = Integer.parseInt(args[1]); System.out.print(" indice : "+i); t[i] = 12; System.out.println(" *** fin normale"); } catch(NegativeArraySizeException e) { System.out.println("Exception de taille de tableau négative "+e.getMessage()); } catch(ArrayIndexOutOfBoundsException e) { System.out.println("Exception indice de tableau "+e.getMessage()); } } } Il existe beaucoup d’exceptions et les traiter toutes serait fastidieux, on traite celles qui sont les plus pertinentes pour le problème à traiter. 5.6 5.6.1 Exercices de Cours Vrai ou Faux ? 1. Un bloc try doit être suivi d’un bloc catch et d’un bloc finally : 2. Si on écrit une méthode susceptible de déclencher une exception vérifiée par le compilateur, on doit envelopper le code dans un bloc try/catch : 3. Les blocs catch peuvent être polymorphes : . 4. Seules les exceptions vérifiées par le compilateur peuvent être interceptées : 5. Si on définit un bloc try/catch, le bloc finally correspondant est optionnel : 6. Si on définit un bloc try, on peut l’apparier avec un bloc catch ou un bloc finally, ou les deux : 7. Si on écrit une méthode qui déclare qu’elle peut lancer une exception vérifiée par le compilateur, on doit envelopper le code dans un bloc try/catch : 8. La méthode main dot gérer toutes les exceptions non gérées qui lui parviennent : 9. Un seul bloc try peut avoir plusieurs blocs catch différents : 10. Une méthode ne peut lancer qu’un seul type d’exception : 11. Un bloc finally peut s’exécuter sans qu’une exception soit lancée : 12. Un bloc finally peut exister sans bloc try : 13. Un bloc try peut exister seul, sans catch ni finally : 14. Lorsqu’on gère une exception on dit parfois qu’on l’esquive : 15. L’ordre des blocs catch n’a jamais d’importance : 16. Une méthode ayant un bloc try et un bloc finally peut éventuellement déclarer l’exception : 17. Les exceptions susceptibles de survenir à l’exécution doivent être gérées ou déclarées : 98 CHAPITRE 5. LA GESTION D’EXCEPTIONS 5.6.2 Mots croisés 1 2 4 8 5 6 9 10 3 7 11 12 13 14 15 16 17 18 19 20 21 22 23 24 HORIZONTALEMENT VERTICALEMENT 4 Donne une valeur (-) 1 A beaucoup de méthodes statiques (-) 6 Intercepte (-) 2 Déclare une exception (-) 8 Arbre généalogique (-) 3 Otées de la pile (-) 12 On en hérite (-) 5 Patron d’objet (-) 14 Recette de code (-) 6 Programmer (-) 18 Programme (-) 7 Avant le catch (-) 19 Le plus petit primitif (-) 9 Faire du nouveau (-) 20 Constante (-) 10 Transformerions en bytecode (-) 21 Lance (-) 11 Objet (-) 22 Non abstraite (-) 13 Pas un comportement (-) 23 Instances de classes (-) 15 Posent des conditions (-) 24 Gestion ou ... telle est la loi (-) 16 Erreur ou problème (-) 17 A des décimales (-) Chapitre 6 Les chaı̂nes de caractères en Java 6.1 6.1.1 Le type String Les chaı̂nes de caractères sont des objets Voici plusieurs manières de créer des chaı̂nes : ch1 String String String String ch1 ch2 ch3 ch4 = = = = new String(); new String("hello"); new String(ch2); "hello"; ch2 ch3 ch4 hello hello hello La dernière instruction est équivalente à la deuxième. Les objets de type String ne sont pas modifiables 6.1.2 Affichage d’une chaı̂ne Pour afficher une chaı̂ne sur la sortie standard on peut utiliser : System.out.println(ch2); Pour afficher une erreur, on peut utiliser : System.err.println("Attention Erreur"); 6.1.3 Longueur d’une chaı̂ne String ch = "bonjour"; int l = ch.length(); Attention : pour les chaı̂nes on utilise une méthode pour obtenir la taille, d’où l’usage des parenthèses (dans le cas de tableaux il s’agissait d’un champ !). 6.1.4 accès aux caractères String ch = "bonjour"; char c1=ch.charAt(0); //caractère à la position 0 : le ’b’ char c2=ch.charAt(2); //caractère à la position 2 : le ’n’ Note : on peut intégrer des caractères spéciaux String salutation="\tBonjour\n\t\tà tous"; 99 100 CHAPITRE 6. 6.1.5 LES CHAÎNES DE CARACTÈRES EN JAVA Concaténation : l’opérateur + String ch1 = "le langage"; String ch2 = " java"; String ch3 = ch1 + ch2; ch1 ch3 le langage ch2 java le langage java Lorsque l’opérateur + est utilisé avec au moins une chaı̂ne, les autres opérandes sont convertis automatiquement en chaı̂ne. int n=24; int y=2005; String d=new String("Nous le somme le " + n + " octobre " + y); L’opérateur += est aussi utilisable : String ch="bonjour "; ch +=" monsieur"; Un nouvel objet contenant la chaı̂ne bonjour monsieur est créé et l’ancienne chaı̂ne bonjour devient candidate au ramasse-miettes. Autre exemple : à la fin on affiche : chiffres= 0123456789 String ch="chiffres= "; for(int i=0;i<10;i++) ch+=i; System.out.println(ch); 6.1.6 Recherche dans une chaı̂ne : la méthode indexOf Elle cherche à partir du début (ou la fin) d’une chaı̂ne ou d’une position donnée : — soit la première position d’un caractère, — soit la première occurrence d’une chaı̂ne. Elle renvoie la position si une correspondance a été trouvée, -1 sinon. String mot="anticonstitutionnellement"; //mot de 25 lettres int n; n=mot.indexOf(’t’); //renvoie la position 2 n=mot.lastIndexOf(’t’); //renvoie la position 24 (dernière occurrence) n=mot.lastindexOf("ti"); //renvoie la position 12 n=mot.indexOf("ti"); //renvoie la position 2 n=mot.indexOf(’x’); //renvoie la position -1 n=mot.indexOf("ti",6); // recherche à partir de la position 6, renvoie 8 n=mot.lastIndexOf(’t’,9); //recherche à partir de la position 9, renvoie 24 6.1.7 La comparaison de chaı̂nes Les chaı̂nes étant des objets, les opérateurs == et ! = testent les adresses des objets et non le contenu. On ne peut pas les utiliser directement pour comparer des chaı̂nes. Tester l’égalité de deux chaı̂nes ch1.equals(ch2) renvoie true si le contenu de ch1 est le même que le contenu de ch2 et f alse sinon. 6.1. LE TYPE STRING 101 Comparer deux chaı̂nes ch1.compareTo(ch2) renvoie — un entier négatif si ch1 est située avant ch2 — un entier nul (0) si les 2 chaı̂nes sont égales — un entier positif si ch1 est située après ch2 L’ordre utilisé est l’ordre classique sur les caractères défini par la table ascii (correspondant à l’ordre alphabétique pour les lettres). ch1 bonjour bonj prix12 Element Element element bonjour monsieur 6.1.8 ch2 monsieur monsieur prix10 élement element élément bonjour bonjour ch1.compareTo(ch2) <0 <0 >0 <0 <0 >0 0 >0 Modification d’un caractère String ch="bonjour"; String ch1=ch.replace(’o’,’a’); Attention un nouvel objet est créé, référencé par ch1, on aurait pu écrire ch=ch.replace(’o’,’a’); 6.1.9 Extraction de sous-chaı̂nes String ch="bonjour"; String ch1=ch.substring(3); // ch1 contient "jour" String ch2=ch.substring(2,4); // ch2 contient "njo" Notez ici une exception sur le nom de la méthode. 6.1.10 Passage en majuscules/minuscules String ch1=ch.toLowerCase(); // passage en minuscule String ch2=ch.toUpperCase(); // passage en majuscule 6.1.11 Conversion d’une chaı̂ne vers un type primitif Vous pouvez utiliser les méthodes explicites suivantes : — Byte.parseByte(ch); — Short.parseShort(ch); — Integer.parseInt(ch); — Long.parseLong(ch); — Float.parseFloat(ch); — Double.parseDouble(ch); S’il y a un caractère qui pose problème lors de la conversion une exception est levée (BuildException). 6.1.12 Conversion d’un type primitif vers une chaı̂ne On peut soit utiliser l’opérateur +, soit la méthode String.value() 102 CHAPITRE 6. LES CHAÎNES DE CARACTÈRES EN JAVA float f=2.5f; String ch0=""+f; // première possibilité String ch1=String.value(f); //deuxième possibilité 6.1.13 Chaı̂nes et tableaux de caractères — Convertir un tableau de caractères en chaı̂ne char mot[]={’b’,’o’,’n’,’j’,’o’,’u’,’r’}; String ch=new String(mot); — Convertir une chaı̂ne en tableau de caractères String ch="bonjour"; char mot[]=ch.toCharArray(); 6.1.14 Tableau de chaı̂nes Le tableau args de la méthode main, contenant les arguments du programme, est un tableau de chaı̂ne. public static void main(String [] args) { System.out.println("Voici la liste des arguments"); for(int i=0; i < args.length; i++) System.out.println("Argument "+String.value(i+1)+" de longueur "+args[i].length()+" : " + args[i]); } Notez la différence entre args.length qui la longueur du tableau et args[i].length() qui est longueur de la chaı̂ne à la position i du tableau. 6.2 La classe StringBuffer La classe StringBuffer fait partie du package java.lang et contrairement à la classe String, elle utilise un buffer de taille variable pour mémoriser une chaı̂ne de caractères modifiables. Cette classe f inal maintient elle-même l’allocation d’espace supplémentaire pour mémoriser l’ajout de caractères. Elle permet donc de manipuler des chaı̂nes modifiable. Voici la liste des méthodes de cette classe : — public StringBuffer() : construit une chaı̂ne vide — public StringBuffer(int length) : construit une chaı̂ne de longueur length — public StringBuffer(String str) : construit une chaı̂ne de type StringBuffer à partir d’une chaı̂ne de caractères classique — int length() : renvoie la taille de la chaı̂ne (comme dans String) — void setCharAt(int index, char c) : modifie le caractère à la position index en le remplaçant par c — StringBuffer append(String str) : ajoute la chaı̂ne str en fin (possibilité de mettre un tableau de caractères ou un objet au lieu d’une String) — StringBuffer insert(int offset, String str) : insère une chaı̂ne à l’indice of f set — String toString() convertit la chaı̂ne au type String Pour plus d’informations, consulter la documentation de la classe StringBuffer. 6.3 La classe StringTokenizer La classe StringTokenizer fait partie du package java.util (il faut importer le package si vous voulez l’utiliser). Elle permet de décomposer une chaı̂ne de caractères en une suite de ”mots” 6.3. LA CLASSE STRINGTOKENIZER 103 séparés par des ”délimiteurs”. Voici les constructeurs : — StringTokenizer(String str, String delim, boolean returnDelims) : str est la chaı̂ne à analyser, delim est une chaı̂ne contenant les délimiteurs reconnus, returnDelims indique si les délimiteurs doivent être renvoyés comme parties de la chaı̂ne. — StringTokenizer(String str) : crée un objet StringTokenizer, str est la chaı̂ne à analyser ; les délimiteurs sont les caractères espace, tabulation, retour chariot et changement de ligne. — StringTokenizer(String str, String delim) : crée un objet StringTokenizer, str est la chaı̂ne à analyser, delim est une chaı̂ne contenant les délimiteurs reconnus. Par défaut, les délimiteurs ne sont pas renvoyés comme éléments de la chaı̂ne. La liste des méthodes : — int countTokens() : calcul le nombre de fois que la méthode nextToken() peut être appelée avant la génération d’une exception. — boolean hasMoreElements() : retourne la même valeur que la méthode hasMoreTokens(). — boolean hasMoreTokens() : vérifie s’il n’y a plus de jetons disponibles à partir de l’objet StringTokenizer. — Object nextElement() : retourne la même valeur que la méthode nextToken(). — String nextToken() : retourne le prochain jeton à partir de l’objet StringTokenizer. — String nextToken(String delim) : retourne le prochain jeton par rapport au délimiteur spécifié. Exemple : StringTokenizer st = new StringTokenizer("C’est une ligne"); while (st.hasMoreTokens()) { System.out.println(st.nextToken()); } Ce bout de code provoque l’affichage de : C’est une ligne Autre exemple : StringTokenizer st = new StringTokenizer("/home/toto/TP-JAVA","/-",false); System.out.println("Il y a "+st.countTokens()+" éléments dans la cha^ ıne"); while (st.hasMoreTokens()) { System.out.println(st.nextToken()); } Ce bout de code provoque l’affichage de : home toto TP JAVA 104 CHAPITRE 6. LES CHAÎNES DE CARACTÈRES EN JAVA Chapitre 7 Les entrées/sorties 7.1 Les fichiers, la classe File L’objet File constitue une représentation abstraite d’un chemin vers un fichier ou un répertoire. Le séparateur de chemin dépend de la plateforme. Sous Unix la valeur de ce séparateur est ’/’ alors que sous Windows sa valeur est égale à ’\’. 7.1.1 Les champs — static String pathSeparator : Ce champ représente le caractère de séparation par défaut dépendant du système sous-jacent, sous la forme d’une chaı̂ne de caractères pour des raisons de commodités. — static char pathSeparatorChar : Ce champ représente le caractère de séparation de chemin par défaut dépendant du système sous-jacent. — static String separator : Ce champ représente le caractère de séparation par défaut dépendant du système sous-jacent, sous la forme d’une chaı̂ne de caractères pour des raisons de commodités. — static char separatorChar : Ce champ représente le caractère de séparation par défaut dépendant du système sous-jacent. 7.1.2 Les constructeurs — File(File parent, String child) crée un nouvel objet File à partir d’un autre chemin abstrait désignant le parent et d’une chaı̂ne de caractères indiquant un chemin enfant. — File(String pathname) crée un nouvel objet File à partir d’un chemin donné sous la forme d’une chaı̂ne de caractères. — File(String parent, String child) crée un nouvel objet File à partir de deux chaı̂nes de caractères désignant respectivement un chemin parent et un autre enfant. — File(URI uri) crée un nouvel objet File en convertissant l’URI fourni en chemin abstrait. 7.1.3 Les méthodes — boolean canRead() teste si l’application peut lire le fichier désigné par l’objet File. — boolean canWrite() teste si l’application peut modifier le fichier désigné par le chemin abstrait. — int compareTo(File pathname) compare lexicographiquement deux objets File. — int compareTo(Object o) compare l’objet File par rapport à un autre objet. — boolean createNewFile() crée atomiquement un nouveau fichier vide désigné par le chemin abstrait si et seulement si un fichier de même nom n’existe pas encore. 105 106 CHAPITRE 7. LES ENTRÉES/SORTIES — static File createTempFile(String prefix, String suffix) crée un nouvel objet File vide dans le répertoire temporaire par défaut en utilisant le préfixe et le suffixe donnés pour générer son nom. — static File createTempFile(String prefix, String suffix, File directory) crée un nouvel objet File vide dans le répertoire spécifié en utilisant le préfixe et le suffixe donnés pour générer son nom. — boolean delete() supprime le fichier ou le répertoire désigné par l’objet File. — void deleteOnExit() demande que le fichier ou le répertoire désigné par le chemin abstrait soit supprimé lorsque la Machine Virtuelle Java s’arrête. — boolean equals(Object obj) teste l’égalité de l’objet File par rapport à un autre objet. — boolean exists() teste si le fichier désigné par le chemin abstrait existe. — File getAbsoluteFile() retourne la forme absolue du chemin abstrait. — String getAbsolutePath() retourne le chemin absolu sous la forme d’une chaı̂ne de caractères de l’objet File. — String getName() retourne le nom du fichier ou du répertoire désigné par le chemin abstrait. — String getParent() retourne le chemin parent, sous la forme d’une chaı̂ne de caractères, de l’objet File, ou null si ce dernier n’a pas de parent. — File getParentFile() retourne le chemin abstrait parent de l’objet File, ou null si ce dernier n’a pas de parent. — String getPath() convertit l’objet File vers un chemin sous forme de chaı̂ne de caractères. int hashCode() cacule un hash code pour l’objet File. — boolean isAbsolute() teste si le chemin abstrait est absolu. — boolean isDirectory() teste si le fichier désigné par le chemin abstrait est un répertoire. — boolean isFile() teste si le fichier désigné par le chemin abstrait est un fichier normal. — boolean isHidden() teste si le fichier désigné par le chemin abstrait est un fichier caché. — long lastModified() retourne le temps de la dernière modification du fichier donné par le chemin abstrait. — long length() Renvoie la longueur du fichier désigné par le chemin asbtrait. — String[] list() retourne un tableau de chaı̂nes de caractères indiquant les fichiers et répertoires dans le répertoire spécifié par le chemin abstrait. — boolean mkdir() crée le répertoire désigné par le chemin abstrait. — boolean mkdirs() crée le répertoire et éventuellement ses répertoires parents désignés par le chemin abstrait. — boolean renameTo(File dest) renomme le fichier défini par le chemin abstrait. — boolean setLastModified(long time) fixe le temps de la dernière modification du fichier ou du répertoire désigné par le chemin abstrait. — boolean setReadOnly() marque le fichier ou le répertoire nommé par le chemin abstrait de telle façon que des opérations en lecture seule sont autorisées. 7.2 Les flux L’écriture et la lecture de fichiers impliquent l’utilisation de flux (streams). Ces derniers représentent des canaux de transmission de données à partir d’une source ou vers une destination. Dans ces canaux, deux types d’informations peuvent transiter de l’application vers un fichier ou inversement. Il s’agı̂t de flux de données binaires ou de caractères. Cette distinction permet de traiter différemment les fichiers textes dont l’extension peut être .txt, .ini, .log, .xml, etc., et les fichiers binaires comme les images, vidéos, sons et autres... Les flux sont également capables de faire transiter des informations provenant d’une entrée standard (System.in) telle que le clavier, ou allant vers une sortie standard (System.out) comme l’écran. Ainsi, on distingue quatre familles de flux appelés également flots : — Les flots en lecture ou d’entrée, 7.3. LECTURE/ÉCRITURE 107 — Les flots en écriture ou de sortie, — Les flots binaires, — Les flots de caractères. Les flots d’entrée et de sortie peuvent être utilisés pour n’importe quel périphérique, à l’image d’un écran, d’une imprimante, d’un fichier disque ou du réseau (sortie du programme) et d’un clavier, d’une souris, d’un ordinateur distant ou d’un fichier disque (entrée du programme). Il est possible de combiner les flux ci-dessus, de cette façon : — Les flots binaires en lecture, — Les flots binaires en écriture, — Les flots de caractères en lecture, — Les flots de caractères en écriture. Les flux sont unidirectionnels, et possèdent donc seulement une entrée et une sortie. Ils ne peuvent envoyer des informations que dans un seul et unique sens. Les classes appropriées pour l’utilisation des flux se trouvent dans le paquetage java.io. On distingue quatre classes principales dont toutes les autres héritent des représentations et comportements propre à chaque famille. — java.io.InputStream (flot binaire en lecture), — java.io.OutputStream (flot binaire en écriture), — java.io.Reader (flot de caractères en lecture), — java.io.Writer (flot de caractères en écriture). 7.3 Lecture/écriture Voici un exemple de classe effectuant une lecture/écriture d’un fichier. Dans un premier temps, on lit le contenu d’un fichier nommé "poeme.txt" et on range chacune des lignes dans une structure de type StringBuffer (cf le chapitre précédent pour ce type). Dans un second temps, on écrit dans un fichier nommé "poeme copie.txt", on commence par y écrire Ma chaine puis un saut de ligne, ensuite on écrit le contenu stocké dans un StringBuffer. import java.io.*; class GestionFichier { public static void litEtEcrit(String args[]) { StringBuffer contenu = new StringBuffer(); try{ //exemple de lecture File cheminAbstraitEntree = new File("poeme.txt"); FileReader fluxLectureTexte = new FileReader(cheminAbstraitEntree); BufferedReader tamponLecture = new BufferedReader(fluxLectureTexte); String ligne; while((ligne = tamponLecture.readLine()) != null){ System.out.print (ligne); contenu.append(ligne); contenu.append("\r\n"); } tamponLecture.close(); fluxLectureTexte.close(); } catch(IOException e) { System.err.println("Erreur de lecture"); } 108 CHAPITRE 7. LES ENTRÉES/SORTIES try{ //exemple d’écriture File cheminAbstraitSortie = new File("poeme_copie.txt"); FileWriter fluxEcritureTexte = new FileWriter(cheminAbstraitSortie); BufferedWriter tamponEcriture = new BufferedWriter(fluxEcritureTexte); tamponEcriture.write("Ma chaine\n"); tamponEcriture.flush(); tamponEcriture.write(contenu.toString()); tamponEcriture.flush(); tamponEcriture.close(); fluxEcritureTexte.close(); } catch(IOException e) { System.err.println("Erreur d’écriture"); } } } On commence par créer une variable de type File. Ensuite, dans le cas d’une lecture de fichier, on crée une flux de type FileReader et ce flux est mis dans un tampon de type BufferedReader. La lecture dans le tampon se fait à l’aide de la méthode readLine(). Pour l’écriture, on crée un flux de type FileWriter, que l’on met dans un tampon BufferedWriter et l’écriture se fait à l’aide de la méthode write. La méthode flush sert à vider le contenu du tampon. A chaque fois, les différents flux doivent être d’abord ouverts (instructions new), puis fermés (instructions close à la fin. Note : pour limiter les accès au disque dur, java conserve les caractères devant être écrits dans un fichier dans un tampon (buffer en anglais), puis écrit le contenu du tampon dans le fichier lorsque le tampon est plein. Si l’on veut forcer l’écriture du contenu du tampon dans le fichier on peut utiliser la commande flush. Il faut toujours appliquer flush au moins une fois juste avant la fermeture du fichier. Chapitre 8 Quelques éléments de l’API 8.1 La classe Math La classe Math possède 2 constantes Math.PI (le nombre pi) et Math.E (exponentielle e1 ). Elle possède également plusieurs méthodes statiques, en voici quelques-unes : — Math.random() : retourne un nombre compris entre 0.0 et 1.0 (exclu). — Math.abs(nombreEnArgument) : retourne la valeur absolue du nombre fourni en argument, le type de retour correspond au type de l’argument. — Math.round(decimalEnArgument) : arrondi à l’entier le plus proche (renvoie un int pour un float et un long pour un double). — Math.min(nombre1,nombre2) : renvoie le plus petit des 2 arguments. — Math.max(nombre1,nombre2) : renvoie le plus grand des 2 arguments. — Math.sin(nombreDouble) : renvoie le sinus du nombre, le type de retour est double. Les fonctions cos, tan, acos, asin, atan sont également disponibles. — Math.pow(nombre1,nombre2) : renvoie nombre1nombre2 , retour de type double. — Math.log(nombreDouble) : renvoie le logarithme népérien d’un nombre, retour de type double. — Math.sqrt(nombreDouble) : renvoie la racine carré d’un nombre, retour de type double. Il en existe quelques autres, à vous d’aller les consulter dans l’API. 8.2 Les classes enveloppes pour les valeurs primitives Il est parfois nécessaire de traiter des valeurs primitives comme des objets, par exemple pour les stocker dans des vecteurs (ArrayList, Vector, . . .), ensembles ou tables de correspondance (HashMap). En effet, on ne peut pas ajouter un entier, par exemple, directement dans un ArrayList puisque celui-ci ne peut contenir que des objets. Il existe une classe enveloppe (un wrapper) pour chaque type primitif. Elles trouvent dans le package java.lang, vous n’avez donc pas besoin de les importer. Elles sont faciles à reconnaı̂tre parce que chacune d’elles est nommée d’après le type primitif qu’elle enveloppe, mais la première lettre est une majuscule pour respecter la convention de nommage des classes. Voici les 8 classes enveloppe qui existent, notez que la classe Integer correspond aux int, que la classe Character aux char, et que les autres classes ont le même nom que leur type associé. — Boolean — Character — Byte — Short — Integer — Long — Float 109 110 CHAPITRE 8. QUELQUES ÉLÉMENTS DE L’API — Double Voici maintenant comment faire pour envelopper une valeur et revenir au type primitif. On transmet la valeur au constructeur de la classe enveloppe. int i = 145; Integer valeurI = new Integer(i); . . . int valeurOriginale = valeurI.intValue(); Notez que toutes les classes fonctionnent de la même manière, Boolean a une méthode booleanValue(), Character a une méthode characterValue(), etc. Ces classes ont également des méthodes statiques très utiles comme Integer.parseInt(). De manière générale, les méthodes de transformation parseXXX() acceptent une valeur de type String et retournent une valeur de type primitif. String s = "2"; int x = Integer.parseInt(s); double d = Double.parseDouble("420.24"); //Attention petite différence pour les booleens //qui n’acceptent que des valeurs true/false boolean b = new Boolean("true").booleanValue(); Notez que lorsqu’une chaı̂ne de caractère ne peut être convertie, une exception NumberFormatException peut se produire. Elle se produit à l’exécution, vous n’êtes pas obligé de la gérer ou de la déclarer. Nous pouvons également faire l’inverse : transformer un nombre en chaı̂ne de caractères. Il existe 2 méthodes principales : double d = 42.5; String doubleString1 = "" + d; String doubleString2 = Double.toString(d); 8.3 8.3.1 Les collections Définition Les principales structures de données des classes utilitaires (java.util) sont regroupées sous une même interface Collection. Cette interface est implémenté par des ensembles, vecteurs dynamiques, tables associatives que nous allons voir dans cette partie. Les éléments de type collection possèdent des méthodes pour ajouter, supprimer, modifier des éléments de type divers. Elles peuvent être parcourues à l’aide d’itérateur (autrefois ceux-ci portaient le nom d’énumeration). Nous verrons une illustration des différentes possibilités des collections lors des descriptions des classes présentées ci-dessous implémentant l’interface Collection (nous ne verrons cependant pas la classe LinkedList mais ses fonctionnalités sont proches de celles des autres classes). Notez que lorsque que l’ordre des éléments est important la méthode compareTo (déjà vue lors de la partie sur la classe Object) est utilisée. 8.3.2 ArrayList Il s’agit d’une sorte de tableau d’objets de taille variable, fonctionnant comme une sorte de liste. Il peut être vu comme un vecteur. 8.3. LES COLLECTIONS 111 Construction ArrayList v = new ArrayList(); //vecteur dynamique vide Ajout d’un élément Object elem; ... v.add(elem); Taille de la liste la liste. On utilise la méthode size() qui renvoie le nombre d’éléments contenus dans Suppression d’un élément Object o = v.remove(3); //supprime le 3eme élément que l’on obtient dans o //l’element renvoyé est de type objet //les éléments suivants sont décalés vers la gauche v.removeRange(3,8); //supprime les éléments de rang 3 à 8 Accès aux éléments ArrayList v = new ArrayList(); ... for(int i=0;i<v.size();i++) { System.out.println(v.get(i)+" "); } Syste.out.println(); Modifier un élément à l’indice i ArrayList v = new ArrayList(); Object o; ... Object o1 = v.set(i,o); La méthode set renvoie la valeur de l’objet à l’indice i avant modification. Parcours à l’aide d’un itérateur ListIterator it = v.listIterator(); while(it.hasNext()) { Object o = it.next(); ... } Autes méthodes — La méthode boolean contains(Object o) renvoie true si l’objet est dans la liste et false sinon. — boolean isEmpty() renvoie true si la liste est vide et false sinon. — int indexOf(Object o) renvoie l’indice de l’objet o dans la liste ou -1 s’il n’est pas présent 112 CHAPITRE 8. QUELQUES ÉLÉMENTS DE L’API L’ancienne classe Vector Dans ses versions antérieures, on y trouvait une classe Vector permettant comme ArrayList de manipuler des vecteurs dynamiques. Cette classe est synchronisée, c’est-à-dire que 2 processus concurrents (threads) peuvent accéder au même vecteur. Dans la version 5 de Java, la classe Vector a été fortement remaniée et offre de nouvelles possibilités (à la fois en facilité d’écriture et en fonctionnalités). A vous de découvrir cette nouvelle classe Vector. 8.3.3 Les ensembles Deux classes implémentent la notion d’ensemble : HashSet et TreeSet. Un ensemble est une collection non ordonnée d’éléments, un élément ne pouvant apparaı̂tre qu’au plus une fois. Nous nous focaliserons plutôt ici sur la notion de HashSet. Construction et parcours HashSet e1 = new HashSet(); // ensemble vide Hashset e2 = new HashSet(c); // ensemble contenant tous les elements d’une collection c Un parcours d’un ensemble se fait à l’aide d’un itérateur. HashSet e; ... Iterator it = e.iterator(); while(it.hasNext()) { Object o = it.next(); .... } Ajout d’un élémént HashSet e; Object elem; ... boolean existe = e.add(elem); if(existe) Sytem.out.println(elem+" existe deja"); else Sytem.out.println(elem+" a ete ajoute"); Suppression HashSet e; Object elem; ... boolean existe = e.remove(elem); if(existe) Sytem.out.println(elem+" a ete supprime"); else Sytem.out.println(elem+" n’existe pas"); Autre possibilité. HashSet e; ... Iterator it = e.iterator(); it.next(); it.next(); it.remove(); 8.3. LES COLLECTIONS 113 Opérations ensemblistes — e1.addAll(e2) place dans e1 tous les éléments de e2. — e1.retain(e2) garde dans e1 tout ce qui appartient à e2. — e1.removeAll(e2) supprime de e1 tout ce qui appartient à e2. Notion de Hachage Un ensemble HashSet est implémenté par une table de hachage, c’est-àdire que ses éléments sont stockés selon une position donnée. Cette position est définie selon un code calculé par la méthode int hashCode() utilisant la valeur effective des objets. Les classes String et File par exemple implémentent déjà cette méthode. Par contre les autres classes utilisent par défaut une méthode dérivée de Object qui se content d’utiliser comme valeur la simple adresse des objets (dans ces conditions 2 objets de même valeur auront toujours des codes de hachage différents). Si l’on souhaite définir une égalité des éléments basés sur leur valeur effective, il faut redéfinir la méthode hashCode dans la classe correspondante. Voici une exemple avec la classe Point. La redéfinition de la méthode equals sert à définir l’égalité entre les objets de manière à n’avoir qu’un seul élément dans un ensemble (pour rappel dans un HashSet les éléments ne peuvent apparaitre qu’une seule fois). import java.util.*; class Point { private int x,y; public Point(int x,int y) { this.x = x; this.y = y; } public int hashCode() { return x+y; } public boolean equals(Object pp) { Point p = (Point) pp; return ((this.x=p.getX()) && (this.y==p.getY())); } public String toString() { return "["+x+" "+"] "; } public int getX() { return x; } public int getY() { return y; } } public class EnsPt { public static void main(String [] args) { Point p1 = new Point(1,3); 114 CHAPITRE 8. QUELQUES ÉLÉMENTS DE L’API Point p1 = new Point(2,2); Point p1 = new Point(4,5); Point p1 = new Point(1,8); Point [] p = {p1, p2, p1, p3, p4, p3}; HastSet ens = new HashSet(); for(int i=0; i<p.length; i++) { System.out.print("le point "+p[i]); boolean ajoute = ens.add(p[i]); if(ajoute) System.out.println(" a ete ajoute"); else System.out.println(" est deja present"); System.out.print("ensemble : "); affiche(ens); } } public static void affiche(HashSet ens) { Iterator iter = ens.iterator(); while(iter.hashNext()) { Point p = (Point) iter.next(); System.out.print(p); } System.out.println(); } } Résultat. le point ensemble le point ensemble le point ensemble le point ensemble le point ensemble le point ensemble [1 3] a ete ajoute = [1 3] [2 2] a ete ajoute = [2 2] [1 3] [1 3] est deja present = [2 2] [1 3] [4 5] a ete ajoute = [4 5] [2 2] [1 3] [1 8] a ete ajoute = [1 8] [4 5] [2 2] [1 3] [4 5] est deja present = [1 8] [4 5] [2 2] [1 3] Remarque la fonction de hachage est dictée par la simplicité, des points de coordonnées différentes peuvent avoir la valeur de hashCode. Il faudrait trouver une fonction qui renvoie une valeur unique pour chaque point. 8.3.4 Les algorithmes Recherche de maximum ou minimum import java.util.*; 8.3. LES COLLECTIONS /*la methode compareTo est definie dans l’interface Comparable class Point implements Comparable { private int x,y; public Point(int x,int y) { this.x = x; this.y = y; } /*ordre simple defini sur la valeur de x*/ public int compareTo(Object pp) { (Point) p = (Point) pp; if(this.x < p.getX()) return -1; else if(this.x==p.getX()) return 0; else return 1; } public String toString() { return "["+x+" "+y+"]"; } public int getX() {return x;} public int getY() {return y;} } public class MaxMin { public static void main(String [] args) { Point p1 = new Point(1,3); Point p2 = new Point(2,2); Point p3 = new Point(4,5); Point p4 = new Point(1,8); ArrayList l = new ArrayList(); l.add(p1); l.add(p2); l.add(p3); l.add(p4); /*Max selon compareTo de Point*/ Point pMax1 = (Point)Collections.max(l); //max suivant compareTo System.out.println("Max suivant compareTo "+pMax1); } } Résultat. Max suivant compareTo = [4 5] Algorithme de tris et mélanges Exemple complet. import java.util.*; L’ordre utilisé est celui définit par la méthode compareTo ; 115 116 CHAPITRE 8. QUELQUES ÉLÉMENTS DE L’API public class Tri { public static void main(String [] args) { int nb[] = {4, 9, 2, 3, 8, 1, 3, 5}; ArrayList t = new ArralyList(); for(int i = 0; i<nb.length; i++) t.add(new Integer(nb[i])); System.out.println("t initial = "+t); Collections.sort(t); //tri des élements de t System.out.println("t trie = "+t); Collections.shuffle(t); //melange des élements de t System.out.println("t melange = "+t); Collections.sort(t,Collections.reverseOrder()); //tri des élements de t en ordre inverse System.out.println("t trie inverse= "+t); } } Résultat. t t t t initial = trie = melage = trie inverse= 8.3.5 [4, [1, [9, [9, 9, 2, 2, 8, 2, 3, 1, 5, 3, 3, 4, 4, 8, 4, 3, 3, 1, 5, 5, 3, 3, 8, 3, 3, 5] 9] 8] 1] Les tables associatives (HashMap) Une table associative (ou de hachage) permet de conserver une information association deux parties nommées clé et valeur. Elle est principalement destinée à retrouver la valeur associée à une clé donnée. Les exemples les plus caractéristiques de telles tables sont : — le dictionnaire : à un mot (clé) on associe une valeur qui est sa définition, — l’annuaire usuel : à un nom (clé) on associe une valeur comportant le numéro de téléphone et éventuellement une adresse, — l’annuaire inversé : à un numéro de téléphone (qui devient la clé) on associe une valeur comportant le nom et éventuellement une adresse. Ces tables sont très utilisées lorsque l’on souhaite manipuler des données provenant de bases de données : on peut associer à un enregistrement un ensemble de valeurs. Ajout d’information HashMap m = new HashMap(); //table vide ... m.put("p",new Integer(3)); //ajoute à la table m un nouvel element //associant la clé "p" (String) à la valeur 3 (Integer) Si la clé fournie à put existe déjà, la valeur associée remplacera l’ancienne (une clé donnée ne pouvant figurer qu’une seule fois dans la table). put fourni en retour soit l’ancienne valeur si la clé existait déjà soit null. Les clés et les valeurs doivent obligatoirement être des objets. Il n’est pas nécessaire que les clés soient de même type, pas plus que les éléments, ce sera cependant souvent le cas pour des raisons évidentes de facilité d’exploitation de la table. 8.3. LES COLLECTIONS 117 Recherche d’information Object o = m.get("x"); if(o==null) System.out.println("Aucune valeur associée à la clé"); if(m.containsKey("p")) Integer n = (Integer) m.get("p"); Suppression d’information Object cle = "x"; Object val = m.remove(cle); //supprime cle+valeur de cle x if(val!=null) System.out.println("On a supprime l’element de cle "+cle+" et de valeur"+val); else System.out.println("la clé "+cle+" n’existe pas"); Parcours d’une table : la notion de vue HashMap m; .... Set entrees = m.entrySet(); //entrees est un ensemble de paires Iterator iter = entrees.iterator(); while(iter.hasNext()) { Map.Entry entree = (Map.Entry) iter.next(); //paire courante Object cle = entree.getKey(); //cle de la paire courante Object valeur = entree.getValue(); //valeur de la paire courante ... } L’ensemble renvoyé par entrySet n’est pas une copie de la table, c’est une vue. Autre vues — L’ensemble des clés : HashMap m; ... Set cles = m.keySet(); — La collection des valeurs : Collection valeurs = m.values(); Exemple complet import java.util.*; public class Map1 { public static void main(String [] args) { HashMap m = new HashMap(); m.put("c","10"); m.put("f","20"); m.put("k","30"); m.put("x","40"); m.put("p","50"); m.put("g","60"); System.out.println("map initial //retrouver la valeur associée à la clé "f" String ch = (String) m.get("f"); "+m); 118 CHAPITRE 8. QUELQUES ÉLÉMENTS DE L’API System.out.println("Valeur associée à f "+ch); //ensemble des valeurs Collection valeurs =m.values(); System.out.println("Liste des valeurs initiales : "+valeurs); valeurs.remove("30");//on supprime la valeur "30" par la vue associee Sytem.out.println("liste des valeurs apres suppression : "+valeurs); //ensemble des cles Set cles = m.keySet(); System.out.println("liste des cles initiales : "+cles); cles.remove("p"); //on supprime la cle "p" par la vue associee System.out.println("liste des cles apres suppression : "+cles); //modification de la valeur associee a la cle x Set entrees = m.entrySet(); Iterator Iter = entrees.iterator(); while(iter.hasNext()) { Map.Entry entree = (Map.Entry) iter.next(); String valeur = (String) entree.getValue(); if(valeur.equals("20")) { System.out.println("Valeur 20 trouvee en cle "+(String)entree.getKey()); iter.remove(); break; } } System.out.println("map apres supression element 20 "); //on supprime l’element de cle "f" m.remove("f"); System.out.println("map apres suppression f: "+m); System.out.println("liste des cles apres suppression f : "+cles); System.out.println("liste des valeurs apres suppression de f : "+valeurs); } } Résultat. map initial : valeur associee a f : liste des valeurs initiales : liste des valeurs apres suppression : liste des cles initiales : liste des cles apres suppression : valeur associee a x avant modif : map apres modif de x liste des valeurs apres modif de x : valeur 20 trouvee en cle f map apres supression element 20 : map apres suppression f : liste des cles apres suppression f : liste des valeurs apres suppresion de f : {c=10, x=40, 20 {10, 40, 50, {10, 40, 50, [c, x, p, g, [c, x, g, f] 40 {c=10, x=25, [10, 25, 60, p=50, k=30, g=60, f=20} 30, 60, 20} 60, 20} f] g=60, f=20} 20] {c=10, x=25, g=60} {c=10, x=25, g=60} [c, x, g] [10, 25, 60] Note. Vous pourrez rencontrer la classe Hashtable qui est l’ancêtre de la classe HashMap. 8.4. DIVERS 8.4 8.4.1 119 Divers Eléments disponibles à partir de Java 1.5 Il existe plusieurs instructions et structures qui ont été ajoutées à partir de la version 1.5. Nous indiquons deux principales ici, à vous de découvrir les autres si vous souhaitez aller plus loin. Boucle for améliorée Une nouvelle boucle for a été ajoutée au language dans le but d’alléger l’écriture de deux sortes de boucles fréquemment utilisées : le parcours d’un tableau et le parcours d’une collection. — Si un tableau contient des éléments d’un certain type TypeElement (qui peut-être un type primitif, un tableau ou une classe), la boucle for(int i=0;i<tableau.length;i++) { lancerMethode(tableau[i]); } peut être écrite plus simplement : for(TypeElement elt : tableau) lancerMethode(elt); — si liste est une structure de donnée capable d’être parcourue par un itérateur (c’est-à-dire un objet de type Collection) et contenant toujours des élements de type TypeElement, la boucle for(Iterateur it=liste.iterator() ; it.hasNext() ; ) lancerMethode( (TypeElement) it.next()); peut être écrite plus simplement : for(TypeElement elt: liste) lancerMethode(elt); 8.4.2 Généricité Si on souhaite définir une collection (vector, arraylist, ...) contenant un ensemble d’objets de sorte que tous ces objets soient de type TypeObjet, on peut définir des collections ne contenant que ce type d’objet en déclarant : List<TypeElement>. Ceci permet de vérifier que l’ajout d’éléments à la liste sont bien de type TypeElement et que lors d’accès à des éléments de la liste on a la certitude qu’ils sont bien de type TypeElement. Un petit exemple. Vector<TypeElement> v=new Vector<TypeElement>(); //déclaration ... //on peut ajouter uniquement des objets de TypeElement //ici monobjet doit etre obligatoirement de ce type v.add(monobjet); ... //on peut récupérer directement des objets TypeElement for(int i=0;i<v.size();i++) { TypeElement e=v.get(i); lancerMethode(e); } 8.4.3 Réflection et Manipulation des types Note : cette section sort du cadre de ce cours et est là uniquement à titre d’information. Le 120 CHAPITRE 8. QUELQUES ÉLÉMENTS DE L’API contenu de cette section est tiré du polycopié Java d’Henri Garreta. La classe java.lang.Class et les classes du paquet java.lang.reflect (aux noms évocateurs, comme Field, Method, Constructor, Array, etc.) offrent la possibilité de pratiquer une certaine introspection : un objet peut inspecter une classe, éventuellement la sienne propre, accéder à la liste de ses membres, appeler une méthode dont le nom n’est connu qu’à l’exécution, etc. Une instance de la classe java.lang.Class représente un type (c’est-à-dire un type primitif ou une classe). Supposons que nous ayons une variable déclarée ainsi : Class uneClasse; Nous avons plusieurs manières de lui donner une valeur : — une classe-enveloppe d’un type primitif (Integer, Double, etc.) possède une constante de classe TYPE qui représente le type primitif en question ; de plus, l’expression type.class a le même effet. Ainsi, les deux expressions suivantes affectent le type int à la variable uneClasse : uneClasse = Integer.TYPE; uneClasse = int.class; — si uneClasse est l’identificateur d’une classe, alors l’expression uneClasse.class a pour valeur la classe en question. L’exemple suivant affecte le type java.math.BigInteger à la variable uneClasse : uneClasse = java.math.BigInteger.class; Notez la différence entre TYPE et class pour les classes-enveloppes des types primitifs : Integer.TYPE est le type int, tandis que Integer.class est le type java.lang.Integer. — on peut aussi demander le chargement de la classe, à partir de son nom complètement spécifié. C’est un procédé plus onéreux que les précédents, ou la plupart du travail de chargement (dont à la détection d’éventuelles erreurs dans les noms des classes) était fait durant la compilation alors que, dans le cas présent, il sera fait pendant l’excution. L’exemple suivant affecte encore le type java.math.BigInteger à la variable uneClasse : uneClasse = Class.forName("java.math.BigInteger"); — enfin, le moyen le plus naturel est de demander à un objet quelle est sa classe. L’exemple suivant affecte encore le type java.math.BigInteger à la variable uneClasse : Object unObjet = new BigInteger("92233720368547758079223372036854775807"); ... uneClasse = unObjet.getClass(); Pour survoler cette question assez pointue, voici un exemple qui illustre quelques unes des possibilités de e la classe Class et du paquet reflect. La méthode demoReflexion ci-dessous, purement démonstrative, prend deux objets quelconques a et b et appelle successivement toutes les méthodes qui peuvent être appelées sur a avec b pour argument, c’est-à-dire les méthodes d’instance de la classe de a qui ont un unique argument de type la classe de b : 8.5. LES AUTRES ÉLÉMENTS DE L’API 121 import java.lang.reflect.Method; import java.math.BigInteger; public class DemoReflexion { static void demoReflexion(Object a, Object b) { try { Class classe = a.getClass(); Method[] methodes = classe.getMethods(); for (int i = 0; i < methodes.length; i++) { Method methode = methodes[i]; Class[] params = methode.getParameterTypes(); if (params.length == 1 && params[0] == b.getClass()) { Object r = methode.invoke(a, new Object[] { b }); System.out.println( a + "." + methode.getName() + "(" + b + ") = " + r); } } } catch (Exception exc) { System.out.println("Probl‘me : " + exc); } } public static void main(String[] args) { demoReflexion(new BigInteger("1001"), new BigInteger("1003")); } } Le programme précédent affiche la sortie suivante : 1001.compareTo(1003) = -1 1001.min(1003) = 1001 1001.add(1003) = 2004 ... 1001.gcd(1003) = 1 1001.mod(1003) = 1001 1001.modInverse(1003) = 501 8.5 Les autres éléments de l’API A vous de consulter régulièrement l’API pour trouver les classes dont vous pouvez avoir besoin. Sachez, par exemple, qu’il existe des classes pour traiter des dates, du son, . . .