Université Claude Bernard Lyon 1 ISFA MASTER 2 – IR, 2015–2016 Remise à Niveau Java TD1. Notions de bases André FABBRI Compétences indispensables à acquérir : • Acquérir les notions de base du langage Java (classe/objet, héritage ...) ; • Savoir mettre en place un projet Java sous NetBeans (création, paramètres). 1 Introduction Java n’est pas seulement une île tropicale du Sud-Est asiatique mais correspond aussi à un langage de programmation informatique. C’est à dire qu’il permet notamment d’écrire certains des programmes informatiques que vous manipulez tous les jours sur votre ordinateur. Pour ce faire, vous avez « simplement » à écrire dans un fichier source (c’est à dire celui avec l’extension .java), un ensemble d’instructions rédigées suivant la syntaxe du langage Java. Ce fichier sera ensuite compilé en Bytecode (c’est à dire des fichiers avec l’extension .class). Les fichiers en Bytecode ne seront pas dépendant des librairies d’exécution propre à la plateforme (Windows, Linux, Mac) sur laquelle vous avez compilé, à la différence d’autres langages de programmation informatique (comme le langage C). Les librairies nécessaires au programme seront chargées lors de son exécution par la JVM (Java Virtual Machine). En d’autres termes, votre programme sera portable sur n’importe quelle plateforme pourvue que Java soit installé. Figure 1 – Processus d’exécution d’un projet Java Lors du lancement du programme, la méthode principale sera exécutée main. En Java, elle doit disposer de l’entête suivante (dans des IDEs comme Netbeans, l’entête de cette méthode est en général écrite automatiquement) : public static void main[] (String args){ ... } 1 1.1 Exercice - Calcul de somme Question 1 Ecrire le programme java Somme (classe Somme) qui permet de calculer et d’afficher à l’écran la somme des 10 premiers entiers Ce programme affichera à l’écran : « La somme des 10 premiers entiers est : 55 » NB Création d’un projet NetBeans : 1. Fichier -> New Project ; 2. Sélectionner dans la catégorie Java un projet de type « Java Application » ; 3. Choisir le nom de la classe contenant le main afin de répondre à l’énoncé. NB Affichage dans un terminal en Java : System.out.println("mon texte"); Question 2 Modifier ce programme pour que celui-ci puisse prendre en paramètre le nombre d’entiers à sommer. 1. Modification de la fonction main : Dans la fonction public static void main(String[] args), args[0] représente le premier paramètre passé au programme sous la forme d’une chaîne de caractères (de type String). NB Transformation d’une chaîne en entier : int monEntier = Integer.parseInt(maChaîne); // par ex. maChaîne = «4» 2. Appeler la fonction main avec paramètre : — depuis NetBeans (ou eclipse) : explorer les paramètres d’exécution du projet (click droit sur le nom du projet depuis l’arborescence, puis sélectionner les propriétés) ; — en ligne de commande : trouver le répertoire contenant les classes compilées (Bytecode), et exécuter : java monPackage.Somme 55 Question 3 (bonus) Modifier le programme de façon à ce que la somme soit désormais celle de la série suivante : 1 + 1/2 + 1/3 + 1/4 + ... + 1/n. Veillez à limiter la perte de précision (vous testerez avec des float pour n = 107 ). 2 2 Un langage orienté objet Java est un langage orienté objet. En d’autres termes, les composantes du programme sont décrites par des briques logicielles (appelés objets) qui sont définies arbitrairement par le concepteur du programme. Un objet peut correspondre par exemple à un concept comme une droite ou un point mais il peut aussi décrire des éléments du « monde physique » comme un étudiant, une voiture. En pratique, chaque objet créé par le programme est l’instance d’une classe définie par le concepteur du programme. Une classe définit en quelque sorte les spécifications des objets créés ; c’est à dire de quoi est composé un objet et comment il est possible de l’utiliser. Une classe est composée d’un ensemble d’attributs (variables internes propres à l’objet) et d’un ensemble de méthodes (fonctions spécifiques aux objets de cette classe). Les attributs et les méthodes forment ce que l’on appelle les membres de la classe. En UML, une classe se représente graphiquement de la manière suivante : Figure 2 – Présentation graphique d’une classe En Java, le code source associée à une classe doit être dans un fichier portant le même nom ! Ainsi le code source associé à la classe présentée ci-dessus devra être stocké dans un fichier appelé NomDeLaClasse.java. Il contiendrait alors les instructions Java suivantes : class NomDeLaClasse{ //attributs private TypeA A_attributPrive; public TypeB B_attributPublic; protected TypeC C_attributProtege; //methodes public TypeMethodeD D_methodePublique() { //code de la fonction } private TypeMethodeE E_methodePrivee() {} protected TypeMethodeF F_methodeProtegee(){} public TypeMethodeG G_avecParametres(Type1 param1,Type2 param2){} }; 3 2.1 Attributs d’une classe En Java, les attributs d’une classe s’écrivent de la manière suivante : (Visibilité) Type Nom (= valeur par défaut) ; La visibilité : public, private, protected ou rien (cf. section 2.4) ; Le type : de l’attribut comme float, String ou une classe déjà existante ; Le nom : de l’attribut, c’est à dire comment il sera identifié dans votre code source. Il est par ailleurs possible d’attribuer une valeur par défaut à un attribut lorsqu’un objet est créé. 2.2 Méthodes d’une classe En Java, les méthodes d’une classe s’écrivent de la manière suivante : (Visibilité) Type de retour Nom ( paramètres ) { code } La visibilité : public, private ou protected ou rien (cf. section 2.4) ; Le type : de variable renvoyée à la fin de la fonction (instruction return) a ; Le nom : pour appeler la fonction ; Les paramètres : de la fonction sous la forme Type nom, ; Le code de la fonction à exécuter lorsque celle-ci est appelée. En mémoire, toute méthode est identifiée, de façon unique, par sa signature. La signature est la combinaison du Nom, des Paramètres (en particulier le type et l’ordre de chacun des paramètres) et le Type de retour. Cela veut dire par exemple que deux méthodes avec des noms et des types identiques mais des paramètres différents seront différentes : on parle alors de surcharge de méthode. a. Si aucune valeur est renvoyée, le mot clé void est nécessaire. 2.3 Constructeur d’une classe Un constructeur est une méthode particulière d’une classe qui permet de créer de nouvelles instances de cette classe : des objets. Il définit notamment le code à exécuter lors de la création de l’objet (initialisation des attributs, éventuel affichage). Un constructeur possède le même nom que la classe et ne possède pas de type de retour. Il peut avoir cependant des paramètres pour initialiser les attributs de l’objet par exemple. Une même classe peut avoir différents constructeurs avec des paramètres différents (le constructeur sera alors surchargé). Par exemple pour la classe précédente on peut avoir les constructeurs : 4 class NomDeLaClasse{ //attributs private TypeA A_attributPrive; //... //methodes public NomDeLaClasse() { A_attributPrive =0; } public NomDeLaClasse(TypeA paramA) { A_attributPrive = paramA; } }; En Java, l’appel du constructeur d’une classe est réalisé par l’opérateur new. Par exemple pour créer un nouvel objet de type NomDeLaClasse, il suffit d’écrire : NomDeLaClasse monObjet = new NomDeLaClasse(objetDuTypeA); 2.4 Accès aux membres d’un objet Chaque objet mettra en oeuvre les spécifications correspondant à sa classe. En d’autres termes, la création d’un nouvel objet implique : l’allocation en mémoire d’un espace pour chacun des attributs et des références à chacune des méthodes de la classe. Pour accéder aux membres d’un objet il est nécessaire d’utiliser l’opérateur (« Point »). Par exemple : . NomDeLaClasse monObjet = new NomDeLaClasse(); // Creation de l’objet monObjet.B_attributPublic; // accès à l’attribut B_attributPublic monObjet.D_methodePublique(); // appel de la méthode B_methodePublique() La visibilité d’un membre définit le contexte depuis lequel il est possible d’accéder à celui-ci. Dans les langages orienté objet comme Java, il existe en général 3 niveaux de visibilité : public : le membre est accessible depuis n’importe quel contexte (i.e. depuis n’importe quel fichier du projet) ; private : le membre est accessible uniquement depuis des objets appartenant à cette même classe (i.e. seulement depuis le fichier de la classe) ; protected : il s’agit d’un niveau intermédiaire entre public et private que nous reverrons plus longuement dans la section 3.1. En Java, lorsque aucune visibilité n’est spécifiée dans la classe, le membre dispose par défaut d’une visibilité package-private : le membre est accessible depuis des objets appartenant au même package (i.e. seulement depuis des fichiers appartenant au même sous-dossier que celui de la classe). 2.5 Exercice - Point, rectangle et cercle Nous souhaitons créer un programme de géométrie euclidienne. Dans le cadre d’un langage de programmation « orienté objet », nous aurons besoin de définir les classes correspondant aux concepts comme un point, un rectangle et un cercle. Question 1 Réalisez ce programme en suivant les spécifications ci-dessous et testez. 5 NB Organisation des fichiers dans NetBeans : 1. Créer un projet Geometrie : une classe Geometrie sera alors créée avec le main ; 2. Créer chacune des classes demandées dans un fichier différent. Vous testerez les méthodes dans le programme principal. Un Point a deux attributs publics correspondant à l’abscisse et à l’ordonnée du point. En outre, les objets de la classe Point disposeront des deux méthodes suivantes : • Un constructeur avec 2 paramètres pour initialiser l’abscisse et l’ordonnée ; • Une méthode qui retourne un objet de type String avec l’abscisse et l’ordonnée. Un Cercle a deux attributs publics : un Point correspondant au centre du cercle et un entier correspondant au rayon. En outre, les objets de la classe Cercle disposeront des deux méthodes suivantes : • Un constructeur avec 2 paramètres pour initialiser le centre et le rayon ; • Une méthode qui retourne un objet de type String avec le centre et le rayon. Un Rectangle a six attributs publics : quatre attributs de type Point correspondant aux quatre sommets du rectangle et deux attributs entiers correspondants à la longueur et la largeur du rectangle. En outre, les objets de la classe Rectangle disposeront des trois méthodes suivantes : • Un constructeur avec comme paramètres deux Points. Ces points correspondront alors à deux des sommets du rectangle situés sur la même diagonale. Nous supposerons ici que le rectangle formé par ces deux points sera systématiquement le rectangle le plus simple qu’il est possible de construire (c’est à dire parallèle aux axes). Vous déduirez dans le constructeur les deux autres sommets ainsi que la longueur et la largeur du rectangle ; • Une méthode qui renvoie le Point correspondant au barycentre du rectangle ; • Une méthode qui retourne un objet de type String avec les principales informations du rectangle : longueur, largeur et le barycentre. Question 2 Quel est le problème à déclarer les attributs de Rectangle en public ? Modifiez en conséquence la classe Rectangle et ajouter des méthodes pour lire chacun des champs. De bonnes pratiques de programmation consistent à déclarer systématiquement les attributs en private et de contrôler leurs accès en lecture et écriture au travers de méthodes spécifiques appelées accesseurs. En général les méthodes pour lire les attributs (appelés getter) sont de la forme : public TypeAttribut getNomAttribut(){...} et les méthodes pour modifier les attributs (appelés setter) sont de la forme : public void SetNomAttribut(TypeAttribut nouvelleValeur){...} Question 3 (bonus) Ajoutez des méthodes pour modifier les attributs de Rectangle en assurant la cohérence entre tous les autres champs à chaque fois : c’est à vous de préciser en commentaire vos choix pour l’ajustement. 6 3 Héritage de classe La définition d’une classe doit parfois être réutilisée et complétée. La programmation orienté objet dispose d’un puissant mécanisme à cet effet : l’héritage. Une classe fille héritée d’une classe mère existante récupère l’ensemble des membres de la classe existante, qu’il est ensuite possible de compléter par de nouveaux membres (attributs ou méthodes). En Java la syntaxe est la suivante : public class ClasseFille extends ClasseMere{ // code de la classe fille }; La classe fille étend le type de la classe mère : il est possible de considérer un objet de la classe fille comme étant aussi une instance de sa classe mère. 3.1 Accès aux membres de la classe mère Une classe fille accède aux membres de sa classe mère comme n’importe quel autre objet. Ainsi un objet de la classe fille ne pourra pas accéder aux membres hérités de sa classe mère si ces derniers ont une visibilité private. C’est pour cette raison que la visibilité protected a été introduite. Les membres de classe mère ayant une visibilité protected sont accessibles depuis des objets de cette même classe (comme private) et des objets appartenant à des classes héritées (classes filles). En Java, les membres ayant une visibilité protected sont en plus accessible par toutes les classes appartenant au même package (package-private). 3.2 Constructeur de la classe fille et méthodes héritées La classe fille définit un nouveau type d’objet. Il est par conséquent nécessaire de définir comment construire ces objets au travers d’un constructeur spécifique. Il est cependant possible de faire appel au constructeur de la classe mère afin d’initialiser les attributs hérités de cette classe par exemple. En Java, l’appel explicite d’une méthode de la classe mère est réalisé avec le mot-clé super. Ainsi l’appel du constructeur de la classe mère consiste à écrire super suivi des paramètres du constructeur entre parenthèses : public class ClasseMere{ public ClasseMere(TypeA a, TypeB b){} }; public class ClasseFille extends ClasseMere{ public ClasseFille(TypeA a, TypeB b){ super(a,b); } }; L’appel du constructeur de la classe mère doit impérativement être la première instruction du constructeur de la classe fille ! 7 De manière générale, lorsque l’on souhaite appeler une méthode de la classe mère, il suffit d’écrire super.nomMethodeClasseMere suivi des paramètres de la méthode entre parenthèses : public class ClasseMere { public TypeA methodeMere(TypeB param){...} }; public class ClasseFille extends ClasseMere { public TypeC methodeFille(TypeB param) { // code super.methodeMere(param); } }; Lorsqu’il n’y a aucune ambiguïté sur la méthode appelée, le mot-clé super n’est pas nécessaire. 3.3 Surcharge et redéfinition Dans la section 2.2, nous avions évoqué la possibilité de surcharger une méthode : deux méthodes ayant le même nom, le même type de retour et les mêmes paramètres. Dans une classe fille, il est possible de redéfinir une méthode de la classe mère. La redéfinition d’une méthode d’une classe consiste à écrire, dans une classe héritée, une méthode avec la même signature exactement : même nom, même type de retour et mêmes paramètres. 3.4 Exercice - Point, rectangle et cercle (suite) Nous souhaitons à présent généraliser les concepts de le rectangle et de cercle. Nous introduisons un nouveau concept FigureUsuelle. La classe FigureUsuelle aura deux attributs : le premier sera Point correspondant au barycentre de la figure et le second sera une couleur représentée par un objet de type String. Les objets de la classe FigureUsuelle auront accès aux trois méthodes suivantes : • Un constructeur par défaut avec aucun paramètre ; • Un constructeur avec 2 paramètres pour initialiser les attributs ; • Une méthode toString qui retourne un objet de type String intégrant ces deux attributs. Les classes Rectangle et Cercle hériteront de la classe FigureUsuelle et veilleront à réutiliser au maximum les méthodes définies dans celles-ci. Question 1 Modifiez le code du programme en conséquence : de nombreuses modifications sont à prévoir et attention aux visibilités. 8 Nous souhaitons à présent pouvoir calculer l’aire de chacune des figures usuelles créées. Question 2 Ajoutez aux trois classes précédentes la méthode aire qui retourne l’aire de la figure. On supposera que l’aire d’une FigureUsuelle est toujours égale à 0. Question 3 Que peut-on dire des méthodes aire des classes Rectangle et Cercle ? Question 4 Essayez les instructions ci-dessous ? Pouvez-vous expliquer pourquoi elles fonctionnent ou pas ? Rectangle rec= new Rectangle (...); FigureUsuelle fig= new FigureUsuelle (...); Rectangle rec2 = fig; FigureUsuelle fig2= rec; Question 5 Que retourne l’appel de toString sur rec et fig2 ? Qu’en déduisez vous ? Quel intérêt voyez-vous à ce mécanisme ? Le mécanisme d’héritage permet de définir des concepts de plus en plus abstraits à tel point qu’il peut sembler aberrant de les instancier. De telles classes servent alors essentiellement à factoriser des attributs ou des comportement communs. C’est pourquoi il est possible de définir des classes abstraites. Une classe abstraite est une classe qui ne peut pas être instanciée par un new. En Java, il suffit d’ajouter le mot-clé abstract dans le nom de la classe. Attention, cela ne veut pas dire pour autant qu’une classe abstraite n’a pas de constructeur ! Question 6 Quelle classe mériterait d’être abstraite en l’occurrence ? Une méthode abstraite est une méthode dont il est nécessaire de redéfinir dans chacune des classes filles. En java, il suffit d’ajouter le mot-clé abstract dans l’entête de la méthode et de retirer le code de la méthode de la classe mère : il ne reste alors que la signature de la méthode. Seule une classe abstraite peut comporter des méthodes abstraites. Les méthodes abstraites correspondent généralement à des méthodes pour lesquelles il n’est pas possible ou souhaitable de les définir au niveau de la classe mère. Question 7 Quelle méthode mériterait d’être abstraite en l’occurrence ? Question 8 (bonus) Allouez l’espace pour un tableau de 5 FigureUsuelle. Dans chacune des cases stockez-y un Rectangle ou un Cercle. Calculez la somme des aires de ces figures en parcourant chacune des cases du tableau. Que constatez-vous ? 9