Programmation Java/Version imprimable
Une version à jour et éditable de ce livre est disponible sur Wikilivres,
une bibliothèque de livres pédagogiques, à l'URL :
https://fr.wikibooks.org/wiki/Programmation_Java
Introduction
Introduction au langage Java
[modifier | modifier le wikicode]Le 23 mai 1995, Sun Microsystems présentait une nouvelle plateforme, composée d'un langage de programmation et d'une machine virtuelle. Java™ était né.
L'histoire de Java commence en fait en 1991, lorsque Sun décide de lancer un projet destiné à anticiper l'évolution de l'informatique, confié à James Gosling, Patrick Naughton et Mike Sheridan. Ce projet, appelé « Green Project » (en anglais « projet vert »), eu comme résultat une plateforme baptisée « Oak » (en anglais « chêne »), indépendante du système, orientée objet et légère. Oak était initialement destinée à la télévision interactive. Il a été renommé en Java pour de simples raisons de droit d'auteur.
Lorsqu'il est révélé en 1995, Java profite de l'essor d'Internet en permettant l'un des premiers mécanismes d'interactivité au niveau du poste client : l'appliquette (applet) Java.
Langage orienté objet d'usage généraliste, Java est enrichi par des bibliothèques, des outils et des environnements très diversifiés, standardisés par le Java Community Process (JCP), consortium chargé de l'évolution de Java. Ce consortium regroupe des entreprises, comme Sun, IBM, Oracle, Borland, BEA, des organismes de normalisation, comme le NIST, des organismes du monde Open Source, comme la Fondation Apache et le JBoss Group, et des particuliers.
Il est possible d'utiliser Java pour créer des logiciels dans des environnements très diversifiés :
- applications sur client lourd (JFC) ;
- applications Web, côté serveur (servlets, JSP, Struts, JSF) ;
- applications réparties (EJB) ;
- applications embarquées (J2ME) ;
- applications sur carte à puce (Java Card).
Ces applications peuvent être enrichies par de nombreuses fonctionnalités :
- accès à des bases de données (JDBC et JDO) ;
- accès à des annuaires (JNDI) ;
- traitements XML (JAXP) ;
- connexion à des ERP (JCA) ;
- accès à des traitements en d'autres langages (JNI) ;
- services web (JAX-RPC, JAXM, JAXR) ;
- multimédia (Java Media) ;
- téléphonie (JTAPI) ;
- télévision interactive (Java TV).
Ceci n'est bien sûr qu'un petit échantillon. Il existe bien d'autres bibliothèques.
Historique de Java
[modifier | modifier le wikicode]- 1991 : Début du projet Oak, qui donnera naissance à Java.
- Été 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.
- 23 mai 1995 : Lancement officiel de Java 1.0
- 23 janvier 1996 : Lancement du JDK 1.0.
- 29 mai 1996 : Première conférence JavaOne. Java Media et les servlets y sont notamment annoncés.
- 16 août 1996 : Premières éditions des livres Java Tutorial et Java Language Specification par Sun et Addison-Wesley.
- Septembre 1996 : Lancement du site Web Java Developer Connection Web.
- 16 octobre 1996 : Spécification des JavaBeans.
- 11 janvier 1997 : Lancement des JavaBeans.
- 18 février 1997 : Lancement du JDK 1.1.
- 28 février 1997 : Netscape annonce que Communicator supporte désormais Java.
- 4 mars 1997 : Lancement des servlets.
- 10 mars 1997 : Lancement de JNDI.
- 5 juin 1997 : Lancement de Java Web Server 1.0.
- 5 août 1997 : Lancement de Java Media et de Java Communication API.
- Mars 1998 : Lancement des JFC et notamment de Swing.
- 8 décembre 1998 : Lancement de Java 2 et du JDK 1.2.
- 4 mars 1999 : Support XML.
- 27 mars 1999 : Lancement de la machine virtuelle HotSpot.
- 2 juin 1999 : Lancement des JSP.
- 15 juin 1999 : Formalisation des environnements J2ME, de J2SE et J2EE ; Lancement de Java TV.
- Août 1999 : Lancement de Java Phone.
- 8 mai 2000 : Lancement de J2SE 1.3.
- 9 mai 2002 : Lancement de J2SE 1.4.
- 24 novembre 2003 : Lancement de J2EE 1.4.
- 30 septembre 2004 : Lancement de J2SE 1.5, nommé également « J2SE 5.0 » ou « Tiger ».
- 11 décembre 2006 : Lancement de JavaSE 6, nommé également « Mustang ».
- 13 novembre 2006 : Passage de Java, c’est-à-dire le JDK (JRE et outils de développement) et les environnements Java EE (déjà sous licence CDDL) et Java ME sous licence GPL. Java devient donc un logiciel libre.
- 27 janvier 2010 : Sun Microsystem est racheté par Oracle. Désormais, Java est maintenu par la société Oracle.
- 28 Juillet 2011 : Lancement de JavaSE 7, nommé également « Dolphin ».
- 18 mars 2014 : Lancement de JavaSE 8, nommé également « Kenaï ».
Présentation du langage
[modifier | modifier le wikicode]Java est un langage typé et orienté objet. Il est compilé et basé sur une architecture logicielle très particulière nécessitant une machine virtuelle Java. Il utilise les notions usuelles de la programmation orientée objet : la notion de classe, d'encapsulation, d'héritage, d'interface, de virtualité, de généricité, … Il est accompagné d'un ensemble énorme de bibliothèques standard couvrant de très nombreux domaines, notamment des bibliothèques graphiques. C'est un langage qui présente d'excellentes propriétés de portabilité du code. Son gros point faible est une relative lenteur, surtout si on le compare à des langages comme le C++. Cependant, ce défaut a été résolu en grande partie par l'introduction de la technologie JIT (compilateur Just-In-Time, en anglais « juste à temps »), qui compile le code à la première exécution, permettant une exécution quasiment aussi rapide qu'en C/C++.
Machine virtuelle
Introduction à la machine virtuelle Java
[modifier | modifier le wikicode]Le langage Java utilise une notion extrêmement importante en informatique : la notion de machine virtuelle. Cette machine virtuelle est composée de trois parties, et est souvent fournie avec l'OS, ou facilement installable comme un logiciel normal.
Sur les ordinateurs, la même machine virtuelle est capable de lancer :
- des applications indépendantes (standalone en anglais), lancées et fonctionnant comme toute application installée sur la machine ;
- des applets, à partir d'une page HTML (internet, réseau local ou en local sur la machine). Pour cela, il faut que le navigateur possède une extension permettant d'utiliser une machine virtuelle Java pour l'exécution de ces applets.
Environnement d'exécution
[modifier | modifier le wikicode]Le langage Java est un langage orienté objet qui doit être compilé. Cependant, le compilateur Java ne produit pas directement un fichier exécutable, mais du code intermédiaire sous la forme d'un ou plusieurs fichiers dont l'extension est .class
; ce code intermédiaire est appelé bytecode. Pour exécuter le programme, il faut utiliser la machine virtuelle Java qui va interpréter le code intermédiaire en vue de l'exécution du programme.
Il ne s'agit pas d'une compilation normale, car le compilateur ne produit pas du code compréhensible directement par le microprocesseur, ni d'un langage interprété, car il y a tout de même la notion de compilation, mais une situation intermédiaire entre un langage interprété et un langage complètement compilé.
En d'autres termes, un programme Java, une fois compilé en code intermédiaire, n'est compréhensible que par la machine virtuelle, qui va traduire à la volée (interprétation) les instructions exécutées en code compréhensible par la machine physique.
Depuis Java SE 1.3, les machines virtuelles d'Oracle contiennent un interpréteur capable d'optimiser le code appelé HotSpot.
L'interpréteur effectue plusieurs tâches :
- vérification du code intermédiaire ;
- traduction en code natif (spécifique à la plateforme, au système d'exploitation) ;
- optimisation.
Outre un interpréteur, l'environnement d'exécution fournit également :
- un noyau multitâches pour les machines virtuelles fonctionnant sur des systèmes monotâches (green virtual machines) ;
- un « bac à sable » (sandbox) pour l'exécution de processus distants.
Chargeur de classe
[modifier | modifier le wikicode]Le chargeur de classe charge les classes nécessaires à l'exécution, alloue l'espace mémoire nécessaire et établit les liens entre elles (linkage). Le chargeur de classe connait la structure d'un fichier .class
.
Gestionnaire de mémoire
[modifier | modifier le wikicode]Le gestionnaire mémoire assure les services liés à la mémoire, en particulier :
- un ramasse-miette (garbage collector) ;
- une protection mémoire même sur les machines dépourvues d'unité de gestion mémoire (MMU).
Avantage de l'utilisation de la machine virtuelle
[modifier | modifier le wikicode]L'utilisation d'une machine virtuelle a l'énorme avantage de garantir une vraie portabilité. Il existe des machines virtuelles Java pour de très nombreux environnements : Windows, MacOS, Linux et autres.
Ces machines virtuelles sont capables d'exécuter exactement le même code intermédiaire (les mêmes fichiers Java en bytecode) avec une totale compatibilité. C'est là une situation unique et assez remarquable qui a fait le succès de ce langage.
La machine virtuelle Java n'est pas uniquement développée sur des ordinateurs classiques, une multitude d'appareils disposent d'une machine virtuelle Java : téléphones portables, assistants personnels (PDA)…
Les interfaces de programmation (API) Java : des fonctionnalités énormes
[modifier | modifier le wikicode]La machine virtuelle Java possède un ensemble de bibliothèques extrêmement complètes : des bibliothèques graphiques, des bibliothèques systèmes, etc.
Toutes ces bibliothèques sont totalement portables d'un environnement à un autre.
La machine virtuelle Java offre donc un véritable système d'exploitation virtuel, qui fonctionne au-dessus du système d'exploitation, de la machine cible et le masque totalement aux applications.
La lenteur de Java
[modifier | modifier le wikicode]Le gros point faible du concept de machine virtuelle est que le code intermédiaire (bytecode) est interprété par la machine virtuelle. Ceci entraîne une baisse importante des performances des programmes.
Toutefois, avec les machines virtuelles actuelles, cet argument n'a plus autant de poids. La technique appelée « compilation juste à temps » (JIT : Just-In-Time) est employée par la machine virtuelle quand une méthode est appelée. Cette technique consiste à compiler à la volée la méthode appelée (la première fois) en code natif directement exécutable par le processeur.
Toute méthode s'exécute ainsi aussi rapidement que du code natif.
Autres langages
[modifier | modifier le wikicode]D'autres langages de programmation permettent de produire du bytecode Java exécutable avec la JVM :
- Jython compile du code source Python en bytecode Java ;
- Scala (de l'anglais Scalable language, « langage adaptable ») est un langage de programmation multi-paradigme exprimant les modèles de programmation courants dans une forme concise et élégante, compilant le code source en bytecode Java ;
- ...
Plus de détails : Développer en Java/Langages dérivés
Processeur Java
[modifier | modifier le wikicode]Un processeur Java est l'implémentation matérielle d'une machine virtuelle Java. C'est à dire que les bytecodes constituent son jeu d'instruction.
Actuellement, il y a quelques processeurs Java disponibles :
- en:picoJava, première tentative de construction d'un processeur par Sun Microsystems ;
- aJ100 de aJile. Disponibles sur cartes Systronix ;
- Cjip (Imsys Technologies) ;
- Komodo : micro-controlleur Java multi-thread pour la recherche sur la planification temps-réel ;
- FemtoJava : projet de recherche ;
- ARM926EJ-S : processeur ARM pouvant exécuter du bytecode Java.
Installation
Installation de Java
[modifier | modifier le wikicode]Pour compiler les programmes Java, il existe un compilateur gratuit très répandu nommé Javac, disponible auprès d'Oracle. Cependant, il faut savoir qu'il existe aussi de nombreux environnements de développement Java permettant de taper, compiler, exécuter ou débuguer des programmes dans ce langage.
Le plus rapide :
- Pour Windows, télécharger le JDK sur https://www.java.com/fr/ ;
- Pour Linux, télécharger OpenJDK selon http://openjdk.java.net/install/.
Par ailleurs, il est possible de tester des morceaux de code sans installation sur des sites comme https://www.jdoodle.com/online-java-compiler/.
NetBeans
Installation de NetBeans
[modifier | modifier le wikicode]NetBeans est un environnement de développement intégré (EDI), générique et extensible. Il permet nativement de développer en Java, C, C++, JavaScript, XML, Groovy, PHP et HTML.
De plus, il possède son propre wiki officiel[1] (en anglais). La présente page est donc destinée à le synthétiser au maximum.
Pour démarrer l'installation, le télécharger la version LTS sur https://netbeans.org/downloads/, ou bien la toute dernière sur http://bits.netbeans.org/dev/nightly/latest/.
Configuration
[modifier | modifier le wikicode]- Pour installer les plugins comme PHP ou Python : https://netbeans.org/downloads/index.html.
- Par défaut le chemin d'accès d'un fichier ouvert apparait en survol de son nom. Pour l'afficher en permanence sous la barre de menu : Tools, Options, Appearance, Show full file path.
- L'autoformatage est réglable dans Tools, Options, Editor, Formatting.
- Pour déplacer la barre verticale rouge qui se trouve par défaut à 80 caractères de la gauche : Tools, Options, Editor, Formatting, Right Margin. Par exemple sur GitHub, la taille des écrans est de 120 caractères, il vaut donc mieux ne pas dépasser cette largeur pour éviter lors des relectures de codes, de descendre chercher un des ascenseurs pour remonter lire une fin de ligne.
- Pour bénéficier des avantages propres à un framework (ex : autocompletion des objets du projet ou ouverture d'une déclaration de méthode avec "CTRL + clic" sur un appel), il convient, après installation dans "Tools\Plugins" de spécifier manuellement son emplacement dans "Tools\Options", puis pour PHP par exemple "Framework & Tools".
Utilisation
[modifier | modifier le wikicode]NetBeans permet de comparer deux fichiers : Tools, Diff...
Gestion de versions
[modifier | modifier le wikicode]Les gestions de version sont regroupées dans le menu Team. NetBeans peut donc tout à fait remplacer des clients Git purs tels que GitHub Desktop ou Smartgit.
Pour éviter de commiter fichier par fichier, ajouter le bouton Commit All - Repository en le faisant glisser depuis View, Toolbars, Customize.
Principaux raccourcis claviers
[modifier | modifier le wikicode]Liste exhaustive en anglais : http://wiki.netbeans.org/KeymapProfileFor60.
- Alt + Maj + o : ouvre le fichier recherché.
- Ctrl + o : ouvre le fichier déclarant l’objet sélectionné (recherche de classe).
- Ctrl + Maj + f : cherche un texte dans les fichiers le dépôt.
- Ctrl + b : cherche la déclaration d'un élément (dans le même fichier ou pas).
- Ctrl + espace : propose une liste de complétion (mots-réservés et squelettes de fonctions).
- Ctrl + Maj + espace : affiche la documentation d'une méthode (Javadoc, PHPDoc...).
- Ctrl + p : affiche uniquement les arguments possibles d'une méthode.
- Ctrl + Maj + c : commente les lignes sélectionnées.
Plugins
[modifier | modifier le wikicode]Certains plugins peuvent se révéler utiles :
- http://plugins.netbeans.org/plugin/45925/sort-line-tools : trie les lignes dans l'ordre alphabétique.
- http://plugins.netbeans.org/plugin/37077/autosave-module-for-6-9-and-later : sauvegarde automatiquement chaque fichier modifié en temps réel.
Références
[modifier | modifier le wikicode]
Eclipse
Eclipse est un environnement de développement intégré (EDI), générique et extensible (site officiel http://www.eclipse.org). Son système de plugins permet d'ajouter des fonctionnalités diverses.
Initialement prévu pour développer en Java, grâce aux plugins il peut maintenant également gérer des projets développés avec d'autres langages de programmation tels que :
- Le C et le C++ grâce à l'ensemble de plugins CDT (C Development Toolkit)[1] (compilateur non intégré).
- Le Python via PyDev[2].
- Avant l'arrivée d'Android Studio, le développement pour Android se faisait avec Eclipse grâce à l'ensemble de plugins ADT (Android Development Toolkit).
Certains IDE sont basés sur Eclipse, et permettent par exemple le développement de logiciel embarqués pour des systèmes temps réel.
Installation de Eclipse
La page de téléchargement d'Eclipse permet de récupérer une version déjà adaptée au langage ciblé sur http://www.eclipse.org/downloads/. Mais pour installer un plugin manuellement, il faut :
- Lancer Eclipse, puis dans le menu déroulant :
Help>Software Updates>Find and Install...
- Cocher Search for new features to install, bouton Next. Bouton New Remote Site..., entrer l'adresse de téléchargement :
Name: Nom du plugin URL: adresse du plugin, ex : http://www.eclipse.org/cdt/downloads.php
- Bouton Finish, choisir un miroir proche puis continuer l'installation.
Utilisation de Eclipse
L'interface de l'IDE Eclipse est basée sur différentes perspectives. Une seule perspective n'est visible à la fois, et se compose de plusieurs vues. Exemples :
- La perspective "Java" se compose par défaut de la vue "Package Explorer", de la vue éditeur de code en Java avec un onglet par fichier ouvert, de la vue "Outline" donnant la hiérarchie des éléments composant la classe du fichier ouvert.
- La perspective "Debug" est ouverte automatiquement au lancement d'une application en mode débogage et se compose par défaut de la vue "Debug" affichant la pile d'appel, de la vue des points d'arrêt nommée "Breakpoints", de la vue éditeur de code en Java avec un onglet par fichier ouvert, de la vue "Outline" donnant la hiérarchie des éléments composant la classe du fichier ouvert.
- Deux ou plusieurs perspectives peuvent être affichées conjointement.
Chaque vue est une sous-fenêtre qui a un titre et se place dans un cadre particulier de la fenêtre de l'IDE. Les vues peuvent être déplacées à la souris par drag and drop pour changer la disposition de la perspective. Plusieurs vues peuvent partager le même cadre, auquel cas, une barre d'onglets permet de basculer entre les vues. Un double clic sur le titre d'une vue provoque l'affichage du cadre qui la contient en pleine fenêtre, réduisant les autres cadres à une icône sur les côtés. Un second double clic restaure les cadres.
Le menu "Window" permet de changer de perspective, et d'ajouter des vues à la perspective courante. Une vue peut également être retirée de la perspective affichée en utilisant la croix à droite du titre de la vue.
Édition de lignes
L'éditeur de code possède des raccourcis clavier pratiques rendant l'édition des lignes de code plus rapide :
Touches | Effet | |
---|---|---|
Shift | ↵ Enter | Ajouter une nouvelle ligne après la ligne courante. |
Ctrl | ↑/↓ | Faire défiler la vue vers le haut/le bas. |
CtrlShift | ↑/↓ | Déplacer le curseur sur le membre précédent/suivant de la classe. |
Alt | ↑/↓ | Déplacer la ligne courante ou les lignes sélectionnées vers le haut/le bas dans le texte. |
CtrlAlt | ↑/↓ | Dupliquer la ligne courante ou les lignes sélectionnées vers le haut/le bas. |
CtrlShift | : | Commenter/Décommenter la ligne courante. |
Complétion de code
L'éditeur de code peut compléter automatiquement le code là où se trouve le curseur :
Touches | Effet | |
---|---|---|
Ctrl | Espace | Ouvrir la liste des suggestions de complétion.
Une fois la suggestion choisie, la validation se fait par l'une des touches suivantes :
Toute autre touche produit le caractère sans valider (annuler la complétion). |
AltShift | : | Complète avec la seule possibilité, ou produit un bip s'il y a plusieurs possibilités. |
Navigation et autres astuces
- Dans les codes sources dépassant la hauteur de fenêtre de l'éditeur, placez le pointeur de la souris sur l'accolade fermante d'un bloc pour voir apparaître un résumé du code d'ouverture du bloc en bulle d'aide. Ceci est fort utile pour vérifier quel bloc est fermé par l'accolade sans avoir à faire défiler le code.
- Placez le pointeur de la souris sur un identifiant de classe, méthode ou variable et enfoncez la touche Ctrl pour faire apparaître un lien cliquable vers la définition.
- Cliquez sur un identifiant de membre de classe pour faire apparaître toutes les occurrences d'utilisation dans le fichier : à la fois dans le texte et dans la barre de défilement. Les blocs apparaissant dans la barre de défilement sont cliquables pour faire défiler le code à la position de l’occurrence. Il y a deux couleurs distinctes pour les occurrences utilisées en lecture (accès, appel, ...) et celles utilisées en écriture (affectation).
- Il peut arriver que cela ne semble pas fonctionner car l'éditeur peut être dans un mode autre que celui par défaut. Il faut dans ce cas appuyer une ou deux fois la touche Échap pour que cela fonctionne.
Créer une archive java (JAR)
[modifier | modifier le wikicode]Une archive java (JAR : Java Archive) est un fichier d'extension .jar
regroupant les fichiers java sous la forme d'une archive compressée au format Zip :
- Une archive des fichiers sources (*.java) permet la distribution des sources d'une application,
- Une archive des fichiers binaires (*.class) permet la distribution d'une application et son exécution.
Le JDK fournit la commande jar
pour la création d'archives et l'extraction de fichiers.
Eclipse permet de créer une archive depuis l'interface graphique de l'IDE.
- Pour créer une archive de fichiers :
- Cliquez le menu « File », « Export » ;
- Sélectionnez « Java », « JAR file » ;
- Sélectionnez les fichiers à inclure, le fichier JAR à créer et les options, puis cliquez le bouton « OK ».
- Vous pouvez également accéder à la création de fichiers JAR en sélectionnant « Export » dans le menu du clic droit sur un projet depuis la vue « Package Explorer ». Les fichiers du projet sont alors présélectionnés dans la boîte de dialogue.
- Pour créer une archive d'application exécutable (quand l'application a déjà été lancée depuis Eclipse) :
- Cliquez le menu « File », « Export », ou clic droit sur un projet puis « Export » ;
- Sélectionnez « Java », « Runnable JAR file » ;
- Sélectionnez la configuration de lancement à utiliser ;
- Sélectionnez le fichier JAR à créer et les options ;
- Vous pouvez éventuellement entrez un fichier de script ANT à générer avec les options d'export ;
- Cliquez le bouton « OK ».
Vous pouvez également lancer un script ANT généré précédemment ou créé manuellement pour générer l'archive.
Références
[modifier | modifier le wikicode]- ↑ https://eclipse.org/cdt/
- ↑ (anglais) http://pydev.org/
Visual Studio Code
Visual Studio Code (ou simplement nommé "code") est un logiciel éditeur de texte graphique, gratuit fonctionnant sous Windows. Il est capable de prendre en charge le débogage, d'effectuer la coloration syntaxique, l'auto-complétion et surtout le pliage de code (code folding), c'est à dire le masquage à volonté de différents blocs d'instructions (contenu d'une classe, d'une fonction, d'une boucle, etc.) : cette fonctionnalité se révèle extrêmement pratique lorsque vos scripts commencent à s'allonger... Il intègre également la gestion de version (notamment Git et SVN)[1], une fenêtre de terminal ainsi qu'un raccourci pour lancement des scripts.
Cet éditeur est disponible pour Windows sur https://code.visualstudio.com .
Langages supportés
Visual Studio Code prend immédiatement en charge presque tous les principaux langages informatiques.
Plusieurs d'entre eux sont inclus par défaut, comme HTML et CSS, et aussi JavaScript, TypeScript, Java, PHP, mais d'autres extensions de langage peuvent être trouvées et téléchargées gratuitement à partir de VS Code Marketplace[2].
Utilisation
Édition de lignes
L'éditeur de code possède des raccourcis clavier pratiques rendant l'édition des lignes de code plus rapide :
Touches | Effet | |
---|---|---|
Ctrl | ↵ Enter | Ajouter une nouvelle ligne après la ligne courante. |
Ctrl | ↑/↓ | Faire défiler la vue vers le haut/le bas. |
Alt | ↑/↓ | Déplacer la ligne courante ou les lignes sélectionnées vers le haut/le bas dans le texte. |
AltShift | ↑/↓ | Dupliquer la ligne courante ou les lignes sélectionnées vers le haut/le bas. |
Ctrl | : | Commenter/Décommenter la ligne courante. |
Références
Bases du langage
Un programme Java est une suite d'instructions exécutées de manière séquentielle. D'une manière générale, les instructions sont terminées par des points-virgules « ;
».
Ces instructions s'exécutent de gauche à droite et de haut en bas. Elles peuvent être organisées dans des blocs.
Identificateurs
[modifier | modifier le wikicode]Les différentes parties d'une classe possède un nom, qui doit être unique dans le contexte local afin d'identifier cet élément :
- nom du package où se situe la classe (implique une structure de répertoires ayant les mêmes noms),
- nom de la classe et des classes internes,
- nom des méthodes et attributs membres de la classes,
- nom des variables locales aux méthodes.
Tous ces noms sont appelés identificateurs et ont des contraintes :
- Un identificateur ne peut être composé que de lettres, de chiffres et du caractère souligné. Les caractères unicode sont acceptés.
- Un identificateur ne peut commencer par un chiffre, pour éviter l'ambiguïté avec les nombres.
- Depuis Java 9, le caractère souligné seul n'est plus accepté car il est devenu un mot clé. En Java 8, cela provoque un avertissement à la compilation.
- Les identificateurs des packages étant identiques aux noms des répertoires, ils doivent également obéir aux règles des noms de répertoire acceptables sur tous les systèmes où le programme tourne. Notamment sur Windows, certains noms sont réservés : aux, nul, com1 ...
Identificateurs valides | Identificateurs invalides |
---|---|
somme_en_€
somme_en_$
somme_en_£
dépenses_2022
|
7jours // <!> commence par un chiffre
somme totale // <!> contient un espace
revenus+dépenses // <!> contient un caractère réservé (+)
|
Point d'entrée du programme
[modifier | modifier le wikicode]Tout programme Java contient au moins une classe[1], nommée par convention avec une majuscule contrairement aux méthodes. De plus, il nécessite un point d'entrée. Pour un programme simple, le point d'entrée est la méthode "main" qui doit être publique, statique et située dans une classe qui elle même doit être publique (d'où les mots-clés public et static) :
public class ClassTest
{
public static void main(String args[])
{
// Instructions du programme
}
}
L'argument args de cette méthode contient les paramètres passés au programme par la ligne de commande.
D'autres types de programmes, comme les applets, les servlets ou encore les applications Android, utilisent d'autres méthodes comme point d'entrée.
Blocs d'instructions
[modifier | modifier le wikicode]Un bloc d'instructions est une suite d'instructions commençant par une accolade ouvrante ( {
) et se terminant par une accolade fermante ( }
).
Un bloc d'instructions est considéré comme une seule instruction par les instructions for, while, if, else et case. Nous détaillerons cela plus loin.
Ce bloc délimite également la portée des variables. Toute variable déclarée dans ce bloc n'est accessible qu'à partir de ce bloc, et n'existe que durant l'exécution du bloc.
Commentaires
[modifier | modifier le wikicode]- Pour plus de détails voir : Programmation Java/Commentaires.
Les commentaires sont, en programmation informatique, des portions du code source ignorées par le compilateur. Ils sont très pratiques pour préciser quelque chose ou pour "mettre de côté" du code sans le supprimer. Java permet d'insérer des commentaires en utilisant deux syntaxes différentes :
- La séquence
//
permet d'insérer un commentaire sur une seule ligne, qui se termine donc à la fin de la ligne.
- La séquence
/*
permet d'insérer un commentaire sur plusieurs lignes. La séquence*/
marque la fin du commentaire. En d'autres termes, tout ce qui est situé entre/*
et*/
est considéré comme faisant partie du commentaire.
Exemple :
// Commentaire sur une ligne
public class /* un commentaire au milieu de la ligne */ Exemple
{
/*
Commentaire sur
plusieurs lignes
...
*/
public static void main(String[] args)
{
}
}
Commentaire Javadoc
[modifier | modifier le wikicode]Les commentaires normaux sont totalement ignorés par le compilateur Java.
En revanche, certains commentaires sont interprétés par le générateur automatique de documentation Javadoc.
Ces commentaires commencent par la séquence /**
et se terminent par */
. Le contenu décrit l'entité qui suit (classe, interface, méthode ou attribut), suivi d'une série d'attributs dont le nom commence par un arobase @
.
La documentation générée étant au format HTML, il est possible d'insérer des balises dans la description.
Exemple :
/**
Une classe pour illustrer les commentaires Javadoc.
@author Moi :-)
*/
public class Exemple
{
/**
Une méthode <b>main</b> qui ne fait rien.
@param args Les arguments de la ligne de commande.
*/
public static void main(String[] args)
{
}
}
En fait, il existe un attribut Javadoc qui est pris en compte par le compilateur : l'attribut @deprecated
. Cet attribut marque une classe, une méthode ou une variable comme obsolète. Ce qui signifie que si une autre classe l'utilise un avertissement est affiché à la compilation de cette classe.
Exemple :
/**
Une méthode obsolète. Il faut utiliser get() à la place.
@deprecated
*/
public Object getElement(int index)
{ ... }
/**
Une nouvelle méthode.
*/
public Object get(int index)
{ ... }
Cet attribut permet de modifier une bibliothèque de classe utilisée par plusieurs applications, en la laissant compatible avec les applications utilisant l'ancienne version, mais en indiquant que les anciennes classes / méthodes / variables ne doivent plus être utilisées et pourraient ne plus apparaître dans une version ultérieure.
Références
[modifier | modifier le wikicode]- ↑ Kathy Sierra, Bert Bates, Java : tête la première, O'Reilly Media, Inc., (lire en ligne)
Premier programme
Premier programme
[modifier | modifier le wikicode]Le fichier source
[modifier | modifier le wikicode]Ce programme doit être écrit dans le fichier Exemple.java.
public class Exemple
{
public static void main(String[] args)
{
System.out.println("Hello world!");
}
}
Explications sur le langage
[modifier | modifier le wikicode]Ce programme est le classique Hello world. Comme son nom l'indique, ce programme va afficher la phrase "Hello world" à l'écran. Analysons-le ligne par ligne :
public class Exemple
{
Cette ligne déclare une classe publique que nous appelons Exemple.
Un fichier .java
ne peut contenir qu'une seule classe publique et le fichier doit porter le nom de cette classe.
Ainsi, le fichier de ce premier exemple s'appellera obligatoirement Exemple.java
(en respectant la casse).
Ce système de nommage permet au compilateur et à l'interpréteur de trouver les fichiers correspondant à une classe.
public static void main(String[] args)
{
Cette ligne déclare une méthode appelée main
. Cette méthode est le point d'entrée du programme (la méthode appelée lorsqu'il sera exécuté).
Elle prend en argument un tableau de chaînes de caractères (String[] args
) et ne retourne rien (void
).
Cette méthode est publique et statique, ce qui sera expliqué plus loin.
System.out.println("Hello world!");
Cette dernière instruction invoque la méthode println
de l'objet out
se trouvant dans la classe System
en lui passant en argument la chaîne Hello world!
.
L'exécution de cette méthode aura pour résultat l'affichage de Hello world!
.
Cette ligne peut sembler obscure pour l'instant. Les détails seront abordés par la suite.
Compilation du fichier source
[modifier | modifier le wikicode]Tapez le programme précédent et sauvegardez le dans un fichier Exemple.java (pour la raison expliquée précédemment) et tapez dans une fenêtre :
Invite de commande DOS | Terminal Unix |
---|---|
> javac Exemple.java
> dir
Exemple.class
Exemple.java
>
|
$ javac Exemple.java
$ ls
Exemple.class
Exemple.java
$
|
Le compilateur Javac a produit le fichier Exemple.class, contenant le code intermédiaire. Ce fichier n'est normalement pas éditable[1], ce qui peut garantir le copyright.
Voici les points à vérifier selon le type de problème :
- Le système indique que le compilateur est introuvable
-
- Vérifiez que vous avez installé un kit de développement Java (le JDK) et pas simplement un environnement d'exécution (JRE) qui ne comporte pas les outils de compilation.
- Vérifiez que le chemin du répertoire bin contenant le compilateur javac (javac.exe sous Windows) est dans la liste de la variable d'environnement
PATH
.
- Le compilateur se lance mais affiche une erreur de classe non trouvée
-
- Si la classe ne déclare aucun paquetage (package), vérifiez que vous lancez la commande depuis le répertoire où se trouve le fichier source (*.java). Changez de répertoire si nécessaire avant de recommencer.
- Sinon, vous devez lancer la commande depuis le répertoire parent du paquetage racine, en donnant le chemin relatif du fichier source depuis ce répertoire parent.
- Dans les deux cas ci-dessus, en plus de changer de répertoire courant, il peut être nécessaire d'ajouter le chemin de ce répertoire dans le classpath. Cela peut être fait soit dans la ligne de commande avec l'option
-classpath
(ou-cp
), soit dans la variable d'environnementCLASS_PATH
.
- Le compilateur se lance mais affiche une erreur de syntaxe
-
- Vérifiez le contenu du fichier source. Pour compiler les exemples de ce livre, le mieux est de faire un copier-coller complet du code.
- Assurez-vous de compiler le bon fichier source et pas un autre.
Exécution du programme
[modifier | modifier le wikicode]Java est une machine virtuelle java fournie par Oracle et disponible pour de nombreux environnements.
Pour exécuter notre code intermédiaire, il faut taper :
java Exemple
L'exécution du programme affiche dans une fenêtre console la fameuse phrase Hello world!.
Voici les points à vérifier selon le type de problème :
- Le système indique que la commande java est introuvable
-
- Vérifiez que vous avez installé un kit de développement Java (le JDK).
- Vérifiez que le chemin du répertoire bin contenant l'interpréteur java (java.exe sous Windows) est dans la liste de la variable d'environnement
PATH
.
- L'interpréteur se lance mais affiche une erreur de classe non trouvée
-
- Vérifiez que vous avez compilé le fichier source *.java (voir section précédente) sans erreur, c'est-à-dire que vous avez obtenu un fichier compilé *.class.
- Si la classe ne déclare aucun paquetage (package), vérifiez que vous lancez la commande depuis le répertoire où se trouve le fichier compilé (*.class). Changez de répertoire si nécessaire avant de recommencer.
- Sinon, vous devez lancer la commande depuis le répertoire parent du paquetage racine, en donnant le nom complet de la classe (incluant le nom du paquetage).
- Dans les deux cas ci-dessus, en plus de changer de répertoire courant, il peut être nécessaire d'ajouter le chemin de ce répertoire dans le classpath. Cela peut être fait soit dans la ligne de commande avec l'option
-classpath
(ou-cp
), soit dans la variable d'environnementCLASS_PATH
.
- L'interpréteur se lance mais rien ne s'affiche, ou le comportement n'est pas le même
-
- Vérifiez le contenu du fichier source. Pour compiler les exemples de ce livre, le mieux est de faire un copier-coller complet du code.
- Assurez-vous de lancer la bonne classe principale et pas une autre.
Extraits de code dans ce livre
[modifier | modifier le wikicode]Dans ce livre, les chapitres peuvent être illustrés par du code source ne constituant pas une application complète en général, afin de ne pas encombrer le code avec les déclarations de classes et de méthode principale. Ces extraits de code incluent en général des importations de paquetages, des déclarations de variables et des instructions. Toutes ces déclarations peuvent être incluses dans le modèle suivant afin de constituer une application pour tester les extraits de code :
package org.wikibooks.org;
//... import de paquetages ici
// exemple :
//import java.util.*;
public class Exemple
{
//... variables membres et statiques ici
// exemple :
//private static String groupe = "Tous";
public static void main(String[] args)
{
//... variables locales ici
// exemple :
//String salut = "Bonjour";
//... instructions ici
// exemple :
//System.out.println(salut + " à " + groupe);
}
}
Voir aussi
[modifier | modifier le wikicode]Références
[modifier | modifier le wikicode]
Commentaires
Principe
[modifier | modifier le wikicode]Un commentaire permet d'insérer du texte qui ne sera pas compilé ni interprété. Il sert à ajouter du texte au code source.
Il est utile pour expliquer ce que fait le code source :
- expliquer comment utiliser la classe / la méthode correctement, à quel moment, avec quels types de paramètres, etc. ;
- expliquer le choix technique effectué : pourquoi tel algorithme et pas un autre, pourquoi appeler telle méthode, pourquoi telle valeur pour la constante, etc. ;
- expliquer ce qui devra être fait par la suite (liste de chose à faire) : amélioration, problème à corriger, etc. ;
- donner les explications nécessaires à la compréhension du code pour le reprendre soi-même plus tard, ou pour d'autres développeurs.
Il peut aussi être utilisé pour que le compilateur ignore une partie du code : code temporaire de débogage, code en cours de développement, etc.
Par ailleurs, il existe aussi deux types de commentaires interprétables qui seront détaillées dans deux chapitres ultérieurs :
- les tags Javadoc, utilisés pour la documentation des classes.
- les annotations, pour donner des instructions relatives au programme.
Syntaxe
[modifier | modifier le wikicode]Les commentaires en Java utilisent la même syntaxe qu'en C++ :
- La séquence
//
permet d'insérer un commentaire sur une seule ligne, qui se termine donc à la fin de la ligne. - La séquence
/*
permet d'insérer un commentaire sur plusieurs lignes. La séquence*/
marque la fin du commentaire. En d'autres termes, tout ce qui est situé entre/*
et*/
est considéré comme faisant partie du commentaire.
Exemples
[modifier | modifier le wikicode]// Un commentaire pour donner l'exemple
int n = 10; // 10 articles
/*
Ceci est un commentaire
sur plusieurs lignes.
*/
/*
Code de débogage désactivé :
int a=10;
while (a-->0) System.out.println("DEBUG: tab["+a+"]="+tab[a]);
*/
Types de base
Les types de base ou types primitifs de Java sont listés dans le tableau plus bas.
Remarques :
- Une constante numérique de type entier est considérée comme étant de type
int
. Il faut ajouter le suffixe L pour avoir une constante de typelong
. - La base par défaut des constantes entières est décimale (base 10), et peut être modifiée avec le préfixe
0x
pour la base hexadécimale (base 16 ; ex :0x1F0C
) ou avec le préfixe0
pour la base octale (base 8 ; ex :0777
). - Une constante numérique à virgule est considérée comme étant de type
double
. Il faut ajouter le suffixe F pour avoir une constante de typefloat
. Il existe également un suffixe D pourdouble
mais son utilisation est optionnelle. String
est le seul type non-primitif (donc absent du tableau) dont les instances possèdent une syntaxe littérale :"caractères..."
Type | Taille | Syntaxe | Description | Intervalle |
---|---|---|---|---|
char
|
2 octets 16 bits |
'caractère'
|
Une unité de code, suffisant à représenter un grand nombre de point de code, et même un caractère Unicode (UTF-16)'b' '\u250C'
|
'\u0000' à '\uFFFF'
|
byte
|
1 octet 8 bits |
Un nombre entier de 8 bits (soit un octet) signé | -128 à 127
| |
short
|
2 octets 16 bits |
Un nombre entier de 16 bits signé entre −32 768 et +32 767 | -32768 à 32767
| |
int
|
4 octets 32 bits |
[+|-]chiffres...
|
Un nombre entier de 32 bits signé entre −2 147 483 648 et +2 147 483 647 | -2147483648 à 2147483647
|
long
|
8 octets 64 bits |
[+|-]chiffres...L
|
Un nombre entier de 64 bits signé entre −9 223 372 036 854 776 000 et +9 223 372 036 854 776 000 | -9223372036854775808L à 9223372036854775807L
|
float
|
4 octets 32 bits |
[+|-][chiffres].[chiffres][E[+|-]chiffres]F
|
Un nombre à virgule flottante de 32 bits signé (simple précision) |
|
double
|
8 octets 64 bits |
[+|-][chiffres].[chiffres][E[+|-]chiffres][D]
|
Un nombre à virgule flottante de 64 bits signé (double précision) |
|
boolean
|
1 octet | false|true
|
Une valeur logique | false (faux) ou true (vrai)
|
Nombres
[modifier | modifier le wikicode]Comme le montre le tableau précédent, Java possède différents types primitifs pour représenter les nombres. Ces types sont tous signés :
- les nombres entiers sur 8 bits (
byte
), 16 bits (short
), 32 bits (int
) et 64 bits (long
) ; - les nombres à virgule flottante sur 32 bits (
float
) et 64 bits (double
) ;
excepté le type char
utilisé pour représenté un point de code Unicode sur 16 bits qui peut aussi être utilisé comme type numérique non signé.
Pour aller au-delà de 64 bits, Java peut gérer des nombres de taille arbitraire, occupant plus de mémoire, via des types non primitifs.
Depuis Java 7, le caractère souligné peut être utilisé pour séparer les chiffres (et uniquement les chiffres) afin de les regrouper.
Exemple :
long isbn = 978_2_416_00018_8L; // Suffixe L pour un nombre de type long double pi = 3.14__159__26;
Il ne peut pas être placé avant le nombre, ou entre un chiffre et le point décimal ou le suffixe :
// Erreurs de compilation : long isbn = _978_2_416_00018_8_L; double pi = _3._14__159__26_;
Booléens
[modifier | modifier le wikicode]Comme en Pascal, le langage Java a un type dédié aux résultats de l'évaluation de conditions ou expressions booléennes : le type boolean
.
Les deux valeurs possibles sont true
(vrai) et false
(faux).
boolean en_dehors_de_l_intervalle = (valeur < min) || (valeur > max);
if (en_dehors_de_l_intervalle) // utilisation dans une condition if
{
System.out.println("La valeur et en dehors de l'intervalle de validité.");
}
Ce type est particulièrement utile pour réutiliser le résultat d'une condition plusieurs fois, ou initialiser un état selon une condition.
Les opérateurs sur les booléens sont les suivants :
- ou
||
- Le résultat est vrai si l'un des deux opérandes est vrai, faux sinon.
- et
&&
- Le résultat est vrai si les deux opérandes sont vrai, faux sinon.
- non
!
- Le résultat est vrai si l'opérande est faux, et vice-versa.
Les deux opérandes binaires du et et du ou sont évalués de manière paresseuse (lazy evaluation) : le deuxième opérande n'est pas évalué si la valeur du premier suffit à déterminer le résultat final :
- Pour le ou, si le premier opérande est
true
, le résultat esttrue
. - Pour le et, si le premier opérande est
false
, le résultat estfalse
.
Cela a de l'importance pour l'évaluation d'expression du deuxième opérande, notamment l'appel à une méthode ou l'accès à un membre d'un objet.
Exemple :
boolean non_vide = texte != null && texte.length() > 0;
// Si texte == null, la non évaluation du deuxième opérande empêche un déréférencement
// de la référence null et évite donc une exception de type NullPointerException.
Les valeurs booléennes peuvent aussi être utilisés avec les opérateurs sur les bits c'est-à-dire &
et |
qui évaluent systématiquement les deux opérandes.
boolean non_vide = texte != null & texte.length() > 0; // <!> Erreur d'opérateur
// Si texte == null, le deuxième opérande est tout de même évalué et un déréférencement
// de la référence null a lieu, provoquant une exception de type NullPointerException !
Pour effectuer un ou exclusif sur les booléens, il est possible d'utiliser l'opérateur de comparaison différent !=
ou l'opérateur ou exclusif sur les bits ^
qui dans les deux cas évaluent les deux opérandes.
La comparaison avec les valeurs true
et false
est inutile et peut apporter de la confusion.
if (non_vide == true) { ... }
if (non_vide == false == false) { ... }
Caractères pour char
et String
[modifier | modifier le wikicode]Les caractères spéciaux suivants peuvent être utilisés pour les valeurs littérales des types char
et String
:
\r
(ou\u000D
)- Retour chariot (carriage Return),
\n
(ou\u000A
)- Nouvelle ligne (New line),
\f
(ou\u000C
)- Nouvelle page (Feed page),
\b
(ou\u000C
)- Retour arrière (Backspace),
\t
(ou\u0009
)- Tabulation (Tab),
\"
- Guillemet (pour qu'il soit inclus au lieu de marquer la fin de la chaîne de caractères),
\'
- Apostrophe (pour qu'il soit inclus au lieu de marquer la fin du caractère),
\OOO
- Caractère 8 bits dont le code OOO est spécifié en octal (jusqu'à 3 chiffres),
\uXXXX
- Caractère 16 bits (UTF-16) dont le code XXXX est spécifié en hexadécimal sur 4 chiffres exactement.
- Cette séquence d'échappement peut également être utilisée en dehors des chaînes et caractères dans le code source :
\u0070ublic \u0063lass UneClasse ... /* <- public class UneClasse ... */
- Cette fonctionnalité du langage est cependant à réserver au cas d'utilisation de code Java externe où les identificateurs de classe, attributs ou méthodes utilisent des caractères non supportés par le clavier du développeur (par exemple, des caractères Japonais).
String exemple = "Ce texte est la première ligne,\n\tsuivie d'une seconde ligne indentée par une tabulation.";
Variables et classes
[modifier | modifier le wikicode]Les classes de base comme String ne sont pas des types primitifs. Il est aisé de les confondre mais les conventions habituelles d'écriture permettent de distinguer les deux types de données. Les types de variables primitifs sont toujours écrits en minuscules, par contre les noms des classes ont par convention leur premier caractère en majuscule. Aussi lorsque vous rencontrez un Boolean, ce n'est pas un type de base mais bien une classe. En effet les valeurs de type primitif peuvent être encapsulées, et Java fournit d'ailleurs pour tous les types primitifs des classes d'encapsulation appelées wrappers.
Ceci peut être utile dans certains cas pour bénéficier de certaines caractéristiques de leur classe mère Object. Par exemple, la pose d'un verrou de synchronisation (instruction synchronized) ne peut se faire que sur un objet.
Les emballages de types primitifs
[modifier | modifier le wikicode]Une classe d'emballage (wrapper en anglais) permet d'englober une valeur d'un type primitif dans un objet. La valeur est stockée sous la forme d'un attribut. Chaque classe permet de manipuler la valeur à travers des méthodes (conversions, ...).
Character
pourchar
Byte
pourbyte
Short
pourshort
Long
pourlong
Integer
pourint
Float
pourfloat
Double
pourdouble
Boolean
pourboolean
Auto-boxing
[modifier | modifier le wikicode]L'auto-boxing permet la conversion d'une valeur de type primitif en objet de la classe englobante correspondante de manière implicite, notamment lors d'une affection ou d'un passage d'arguments en paramètres d'une méthode.
Exemple :
public Integer somme(Integer a, Integer b)
{
// <!> a et b sont de type objet et peuvent valoir null.
// Dans ce cas, pour cette méthode, considérer que null vaut 0 :
if (a==null) return b;
if (b==null) return a;
return a + b;
}
//...
System.out.println("Somme 1+2 = "+somme(1,2));
System.out.println("Somme +2 = "+somme(null,2));
Pour plus de détails, voir la section Autoboxing du chapitre Transtypage de ce livre.
Conversions
[modifier | modifier le wikicode]Pour convertir une valeur du type indiqué par la ligne vers celui de la colonne :
Conversion | vers Integer | vers Float | vers Double | vers String | vers Array | vers Boolean |
---|---|---|---|---|---|---|
d'Integer | - | (float)x | (double)x | x.toString() | new int[] {x} | |
de Float | java.text.DecimalFormat("#").format(x) | - | (double)x x.doubleValue() |
x.toString() Float.toString(x) |
new float[] {x} | |
de Double | java.text.DecimalFormat("#").format(x) | java.text.DecimalFormat("#").format(x) | - | x.toString() | new double[] {x} | |
de String | Integer.parseInt(x) | Float.parseFloat(x) | Double.parseDouble(x) | - | new String[] {x} | Boolean.valueOf(x) |
de tableau | x[0] | x[0] | x[0] | Arrays.toString(x) | - | |
de Boolean | - |
Opérateurs
Les expressions sont évaluées en suivant l'ordre de priorité des opérateurs. |
Les opérateurs
[modifier | modifier le wikicode]Le tableau ci-dessous présente la liste des opérateurs utilisables en Java, avec leur signification et leur associativité, dans l'ordre de leur évaluation (du premier au dernier évalué) et le type de données auquel chaque opérateur s'applique. Les opérateurs regroupés entre deux lignes épaisses ont la même priorité.
Opérateur | Description | Type | Associativité |
---|---|---|---|
()
|
Appel de méthode | classes et objets | de gauche à droite |
[]
|
Elément d'un tableau | tableaux | |
.
|
Membre d'une classe ou d'un objet | classes et objets | |
++
|
Incrémentation post ou pré-fixée | byte char short int long float double
|
de droite à gauche |
--
|
Décrémentation post ou pré-fixée | byte char short int long float double
| |
+
|
Positif | byte char short int long float double
| |
-
|
Négation | byte short int long float double
| |
!
|
Non logique | boolean
| |
~
|
Non binaire | byte char short int long
| |
(type)
|
Conversion de type | tous | |
*
|
Multiplication | byte char short int long float double
|
de gauche à droite |
/
|
Division | byte char short int long float double
| |
%
|
Modulo (reste de la division entière) | byte char short int long
| |
+
|
Addition | byte char short int long float double
|
de gauche à droite |
-
|
Soustraction | byte char short int long float double
| |
<<
|
Décalage de bit vers la gauche | byte char short int long
|
de gauche à droite |
>>
|
Décalage de bit vers la droite (signe conservé) |
byte char short int long
| |
>>>
|
Décalage de bit vers la droite (signe décalé) |
byte char short int long
| |
<
|
Inférieur | byte char short int long float double
|
de gauche à droite |
<=
|
Inférieur ou égal | byte char short int long float double
| |
>
|
Supérieur | byte char short int long float double
| |
>=
|
Supérieur ou égal | byte char short int long float double
| |
==
|
Egal | byte char short int long float double objet
|
de gauche à droite |
!=
|
Différent | byte char short int long float double objet
| |
&
|
ET binaire | byte char short int long boolean
|
de gauche à droite |
^
|
OU exclusif binaire | byte char short int long boolean
|
de gauche à droite |
|
|
OU binaire | byte char short int long boolean
|
de gauche à droite |
&&
|
ET logique | boolean
|
de gauche à droite |
||
|
OU logique | boolean
|
de gauche à droite |
?:
|
Opérateur ternaire de condition | boolean ? tous : tous
|
de droite à gauche |
=
|
Affectation | tous | de droite à gauche |
+=
|
Addition et affectation | byte char short int long float double
| |
-=
|
Soustraction et affectation | byte char short int long float double
| |
*=
|
Multiplication et affectation | byte char short int long float double
| |
/=
|
Division et affectation | byte char short int long float double
| |
%=
|
Modulo et affectation | byte char short int long float double
| |
<<=
|
Décaler à gauche et affectation | byte char short int long
| |
>>=
|
Décaler à droite (excepté signe) et affectation | byte char short int long
| |
>>>=
|
Décaler à droite (signe aussi) et affectation | byte char short int long
| |
&=
|
ET binaire et affectation | byte char short int long boolean
| |
^=
|
OU exclusif binaire et affectation | byte char short int long boolean
| |
|=
|
OU binaire et affectation | byte char short int long boolean
| |
,
|
Enchaînement d'expressions | tous | de gauche à droite |
Chaque case de la colonne « associativité » regroupe les opérateurs de même priorité.
Expressions
[modifier | modifier le wikicode]short
et byte
[modifier | modifier le wikicode]Java effectue une conversion en valeur de type int
de manière implicite sur les valeurs de type short
et byte
dès qu'une opération est effectuée, ce qui peut donner des résultats non conforme à ce que l'on pourrait attendre (détails).
Expressions booléennes
[modifier | modifier le wikicode]Les expressions booléennes employant les opérateurs ET logique (&&
) et OU logique (||
) sont évaluées de manière paresseuse.
C'est à dire que l'évaluation s'arrête aussitôt que le résultat est déterminé.
Exemple avec l'opérateur ET logique (vrai si les deux opérandes sont vrais), si le premier opérande est faux le résultat est faux et le deuxième opérande n'est pas évalué. Si le deuxième opérande est le résultat de l'appel à une méthode, cette méthode ne sera pas appelée :
String s = getText();
if ((s!=null) && (s.length()>0))
// si s vaut null, la longueur n'est pas testée car cela provoquerait une exception.
{ /*...*/ }
Parfois ce comportement n'est pas désirable. Dans ces cas-là, il faut utiliser les opérateurs binaires équivalents ET (&
) et OU (|
).
Le remplacement dans l'exemple précédent serait cependant une erreur :
String s = getText();
if ((s!=null) & (s.length()>0))
// si s vaut null, la longueur est tout de même testée et provoque donc une exception !
{ /*...*/ }
Savoir si une racine carrée est entière
[modifier | modifier le wikicode]La fonction calculant les racines carrées pouvant trouver des nombres à virgules, voici une courte technique pour savoir s'il s'agit d'un entier :
public boolean testRacineEntiere(int n)
{
int r = (int)Math.sqrt(n);
return (r*r == n);
}
Conditions
En Java, les séquences d'instructions if...else et switch..case permettent de formuler des traitements conditionnels.
Instructions if...else
[modifier | modifier le wikicode]L'instruction if permet d'exécuter une instruction (ou un bloc d'instructions) si une condition est remplie.
La condition est une expression booléenne dont le résultat est de type boolean
.
if (condition)
// instruction à exécuter
L'instruction else complète l'instruction if. Elle permet d'exécuter une instruction si la condition indiquée par le if n'est pas remplie :
if (condition)
// instruction à exécuter
else
// instruction à exécuter si la condition n'est pas remplie
Exemple :
if (a > 10) System.out.println("A est plus grand que 10.");
else System.out.println("A ne dépasse pas 10.");
Pour plusieurs instructions, il faut les regrouper avec des accolades :
if (condition)
{
// instructions à exécuter
}
else
{
// instructions à exécuter si la condition n'est pas remplie
}
On peut aussi enchaîner les tests conditionnels les uns à la suite des autres :
if (condition1)
{
// bloc1
}
else if (condition2)
{
// bloc2
}
else
{
// bloc3
}
Pour une meilleure compréhension du code, il est recommandé que la condition dans le if
soit positive et simplifiée.
À éviter | Recommandé |
---|---|
public void ouvrirFichier(boolean mode_local)
{
if (!mode_local)
{
// pas en mode local ...
}
else
{
// en mode local ...
}
}
|
public void ouvrirFichier(boolean mode_local)
{
if (mode_local)
{
// en mode local ...
}
else
{
// pas en mode local ...
}
}
|
if (! (mode != OPEN || !can_read) )
{
// Condition difficile à comprendre ...
//
}
|
if (mode == OPEN && can_read)
{
// Condition plus facile à comprendre :
// si mode vaut OPEN et autorisé à lire
}
|
Pareillement, les variables booléennes devraient évaluer une condition positive plutôt que négative.
À éviter | Recommandé |
---|---|
boolean pas_d_ouverture = mode != OPEN || !can_read
if (pas_d_ouverture)
{
// pas d'ouverture possible
}
|
boolean ouverture = mode == OPEN && can_read
if (!ouverture)
{
// pas d'ouverture possible
}
|
Instructions switch...case
[modifier | modifier le wikicode]La séquence d'instructions switch permet d'exécuter différentes instructions (ou blocs d'instructions) en fonction d'une liste de cas. L'instruction default permet de définir un cas par défaut.
switch (expression)
{
case constante1:
// instructions à exécuter
break;
case constante2:
// instructions à exécuter
break;
case constante3:
case constante4:
// instructions à exécuter pour les deux constantes
break;
// ...
default:
// instructions à exécuter
break;
}
L'instruction break sert à sortir du test. Dans le cas contraire, les instructions du case suivant seraient également exécutées, ce qui est utile pour traiter plusieurs cas de la même façon. Pour plus de précision, voir la section suivante (itération).
Remarque : le dernier break est facultatif (qu'il s'agisse de la clause default dans le cas précédent, ou d'une clause case). Cependant ajouter ce dernier break améliore la maintenabilité du code en cas d'ajout d'un nouveau cas (case ou default) ou de déplacement du dernier cas.
L'instruction switch ne fonctionne que sur des types simples : int, short, byte et char, ainsi que sur les wrappers correspondants (Integer, Short, Byte et Character). Depuis JDK 6 on compte aussi le type énumération (enum
), et depuis JDK 7 le String
(mais pas sur OpenJDK 1.7).
Bloc de portée
Dans le code des méthodes, il est possible de créer des blocs de code entre accolades sans aucune construction spécifique avant (if, while, déclaration de méthode, ...). De tels blocs sont des blocs permettant de limiter la portée des variables.
Ce chapitre explique son utilité à travers différentes utilisations possibles.
Réutilisation de noms de variables
[modifier | modifier le wikicode]La limitation de la portée permet de réutiliser les noms de variables.
Exemple :
public double unCalculComplexe(double montant, int duree, double taux)
{
{
double total = coutTotalSurUnAn(montant, taux); // (1)
if (total > 50000) taux = taux * 0.94;
}
// La variable total (1) devient hors de portée.
// On peut réutiliser le nom de variable total :
double total; // (2)
total = coutTotalSur(duree, montant, taux); // (2)
// ...
return total;
}
L'ordre peut avoir de l'importance comme dans l'exemple suivant :
public double unCalculComplexe(double montant, int duree, double taux)
{
double total; // (1)
{
double total = coutTotalSurUnAn(montant, taux); // (2)
// ^ erreur de compilation ici car la variable total (2)
// est en conflit avec total (1) qui est toujours à portée.
if (total > 50000) taux = taux * 0.94;
}
total = coutTotalSur(duree, montant, taux); // (1)
// ...
return total;
}
Restreindre la portée d'une variable
[modifier | modifier le wikicode]Restreindre la portée d'une variable en encadrant le bloc de code sensé être le seul à l'utiliser permet de tester que c'est le cas. Cela permet ensuite, par exemple, de déplacer ce bloc dans une méthode séparée pour le réutiliser.
public void uneMethodeAScinder()
{
// ...
{
int nombre_de_pixels;
// ...code déplaçable dans une méthode...
}
// Le code ci-dessous ne peut utiliser la variable nombre_de_pixels
// devenue hors de portée et ne dépend donc pas de celle-ci.
// ...
}
Réduire la quantité de pile utilisée
[modifier | modifier le wikicode]Les variables qui deviennent hors de portée libère immédiatement de l'espace pour d'autres variables locales dans la pile. Il est évidemment non recommandé d'avoir beaucoup de variables locales. Cependant réduire la quantité de pile est important pour les méthodes récursives : chaque nouvel appel prend une quantité importante de pile.
Réduire la quantité de mémoire utilisée
[modifier | modifier le wikicode]L'espace mémoire occupé par les objets alloués et référencés uniquement par les variables d'un bloc devient libérable quand ces variables deviennent hors de portée.
Exemple : Génération de plusieurs images
public void genererCartes(double longitude, double latitude)
{
{
int[] pixels = genererCarteZone(longitude-0.5, longitude+0.5, latitude-0.5, latitude+0.5);
ecrireImage(new File(repertoire, "carte_gloable.png"), pixels);
}
// La variable pixels devient hors de portée.
// Le tableau référencé devient libérable si la méthode ecrireImage et les méthodes
// qu'elle appelle n'ont conservé aucune référence au tableau après utilisation.
// Le garbage collector peut libérer la mémoire si besoin avant d'allouer le tableau
// pour l'image ci-dessous.
{
int[] pixels = genererCarteZone(longitude-0.01, longitude+0.01, latitude-0.01, latitude+0.01);
ecrireImage(new File(repertoire, "carte_detaillée.png"), pixels);
}
}
Itérations
Les instructions itératives permettent de répéter l'exécution d'une ou plusieurs instructions, jusqu'à une condition de fin de boucle spécifique : compteur ayant dépassé une limite, changement d'état d'une condition... Java offre deux instructions permettant de réaliser des traitements itératifs : while, for. De plus, les instructions break et continue permettent d'altérer le déroulement des boucles.
Quand plusieurs instructions sont répétées, elles doivent être contenues dans un bloc d'instructions entre accolades.
Instruction while
[modifier | modifier le wikicode]L'instruction while continue d'exécuter le traitement situé dans la boucle tant que la condition indiquée est vérifiée :
while (condition)
{
// instructions à exécuter
}
Exemple :
String s = "gro.skoobikiw.rf";
int i = s.length();
while (i>0)
{
i--; // Décrémenter i
System.out.print(s.charAt(i));
}
System.out.println();
// Affiche le contenu de la chaîne à l'envers : fr.wikibooks.org
Une autre forme du while évalue la condition à la fin de chaque cycle, plutôt qu'au début. Le bloc d'instruction est donc toujours exécuté au moins une fois :
do
{
// instructions à exécuter
}
while (condition);
Instruction for
[modifier | modifier le wikicode]L'instruction for permet d'exécuter un traitement de manière répétitive, mais en spécifiant différemment la condition d'arrêt.
À l'intérieur des parenthèses se trouvent trois blocs d'instructions séparés par un point-virgule. L'exécution de la boucle for commence par exécuter le premier bloc, qui sert généralement à initialiser une variable. Ensuite le second bloc est exécuté (la condition). Si la condition retourne la valeur booléenne true, le corps de la boucle for est exécuté (le bloc d'instruction entre accolade). Sinon l'exécution de la boucle for se termine. Si le corps de la boucle a été exécuté, le troisième bloc est exécuté (incrémenter une variable en général) et le processus recommence : la condition est évaluée, etc.
Voici un exemple typique d'une boucle for qui défini un compteur pour exécuter la boucle i fois. En d'autres termes, le traitement est effectué tant que le compteur n'a pas atteint une limite :
for (int i=0 ; i<limite ; i=i+increment)
{
// instructions à exécuter
}
Exemple :
String s = "gro.skoobikiw.rf";
for (i=s.length()-1 ; i>=0 ; i--)
System.out.print(s.charAt(i));
System.out.println();
// Affiche le contenu de la chaîne à l'envers : fr.wikibooks.org
Environnement concurrentiel
[modifier | modifier le wikicode]L'accès aux données par l'intermédiaire des itérateurs est par essence séquentiel, deux threads ne peuvent bien sur pas utiliser le même itérateur, un objet ne peut pas fournir un itérateur à plusieurs threads. Il est conseillé d'utiliser dans ce cas les classes spécialisées de java.util.concurrent.
Sauts dans le code
[modifier | modifier le wikicode]Java permet de faire des sauts dans le code lors de l'exécution d'une boucle. C'est en général une mauvaise idée et il faut, autant que possible éviter de recourir à ces pratiques. Ces sauts sont toutefois utilisés notamment avec les boucles imbriquées pour éviter des conditions complexes pouvant être sources de confusion et de problèmes.
Il s'agit des instructions break qui permet de sortir immédiatement de la boucle (c'est-à-dire de sauter immédiatement à la première instruction qui suit la boucle) ou de l'instruction continue qui permet de ne pas exécuter le reste de l'itération en cours et passer à l'itération suivante, c'est-à-dire de sauter immédiatement au bloc d'incrémentation et à une nouvelle évaluation de la condition.
L'utilisation demeure obligatoire pour interrompre une boucle de type for each car la condition de boucle n'est pas modifiable. La boucle de type for each sera vue aussi dans les prochains chapitres : elle permet de parcourir les éléments des tableaux, des collections, et des classes itérables.
Exemple :
String[] theme_calendrier = { "Date", "Heure", "Jour de la semaine" };
for(String page : theme_calendrier)
if (page.equals("Heure"))
{
System.out.println("Page trouvée.");
break; // Interrompt la boucle la plus proche : for
}
Un label peut être associé à une instruction de boucle ou un bloc d'instructions pour interrompre une boucle de plus haut niveau, et poursuivre l'exécution après l'instruction ou le bloc nommé.
Exemple :
String[][] pages_thematiques =
{
// POO
{ "Classe", "Attribut", "Héritage" },
// Calendrier
{ "Date", "Heure", "Jour de la semaine" },
// Cuisine
{ "Recette", "Ustensiles", "Techniques" },
};
l_recherche:
{
for(String[] pages : pages_thematiques)
{
for(String page : pages)
if (page.equals("Heure"))
{
System.out.println("Page trouvée.");
break l_recherche;
}
}
System.out.println("Page non trouvée."); // Non exécuté si page trouvée.
}
Pour les collections
[modifier | modifier le wikicode]Le chapitre Collections montrera que ce type d'objet possède sa propre itération : pour chaque objet d'une collection.
Tableaux
Un tableau est un ensemble indexé de données d'un même type. L'utilisation d'un tableau se décompose en trois parties :
- Création du tableau ;
- Remplissage du tableau ;
- Lecture du tableau.
Création d'un tableau
[modifier | modifier le wikicode]Un tableau se déclare et s'instancie comme une classe :
int monTableau[ ] = new int[10];
ou
int [ ] monTableau = new int[10];
L'opérateur [ ] permet d'indiquer qu'on est en train de déclarer un tableau.
Dans l'instruction précédente, nous déclarons un tableau d'entiers (int, integer) de taille 10, c'est-à-dire que nous pourrons stocker 10 entiers dans ce tableau.
Si [ ] suit le type, toutes les variables déclarées seront des tableaux, alors que si [ ] suit le nom de la variable, seule celle-ci est un tableau :
int [] premierTableau, deuxiemeTableau;
float troisiemeTableau[], variable;
Dans ces quatre déclarations, seule variable n'est pas un tableau.
Remplissage d'un tableau
[modifier | modifier le wikicode]Une fois le tableau déclaré et instancié, nous pouvons le remplir :
int [] monTableau = new int[10];
monTableau[5] = 23;
L'indexation démarre à partir de 0, ce qui veut dire que, pour un tableau de N éléments, la numérotation va de 0 à N-1.
Dans l'exemple ci-dessus, la 6ème case contient donc la valeur 23.
Nous pouvons également créer un tableau en énumérant son contenu :
int [] monTableau = {5,8,6,0,7};
Ce tableau contient 5 éléments.
Lorsque la variable est déjà déclarée, nous pouvons lui assigner d'autres valeurs en utilisant l'opérateur new
:
monTableau = new int[]{11,13,17,19,23,29};
Lecture d'un tableau
[modifier | modifier le wikicode]Pour lire ou écrire les valeurs d'un tableau, il faut ajouter l'indice entre crochets ([
et ]
) à la suite du nom du tableau :
int [] monTableau = {2,3,5,7,11,23,17};
int nb;
monTableau[5] = 23; // -> 2 3 5 7 11 23 17
nb = monTableau[4]; // 11
L'indice 0 désigne le premier élément du tableau.
L'attribut length
d'un tableau donne sa longueur (le nombre d'éléments).
Donc pour un tableau nommé monTableau
l'indice du dernier élément est monTableau.length-1
.
Ceci est particulièrement utile lorsque nous voulons parcourir les éléments d'un tableau.
for (int i = 0; i < monTableau.length; i++)
{
int élément = monTableau[i];
// traitement
}
Parcours des tableaux
[modifier | modifier le wikicode]Java 5 fournit un moyen plus court de parcourir un tableau.
L'exemple suivant réalise le traitement sur monTableau (tableau d'entiers) :
int[] monTableau = {150, 200, 250};
for (int élément : monTableau)
{
// traitement
}
Attention néanmoins, la variable element
contient une copie de monTableau[i]
.
Avec des tableaux contenant des variables primitives, toute modification de élément
n'aura aucun effet sur le contenu du tableau.
// Vaine tentative de remplir tous les éléments du tableau avec la valeur 10
for(int élément : monTableau)
{
élément = 10;
}
// La bonne méthode :
for(int i=0 ; i<monTableau.length ; i++)
{
monTableau[i] = 10;
}
// Ou plus court :
Arrays.fill(monTableau, 10);
Pour éviter de modifier la variable, utilisez le mot-clé final
:
for (final int élément : monTableau)
{
// traitement
// toute tentative de modification de la variable élément sera détectée par le compilateur.
}
Tableaux à plusieurs dimensions
[modifier | modifier le wikicode]En Java, les tableaux à plusieurs dimensions sont en fait des tableaux de tableaux.
Exemple, pour allouer une matrice de 5 lignes de 6 colonnes :
int[][] matrice = new int[5][];
for (int i=0 ; i<matrice.length ; i++)
matrice[i] = new int[6];
Java permet de résumer l'opération précédente en :
int[][] matrice=new int[5][6];
La première version montre qu'il est possible de créer un tableau de tableaux n'ayant pas forcément tous la même dimension.
On peut également remplir le tableau à la déclaration et laisser le compilateur déterminer les dimensions des tableaux, en imbriquant les accolades :
int[][] matrice =
{
{ 0, 1, 4, 3 } , // tableau [0] de int
{ 5, 7, 9, 11, 13, 15, 17 } // tableau [1] de int
};
Pour déterminer la longueur des tableaux, on utilise également l'attribut length
:
matrice.length // 2
matrice[0].length // 4
matrice[1].length // 7
De la même manière que précédement, on peut facilement parcourir tous les éléments d'un tableau :
for (int i=0 ; i<matrice.length ; i++)
{
for (int j=0 ; j<matrice[i].length ; j++)
{
//Action sur matrice[i][j]
}
}
Depuis Java 5, il est possible de parcourir les valeurs comme ceci :
for (int[] row : matrice)
{
for (int j=0 ; j<row.length ; j++)
{
//Action sur row[j]
}
}
Le parcours des éléments du tableau row
peut également utiliser la boucle for itérative sur le type primitif int
.
Ce type de boucle ne permet pas de modifier les éléments du tableau.
for (int[] row : matrice)
{
// Modifications sur row[index] répercutées sur matrice[...][index]
// Modifications sur row ignorées (copie locale de la référence au tableau)
for (int cell : row)
{
// Action sur cell
// Modifications sur cell ignorées (copie locale de la valeur)
}
}
Pour une matrice d'objet, cela est donc également possible :
String[][] matrice_de_themes =
{
{ "Java", "Swing", "JavaFX" },
{ "Python", "Numpy" },
{ "Vélo", "Chambre à air", "Rustine", "Guidon" },
{ "Cuisine", "Recette", "Ingrédient", "Préparation", "Ustensile" },
};
for (String[] ligne_theme : matrice_de_themes)
{
for (String mot : ligne_theme)
{
//Action sur mot
System.out.println(mot);
}
}
La classe Arrays
[modifier | modifier le wikicode]La classe Arrays
du package java.util
possède plusieurs méthodes de gestion des tableaux de types de base, et d'objets :
- la méthode
asList
convertit un tableau en liste, - la méthode
binarySearch
effectue la recherche binaire d'une valeur dans un tableau, - la méthode
equals
compare deux tableaux renvoie un booléen true s'ils ont même longueur et contenu, - la méthode
fill
remplit un tableau avec la valeur donnée, - la méthode
sort
trie un tableau dans l'ordre croissant de ses éléments.
String[] mots_clés =
{
"Série",
"Auteur",
"Épisode",
"Comédien",
"Acteur",
"Film",
"Cinéma",
"Réalisateur"
};
Arrays.sort(mots_clés);
for(final String mot : mots_clés)
System.out.println(mot);
|
Sortie console : Acteur Auteur Cinéma Comédien Film Réalisateur Série Épisode |
Par défaut, Arrays.sort()
place les caractères non ASCII après le "z" (ex : à, é...). Pour tenir compte de l'Unicode, il faut donc utiliser son deuxième paramètre : un java.text.Collator
ou un java.util.Comparator
.
String[] mots_clés =
{
"Série",
"Auteur",
"Épisode",
"Comédien",
"Acteur",
"Film",
"Cinéma",
"Réalisateur"
};
String règles_français =
"<a,A<b,B<c,C<d,D<e,E<f,F<g,G<h,H<i,I" +
"<j,J<k,K<l,L<m,M<n,N<o,O<p,P<q,Q<r,R" +
"<s,S<t,T<u,U<v,V<w,W<x,X<y,Y<z,Z" +
"<\u00E6,\u00C6 aa,AA";
RuleBasedCollator collatorFrançais =
new RuleBasedCollator(règles_français);
Arrays.sort(mots_clés, collatorFrançais);
for(final String mot : mots_clés)
System.out.println(mot);
|
Sortie console : Acteur Auteur Cinéma Comédien Épisode Film Réalisateur Série |
Copie d'un tableau
[modifier | modifier le wikicode]La copie d'un tableau implique la copie de ses éléments dans un autre tableau. Dans le cas d'un tableau d'objets, seules les références à ces objets sont copiées, aucun nouvel objet n'est créé.
La méthode arraycopy
de la classe System
permet de copier tout ou partie d'un tableau vers un autre tableau déjà alloué.
Comme toutes les classes, les tableaux dérivent de la classe java.lang.Object
. Les méthodes de la classe Object
sont donc utilisables :
int[] premiers = { 2, 3, 5, 7, 11 };
System.out.println( premiers.toString() ); // Par défaut <type>@<hashcode>, exemple : [I@a108298c
System.out.println( Arrays.toString(premiers) ); // Pour afficher son contenu à l'écran
La copie intégrale d'un tableau dans un nouveau tableau peut donc se faire en utilisant la méthode clone()
. La valeur retournée par cette méthode étant de type Object
, il faut la convertir dans le type concerné.
Exemple :
int[] nombres = { 2, 3, 5, 7, 11 };
int[] copie = (int[]) nombres.clone();
nombres[1]=4; // nombres contient 2 4 5 7 11
// tandis que copie contient toujours 2 3 5 7 11
Somme des éléments d'un tableau
[modifier | modifier le wikicode]public class SommeTableaux
{
public static void main(String[] args)
{
int[] nb = {1,2,3,4,5,6,7,8,9};
int somme = java.util.stream.IntStream.of(nb).sum();
System.out.println(somme); //45
}
}
Modifier la taille et copier une partie
[modifier | modifier le wikicode]La taille d'un tableau est fixe, mais il est possible d'allouer un autre tableau avec une taille différente et de recopier une partie des éléments du tableau d'origine.
Cela peut se faire manuellement, ou en utilisant les méthodes existantes de la classe java.util.Arrays
.
Arrays.copyOf(T[] original, int nouvelle_taille)
- Retourne un nouveau tableau de la taille spécifiée rempli avec les éléments du tableau original. Si la taille est plus petite, les derniers éléments ne sont pas copiés. Si la taille est plus grande, la fin du nouveau tableau est remplie par l'élément nul du type des éléments :
false
pourboolean
,0
pour les types numériques,null
pour tous les autres types (tableau, chaîne, objet, ...).
Une autre méthode permet de copier une portion d'un tableau en spécifiant un index de début et un index de fin. Cet index de fin peut aller au delà de la taille du tableau original, auquel cas les derniers éléments du nouveau tableau sont remplis par l'élément nul du type des éléments, comme pour la méthode précédente.
Arrays.copyOfRange(T[] original, int index_début, int index_fin)
- Retourne un nouveau tableau dont la taille est la différence entre les deux indexs, rempli avec les éléments du tableau original dont l'indice est compris entre l'index de début (inclus) et celui de fin (exclu).
Comparaison de deux tableaux
[modifier | modifier le wikicode]Référence
[modifier | modifier le wikicode]Une variable de type tableau étant de type référence, l'utilisation des opérateurs de comparaison ==
et !=
ne font que comparer les références.
Ils ne permettent pas de détecter des références à deux tableaux différents mais dont la taille et le contenu est le même (éléments dans le même ordre).
import java.util.*;
public class CompareTableaux
{
public static void main(String[] args)
{
String[]
arr1 = { "2", "3", "5", "7", "11" },
arr2 = { "2", "4", "6", "8", "11", "12" },
arr3 = { "2", "4", "6", "8", "11", "12" }, // Même contenu que arr2
arr4 = arr3; // Même référence que arr3
System.out.println("arr1 == arr2 --> " + (arr1 == arr2)); // false
System.out.println("arr2 == arr3 --> " + (arr2 == arr3)); // false
System.out.println("arr3 == arr4 --> " + (arr3 == arr4)); // true
}
}
Contenu identique (éléments dans le même ordre)
[modifier | modifier le wikicode]Pour tester si deux tableaux ont la même taille et les mêmes éléments à la même position, le plus simple est d'utiliser la méthode statique equals
de la classe java.util.Arrays
.
Cette méthode est en fait surchargée pour supporter les éléments de tous les types primitifs et des types références (objets).
import java.util.*;
public class CompareTableaux
{
public static void main(String[] args)
{
String[]
arr1 = { "2", "3", "5", "7", "11" },
arr2 = { "2", "4", "6", "8", "11", "12" },
arr3 = { "2", "4", "6", "8", "11", "12" }, // Même contenu que arr2
arr4 = arr3; // Même référence que arr3
System.out.println("Arrays.equals(arr1, arr2) --> " + Arrays.equals(arr1, arr2)); // false
System.out.println("Arrays.equals(arr2, arr3) --> " + Arrays.equals(arr2, arr3)); // true
System.out.println("Arrays.equals(arr3, arr4) --> " + Arrays.equals(arr3, arr4)); // true
}
}
Éléments communs et particuliers
[modifier | modifier le wikicode]Il serait possible de lancer des boucles de comparaison, mais le plus court moyen donne un avant-goût du chapitre Collections. Le code ci-dessous établit, à partir de deux tableaux de chaînes de caractères, la liste des éléments communs aux deux et la liste des éléments particuliers à chaque tableau.
import java.util.*;
public class CompareTableaux
{
public static void main(String[] args)
{
String[]
arr1 = { "2", "3", "5", "7", "11" },
arr2 = { "2", "4", "6", "8", "11", "12" };
List<String> l1 = Arrays.asList(arr1);
List<String> l2 = Arrays.asList(arr2);
// Intersection, comme un et logique :
// Éléments dans le tableau arr1 ET dans le tableau arr2
HashSet<String> communs = new HashSet<String>(l1);
communs.retainAll(l2);
// Union exclusive, comme un ou exclusif logique :
// Éléments dans le tableau arr1 OU dans le tableau arr2 mais pas les deux.
HashSet<String> non_communs = new HashSet<String>(l1);
non_communs.addAll(l2);
non_communs.removeAll(communs);
System.out.println(communs); // [11, 2]
System.out.println(non_communs); // [12, 3, 4, 5, 6, 7, 8]
}
}
Nouvelle allocation d'un tableau de grande taille
[modifier | modifier le wikicode]Certaines applications ont besoin de beaucoup de mémoire et alloue beaucoup de ressources. Par exemple, un éditeur de vidéos peut garder en mémoire plusieurs images sous la forme d'un tableau de pixels pour modifier une séquence vidéo.
Dans une telle situation, la limite mémoire est souvent atteinte. Supposons que l'application ait alloué un tableau de pixels pour une opération qui vient de se terminer, et qu'un nouveau tableau de pixels de taille différente soit nécessaire pour la suite du traitement.
public class VideoProcessing
{
private int[] pixels;
private void traitement()
{
// Traitement 1
pixels = new int[width*height*2]; // HD, 16 bits/couleurs
// ...
// Traitement 2
pixels = new int[width2*height2*2];
// ...
}
}
Au moment de la seconde allocation, il est nécessaire que la quantité de mémoire disponible soit suffisante pour la taille du nouveau tableau.
Durant l'allocation du second tableau, le premier tableau n'est pas libéré : l'opération new
est effectuée avant l'assignation qui écrase la référence au premier tableau.
Afin d'éviter un problème de quantité de mémoire disponible insuffisante, il est recommandé d'assigner null
à la référence au premier tableau (et à toutes les autres références à ce même tableau) afin que le ramasse-miettes puisse libérer ce tableau si besoin.
public class VideoProcessing
{
private int[] pixels;
private void traitement()
{
// Traitement 1
pixels = new int[width*height*2]; // HD, 16 bits/couleurs
// ...
// Traitement 2
pixels = null; // Premier tableau libérable si besoin, avant new
pixels = new int[width2*height2*2];
// ...
}
}
Les classes en Java
Introduction
[modifier | modifier le wikicode]La notion de classe constitue le fondement de la programmation orientée objet. Une classe est la déclaration d'un type d'objet.
JSE offre un certain nombre de classes natives dans différents packages[1] :
Version de Java | Année de sortie | Nombre de classes |
---|---|---|
9 | 2017 | 6 005 |
8 | 2014 | 4 240 |
7 | 2011 | 4 024 |
6 | 2006 | 3 793 |
5 | 2004 | 3 279 |
1.4.2 | 2002 | 2 723 |
1.3.1 | 2000 | 1 840 |
Il faut au moins connaitre les plus basiques, telles que String
pour les chaines de caractères.
Créer une classe
[modifier | modifier le wikicode]En Java, les classes sont déclarées à l'aide du mot-clef class
, suivi du nom de la classe déclarée, suivi du corps de la classe entre accolades.
Par convention, un nom de classe commence par une majuscule.
public class MaClasse
{
// corps de la classe
}
Le mot clé public
précise que la classe est publique, donc utilisable par toute autre classe quel que soit le paquetage où elle est définie.
Le fichier contenant cette déclaration doit avoir pour extension .java. Ce fichier doit avoir le même nom que la classe publique déclarée.
Un fichier peut contenir plusieurs déclarations de classes (ce n'est pas recommandé, il faut partir du principe 1 classe = 1 fichier, pour des problèmes évidents de relecture du code, devoir modifier du code où plusieurs classes sont écrites dans un seul fichier est le meilleur moyen de faire n'importe quoi), mais il ne peut contenir qu'au plus une classe dite publique (dont le mot-clef class
est précédé de public
, comme dans l'exemple ci-dessus).
Le fichier doit obligatoirement porter le même nom que cette classe publique : dans l'exemple ci-dessus, il faudrait donc sauver notre classe dans un fichier nommé MaClasse.java.
Importer une classe
[modifier | modifier le wikicode]Un fichier .java peut commencer par une ou plusieurs déclarations d'import. Ces imports ne sont pas indispensables, mais autorisent en particulier l'accès aux classes prédéfinies sans avoir à spécifier leur chemin d'accès complet dans les collections de classes prédéfinies (organisées en packages). Dans le code ci-dessous, on souhaite par exemple utiliser la classe prédéfinie Vector (un type de données comparable à des tableaux dont la taille peut varier dynamiquement). Dans la sous-collection de classes prédéfinies "java", cette classe se trouve dans la sous-collection "util" (ou encore : cette classe est dans le package "java.util").
Sans import, il faut spécifier le nom complet de la classe (packages inclus) :
public class MaClasse
{
// ...
public static void main(String[] args)
{
// sans l'import :
java.util.Vector v = new java.util.Vector();
// ...
}
}
Avec import, seul le nom de la classe (sans packages) utilisée est nécessaire :
import java.util.Vector;
public class MaClasse
{
// ...
public static void main(String[] args)
{
// avec l'import :
Vector v = new Vector();
// ...
}
}
Quand plusieurs classes du même package sont utilisées, l'import peut utiliser le caractère étoile. Une classe peut donc avoir une longue liste d'import :
import java.util.Vector;
import java.util.ArrayList;
import java.util.HashMap;
public class MaClasse
{
// ...
public static void main(String[] args)
{
Vector v = new Vector();
ArrayList a = new ArrayList();
HashMap m = new HashMap();
}
}
ou une liste plus courte :
import java.util.*;
public class MaClasse
{
// ...
public static void main(String[] args)
{
Vector v = new Vector();
ArrayList a = new ArrayList();
HashMap m = new HashMap();
}
}
Enfin, la définition d'une classe peut aussi être précédée d'une (et une seule) déclaration de package, qui indique à quel emplacement se trouve le fichier dans l'arborescence des répertoires d'un projet Java.
Par exemple, si la racine de notre projet Java est le répertoire /home/user/monProjet/
(ou C:\Mes documents\monProjet\
sous Windows) et que notre classe se trouve dans le sous-répertoire org/classes/
(soit, respectivement /home/user/monProjet/org/classes/
et c:\Mes documents\monProjet\org\classes\
) nous aurons une déclaration du package de la forme :
package org.classes;
public class MaClasse
{
// contenu de la classe
}
Il est fortement recommandé d'utiliser la déclaration de packages afin d'organiser le code :
- Sans package il est difficile de retrouver une classe au milieu d'un grand nombre de fichiers tous dans le même répertoire. Il faut regrouper les classes en petit nombre dans des packages différents.
- Un package rassemble les classes relatives à un même type de traitement (Exemple :
java.io
pour les classes traitant des entrées-sorties) ; - Afin d'éviter les conflits, le nom du package doit suivre les recommandations de nommage suivantes :
- Le nom du package doit commencer par un nom de domaine inversé (en général, celui du propriétaire du code) ;
- Exemple :
org.wikibooks.fr
- Exemple :
- Les noms de package commençant par
java
etjavax
sont réservés.
- Le nom du package doit commencer par un nom de domaine inversé (en général, celui du propriétaire du code) ;
Instancier une classe
[modifier | modifier le wikicode]Un objet peut être vu comme un ensemble de données regroupées à la manière des structures de C ou des enregistrements de Pascal. Une classe définit un modèle d'objet. Chaque objet créé à partir d'une classe est appelé instance de cette classe. L'ensemble de données internes d'un objet - les champs de cet objet - est spécifié par sa classe. Une classe peut en outre contenir : d'une part, des constructeurs - du code destiné à l'initialisation de ses instances au moment de leur création ; d'autre part des méthodes - du code destiné à la gestion des données internes de chacune de ses instances. On dit que ces méthodes sont invocables sur les objets.
Les objets se manipulent à l'aide de variables appelées références. Une référence vers un objet est l'analogue d'un pointeur vers celui-ci, au sens usuel de la notion de pointeur dans les langages impératifs tels que C - on dit que cette référence pointe sur l'objet, ou encore qu'elle référence cet objet. Notez que par abus de langage, mais par abus de langage seulement, on confond en pratique une référence vers un objet et l'objet lui-même. On parlera ainsi de "l'objet r
" plutôt que de "l'objet référencé par r
" ou "l'objet pointé par r
". Pour autant, une référence vers un objet n'est pas l'objet lui-même : une référence peut ne pointer vers aucun objet - en particulier si elle est déclarée sans être initialisée, ou si on lui affecte la valeur de référence nulle, notée null
; elle peut pointer successivement vers des objets différents au cours du temps ; d'autre part, deux références distinctes peuvent pointer vers le même objet - les champs de l'objet sont alors accessibles, et les méthodes de sa classe deviennent invocables via l'une ou l'autre de ces deux références, avec le même effet.
Le type d'une référence spécifie la (ou même les) classe(s) des objets sur lesquels elle est susceptible de pointer. Une référence déclarée de type "référence vers MaClasse
", où MaClasse
est le nom d'une classe, est susceptible de pointer vers n'importe quelle instance de MaClasse
.
MaClasse r; // déclaration d'une référence de type "référence vers MaClasse"
/* instructions diverses */
r = new MaClasse(); // création d'une instance de MaClasse, puis référencement par r de l'objet crée.
On peut déclarer une référence et lui faire immédiatement référencer une nouvelle instance, créée dans cette déclaration :
MaClasse r = new MaClasse(); // on crée une instance de MaClasse, que l'on référence par r
Références
[modifier | modifier le wikicode]- ↑ (anglais) « How many classes are there in Java standard edition? », Stack Overflow, question posée le 24 juin 2010.
Membres
Introduction
[modifier | modifier le wikicode]Les membres d'une classe sont les méthodes (traitements) et attributs (données) qui en font partie.
Exemple :
public class Horloge
{
// Définition des attributs
int heures;
int minutes;
int secondes;
// Définition des méthodes
public void definitHeure()
{
heures = 12;
minutes = 30;
secondes = 30;
}
public void incrementeHeure()
{
secondes++;
if (secondes==60)
{
secondes=0;
minutes++;
if (minutes==60)
{
minutes=0;
heures++;
if (heures==24)
{
heures=0;
}
}
}
}
protected void afficheHeure()
{
System.out.println("Il est "+heures+":"+minutes+":"+secondes);
}
public static void main (String[] args)
{
Horloge montre = new Horloge(); // Nouvelle instance de la classe
// Accès aux membres de la classe de l'objet avec le caractère point : <objet>.<membre>
montre.definitHeure();
for (int i=0 ; i<10 ; i=i+1)
{
montre.incrementeHeure();
montre.afficheHeure();
}
}
}
Dans cet exemple, la classe Horloge contient trois attributs servant à représenter l'heure (heures, minutes et secondes) et trois méthodes (definitHeure qui initialise, incrementeHeure, qui augmente l'heure d'une seconde, et afficheHeure, qui affiche l'heure).
On distingue parmi les méthodes une catégorie particulière, les constructeurs.
Déclaration
[modifier | modifier le wikicode]Attribut
[modifier | modifier le wikicode]La déclaration d'un attribut se fait de la manière suivante :
modificateurs type nom;
Exemple :
protected String auteur;
La déclaration peut inclure une initialisation :
protected String auteur = "Moi";
Constructeur
[modifier | modifier le wikicode]La déclaration d'un constructeur se fait de la manière suivante :
modificateurs NomDeLaClasse(type et nom des paramètres éventuels) { corps du constructeur }
Exemple :
public Livre(String titre, String auteur)
{
//...
}
Méthode
[modifier | modifier le wikicode]La déclaration d'une méthode se fait comme suit :
modificateurs typeDeRetour nom(type et nom des paramètres éventuels) { corps de la méthode }
Exemple :
public String getTitre()
{
return this.titre;
}
En Java (au moins jusqu'à la version 8) les fonctions :
- ne peuvent pas avoir de paramètre avec une valeur par défaut (comme en C++ ou PHP).
- ne peuvent pas contenir de paramètre facultatif.
Cependant, pour le dernier point, si un paramètre peut être facultatif, il est possible de créer deux versions de la méthode ayant le même nom mais dont l'une n'a pas ce paramètre.
Méthode à nombre de paramètres variable
[modifier | modifier le wikicode]Java 5 introduit un mécanisme permettant d'écrire des méthodes acceptant un nombre variable d'arguments ("varargs"), alors qu'il fallait passer par un tableau ou autre artifice similaire auparavant. La syntaxe est très similaire à la syntaxe utilisée pour la fonction printf en C, ce qui a permis d'ajouter une méthode printf
dans la classe PrintStream
, ce qui permet de faire System.out.printf("what ever %d",5);
Exemple :
public class Message
{
public void message(String recette, String... arguments)
{
System.out.println("Ingrédients pour "+recette+" :");
for (String s : arguments)
System.out.println(s);
}
public static void main (String[] args)
{
Message menu = new Message();
menu.message("déjeuner","ail","oignon","échalote");
}
}
Bloc d'initialisation
[modifier | modifier le wikicode]Une classe peut comporter un ou plusieurs blocs d'instructions servant à initialiser toute instance de la classe, ou à initialiser des membres statiques si le bloc est déclaré statique (static
).
Exemple :
public class ImageFile
{
final int MAX_SIZE; // final -> variable initialisée une seule fois
// Bloc d'initialisation
{
int maxsize;
try
{
maxsize = Integer.parseInt(System.getProperty("file.maxsize"));
}
catch(Exception ex) // propriété "file.maxsize" non définie (NullPointerException) ou n'est pas un nombre
{
maxsize = 1000; // valeur par défaut
}
MAX_SIZE = maxsize; // variable initialisée une seule fois
}
ImageFile(File f){ /* ... */ }
ImageFile(File f, int width){ /* ... */ }
ImageFile(File f, int width, int height){ /* ... */ }
}
Les instructions contenues dans les blocs d'initialisation sont appelées avant les instructions du constructeur utilisé. Un bloc d'initialisation permet donc de :
- gérer les exceptions (contrairement à une affectation dans la déclaration de la variable membre),
- éviter la duplication du code dans les différents constructeurs.
La classe peut aussi comporter des blocs d'initialisation statiques, qui sont des blocs d'instructions précédés du modificateur static
, et qui sont exécutés au chargement de la classe, juste après l'initialisation des attributs statiques.
Exemple :
public class Exemple
{
static
{
System.out.println("La classe Exemple est chargée");
}
}
Un bloc d'initialisation statique permet de :
- gérer les exceptions (contrairement à une affectation dans la déclaration de la variable membre),
- charger la bibliothèque native implémentant les méthodes natives de la classe.
Modificateur
Modificateurs d'accès
[modifier | modifier le wikicode]En Java, la déclaration d'une classe, d'une méthode ou d'un membre peut être précédée par un modificateur d'accès.
Un modificateur indique si les autres classes de l'application pourront accéder ou non à la classe/méthode/membre (qualifié par la suite d'« item »).
Ces modificateurs sont au nombre de quatre :
- public : toutes les classes peuvent accéder à l'item
- protected : seules les classes dérivées et les classes du même package peuvent accéder à l'item
- private : l'item est seulement accessible depuis l'intérieur de la classe où il est défini.
- (par défaut) : sans modificateur d'accès, seules les classes du même package peuvent accéder à l'item.
L'utilisation des modificateurs permet au programmeur de contrôler la visibilité des différents items et permet d'empêcher que des actions illégales soient effectuées sur les items.
Pour plus de détails sur les restrictions d’accès, voir le chapitre « Encapsulation ».
abstract
[modifier | modifier le wikicode]Le modificateur abstract
indique qu'une classe ou méthode est abstraite.
final
[modifier | modifier le wikicode]Le modificateur final
indique une non modification de l'entité déclarée.
Il s'applique aux attributs d'une classe, aux méthodes et aux classes.
Attribut final
[modifier | modifier le wikicode]Ajouté devant un attribut, il le rend immuable, dès lors qu'il est initialisé. Il n'est pas obligatoire de l'initialiser dès la déclaration, contrairement à d'autres langages. Cependant l'initialisation doit être faite par le constructeur ou un bloc de code d'initialisation, car leur code est appelé une seule fois. L'initialisation ne peut être assurée par une méthode car une méthode peut être appelée n'importe quand, et un nombre de fois indéterminé.
Pour les types primitifs, final
fige la valeur, pour les objets, final
fige la référence, et non la valeur de la référence (i.e. seule l'instanciation est figée).
Devant une variable locale (dans une méthode, donc), il a le même comportement que pour un attribut.
Méthode finale
[modifier | modifier le wikicode]Devant une méthode, il indique que cette méthode ne peut pas être modifiée dans une classe dérivée.
Les méthodes static
et private
sont implicitement final
.
Classe finale
[modifier | modifier le wikicode]Devant une classe, il indique que cette classe ne peut pas avoir de sous-classes. Les méthodes de cette classe sont donc également finales.
static
[modifier | modifier le wikicode]Le modificateur static indique, pour une méthode, qu'elle peut être appelée sans instancier sa classe (syntaxe : Classe.methode()).
Pour un attribut, qu'il s'agit d'un attribut de classe, et que sa valeur est donc partagée entre les différentes instances de sa classe.
De plus, il est possible de déclarer dans une classe un bloc d'initialisation statique, qui est un bloc d'instruction précédé du modificateur static
.
synchronized
[modifier | modifier le wikicode]Le modificateur synchronized
indique que la méthode ne peut être exécutée que par un seul thread à la fois. Le verrou ne s'active que pour l'objet sur lequel la méthode a été appelée (une même méthode synchronized peut être exécutée en même temps par deux threads différents sur deux objets différents).
transient
[modifier | modifier le wikicode]Le modificateur transient
indique que lors de la sérialisation de l'objet, cet attribut n'est pas sérialisé et donc il est ignoré. Cela signifie que lorsque l'on désérialise l'objet, l'attribut portant le modificateur transient
n'est pas défini dans l'objet désérialisé.
Il s'agit en général d'attributs qui peuvent être recalculés à partir des autres attributs de l'objet.
native
[modifier | modifier le wikicode]Ce modificateur permet d'indiquer que cet item est défini dans une bibliothèque externe écrite dans un autre langage de programmation, utilisant l'API JNI.
strictfp
[modifier | modifier le wikicode]Pour une méthode, une classe ou une interface, le modificateur strictfp
(abréviation de strict floating point) force la JVM à évaluer les opérations à virgules flottantes (sur les double
et float
) conformément à la spécification Java, c'est-à-dire de la gauche vers la droite. Cela permet d'avoir un comportement identique d'une JVM à une autre et d'éviter certains dépassements de valeur limite pour les résultats intermédiaires.
volatile
[modifier | modifier le wikicode]Pour une variable, le modificateur volatile
force la JVM, avant et après chaque utilisation de la variable, à la rafraîchir à partir de la mémoire principale au lieu d'utiliser un cache local. Cela permet de synchroniser la valeur de la variable entre plusieurs threads.
Héritage
L’héritage est la définition d’une classe par extension des caractéristiques d’une autre classe, exemple : on a créé une classe Vehicule. Ainsi les classes Automobile et Avion ont pu hériter des caractéristiques de Vehicule.
L'héritage, est l'un des mécanismes les plus puissants de la programmation orientée objet, permet de reprendre des membres d'une classe (appelée superclasse ou classe mère) dans une autre classe (appelée sous-classe, classe fille ou encore classe dérivée), qui en hérite. De cette façon, on peut par exemple construire une classe par héritage successif.
En Java, ce mécanisme est mis en œuvre au moyen du mot-clé extends
Exemple :
public class Vehicule
{
public int vitesse;
public int nombre_de_places;
}
public class Automobile extends Vehicule
{
public Automobile()
{
this.vitesse = 90;
this.nombre_de_places = 5;
}
}
Dans cet exemple, la classe Automobile hérite de la classe Vehicule, ce qui veut dire que les attributs vitesse et nombre_de_places, bien qu'étant définis dans la classe Vehicule, sont présents dans la classe Automobile. Le constructeur défini dans la classe Automobile permet d'ailleurs d'initialiser ces attributs.
En Java, le mécanisme d'héritage permet de définir une hiérarchie de classes comprenant toutes les classes. En l'absence d'héritage indiqué explicitement, une classe hérite implicitement de la classe Object. Cette classe Object est la racine de la hiérarchie de classe.
La classe Object
[modifier | modifier le wikicode]Au moment de l'instanciation, la classe fille reçoit les caractéristiques qui peuvent être héritées de sa super-classe, qui elle-même reçoit les caractéristiques qui peuvent être héritées de sa propre superclasse, et ce récursivement jusqu'à la classe Object.
Ce mécanisme permet de définir des classes génériques réutilisables, dont l'utilisateur précise le comportement dans des classes dérivées plus spécifiques.
Il faut préciser que, contrairement à C++, Java ne permet pas l'héritage multiple, ce qui veut dire qu'une classe dérive toujours d'une et d'une seule classe.
Héritage d'interface
[modifier | modifier le wikicode]L'héritage d'interface est aussi possible en Java. À la différence des classes, l'héritage multiple est autorisé, ce qui veut dire qu'une interface peut hériter d'autant d'autres interfaces que désiré.
L'héritage d'interface se fait avec le mot clé extends, puisque c'est une interface qui "étend" une interface existante. Les différentes interfaces héritées sont séparées par une virgule.
L'héritage « multiple » pour une classe (via les interfaces) se fait avec le mot-clé implements. Exemple :
public interface InterfaceA {
public void methodA();
}
public interface InterfaceB {
public void methodB();
}
public interface InterfaceAB extends InterfaceA, InterfaceB {
public void otherMethod();
}
public class ClassAB implements InterfaceAB{
public void methodA(){
System.out.println("A");
}
public void methodB(){
System.out.println("B");
}
public void otherMethod(){
System.out.println("blablah");
}
public static void main(String[] args) {
ClassAB classAb = new ClassAB();
classAb.methodA();
classAb.methodB();
classAb.otherMethod();
}
}
Le mot-clé super
[modifier | modifier le wikicode]Le mot-clé super permet d'accéder aux membres de la super-classe d'une classe, de la même manière que l'on accède aux attributs de la classe elle-même à l'aide du mot-clé this.
Exemple :
public class Avion extends Vehicule
{
public Avion()
{
super();
}
}
Dans cet exemple, le constructeur de la classe Avion fait appel au constructeur de la classe Vehicule.
Ce mot-clé permet également d'accéder explicitement aux membres de la classe de base, dans le cas, par exemple, où il existe déjà un membre portant ce nom dans la classe (surcharge de méthode, ...).
Exemple :
public class Vehicule
{
// ...
public void rouler() throws Exception
{
position += vitesse;
}
}
public class Avion extends Vehicule
{
// ...
public void rouler() throws Exception
{
if (altitude>0)
throw new Exception("L'avion en vol ne peut rouler");
else super.rouler();
}
}
Lien externe
[modifier | modifier le wikicode]
Encapsulation
En Java, comme dans beaucoup de langages orientés objet, les classes, les attributs et les méthodes bénéficient de niveaux d'accessibilité, qui indiquent dans quelles circonstances on peut accéder à ces éléments.
Ces niveaux sont au nombre de 4, correspondant à 3 mots-clés utilisés comme modificateurs : private, protected et public. La quatrième possibilité est de ne pas spécifier de modificateur (comportement par défaut).
Les sections suivantes décrivent les modificateurs du plus restrictif au moins restrictif. Chaque modificateur moins restrictif que le précédent ajoute une nouvelle possibilité d'accès.
Modificateur "private"
[modifier | modifier le wikicode]Un attribut ou une méthode déclarée "private" n'est accessible que depuis l'intérieur de la même classe.
Comportement par défaut (paquetage)
[modifier | modifier le wikicode]Si aucun modificateur n'est indiqué, l'élément n'est accessible que depuis les classes faisant partie du même paquetage (package).
Exemple :
package org.wikibooks.fr.temps;
class Horloge
{
// corps de la classe
}
public class Calendrier
{
void ajouteJour()
{
// corps de la methode
}
int mois;
// suite de la classe
}
La classe Horloge, la méthode ajouteJour et l'attribut mois ne sont accessibles que depuis les classes faisant partie du package org.wikibooks.fr.temps.
Modificateur "protected"
[modifier | modifier le wikicode]Un attribut ou une méthode déclarée "protected" est accessible uniquement aux classes d'un package et à ses sous-classes même si elles sont définies dans un package différent.
Si la classe qui accède au membre est dans un paquetage différent, elle doit être une sous-classe de la classe définissant le membre accédé.
Toutefois, elle ne peut le faire que pour la référence this
ou super
; elle ne peut pas accéder au membre d'un autre objet.
Exemple :
package org.wikibooks.fr.transport; public class Vehicule { protected int kilometrage = 0; }
package org.wikibooks.fr.transport.route; public class Voiture extends Vehicule { public boolean estPlusUtiliséeQue(Voiture autre) { return this.kilometrage > autre.kilometrage; // ^ OK ^ Erreur } }
Modificateur "public"
[modifier | modifier le wikicode]Une classe, un attribut ou une méthode déclarée "public" est visible par toutes les classes et les méthodes.
En résumé
[modifier | modifier le wikicode]Le tableau résume les différents mode d'accès des membres d'une classe.
Modificateur du membre | private
|
aucun | protected
|
public
|
---|---|---|---|---|
Accès depuis la classe | Oui | Oui | Oui | Oui |
Accès depuis une classe du même package
|
Non | Oui | Oui | Oui |
Accès depuis une sous-classe | Non | Non | Oui | Oui |
Accès depuis toute autre classe | Non | Non | Non | Oui |
Polymorphisme
Le polymorphisme veut dire que le même service, aussi appelé opération ou méthode, peut avoir un comportement différent selon les situations.
Polymorphisme paramétrable
[modifier | modifier le wikicode]Plusieurs signatures pour une même méthode (ad hoc)
[modifier | modifier le wikicode]On peut donner à une même méthode, plusieurs signatures pour implémenter des comportements différents selon les types des paramètres passés. La signature d'une méthode est composée du nom de celle ci, de sa portée, du type de donnée qu'elle renvoie et enfin du nombre et du type de ses paramètres.
public class A {
private int a;
public A() { //constructeur 1
System.out.println("Création de A");
}
public A(int a) { //constructeur 2 par surcharge
this.a = a;
System.out.println("Création de A");
}
public int getter() {
return this.a;
}
public void setter(int a) {
this.a = a;
}
public static void main(String[] args) {
A premierA = new A(); //construction par 1
A secondA = new A(1); //construction par 2
}
}
Proposer le passage d'un nombre inconnu de paramètres
[modifier | modifier le wikicode]Dans la signature d'une méthode , on peut préciser qu'il est possible de passer plus de 1 paramètre du même type en suffixant le type du paramètre avec « ...
».
// supposons la méthode suivante :
public String concatenation(String... elements) {
// pour l'implementation, il faut considérer le paramètre comme un tableau
String resultat = "";
for (String element : elements) {
resultat += element;
}
return resultat;
}
// elle peut être appelée ainsi
concatenation("abc", "de", "f"); // renvoie "abcdef"
Polymorphisme d'héritage
[modifier | modifier le wikicode]En redéfinissant une méthode dans une sous-classe, on peut spécialiser le comportement d'une méthode.
public class B extends A {
private int b;
public B() {
super();
System.out.println("Création de B");
}
public B(int a, int b){
super(a);
this.b = b;
System.out.println("Création de B");
}
public int getter() {
return this.b;
}
public void setter(int a, int b) {
super.setter(a);
this.b=b;
}
public static void main(String[] args) {
A[] table = new A[2];
table[0] = new A(10);
table[1] = new B(20,30);
for(A a : table){
System.out.println("* " + a.getter());
}
/* les rėsultats sur console:
Création de A
Création de A
Création de B
* 10
* 30
*/
}
}
Classes abstraites
- Une classe abstraite se trouve à mi-chemin entre les interfaces et les classes.
- Les classes abstraites, comme les interfaces, ne sont pas instanciables.
- Les classes abstraites sont déclarées par le modificateur abstract.
- Il y a plusieurs intérêts à définir des classes abstraites :
- pour interdire l'instanciation d'une classe ;
- pour faire de la factorisation de code en ne donnant qu'une implémentation partielle.
Exemple
[modifier | modifier le wikicode]public interface Chien
{
public void vieillir();
public void aboyer();
}
On sait que la méthode vieillir sera implémentée de la même manière quelle que soit l'implémentation de Chien. Plutôt que d'implémenter cette interface à chaque fois, on va factoriser le code dans une classe abstraite et étendre cette classe quand le besoin s'en fait sentir. On crée donc une classe AbstractChien qui n'implémente que la méthode vieillir de notre interface, les autres étant laissées abstract.
package org.wikibooks.fr;
public abstract class AbstractChien implements Chien
{
// Champs
// On met les champs en protected pour que les classes filles
// puissent les manipuler directement.
protected int age;
protected String couleur;
//Constructeur
//Pourra être utilisé par les classes filles pour initialiser les champs.
public AbstractChien(int age, String couleur)
{
this.age = age;
this.couleur = couleur;
}
// Méthode
//On donne l'implémentation qui est commune à tous les chiens
public void vieillir()
{
age ++;
}
//Cette méthode n'est définie que par les classes filles
//Elle est donc laissée abstract.
public abstract void aboyer();
}
Interfaces
Une interface définit un ensemble de méthodes mais pas leur implémentation. C'est pourquoi il n'est pas possible d'instancier une interface directement. Il est toutefois possible d'appeler une méthode en utilisant une référence à une interface, sans savoir quelle classe implémentant concrètement la méthode appelée est réellement utilisée.
Présentation
[modifier | modifier le wikicode]En fait, une interface est une classe abstraite dont toutes les méthodes sont abstraites et dont tous les attributs sont constants (des constantes, voir le mot-clé final).
- Liste de méthodes dont on donne seulement la signature ;
- Représente un "contrat", ce qu'on attend d'un objet ;
- Peut être implémentée par une ou plusieurs classes qui doivent donner une implémentation pour chacune des méthodes annoncées (et éventuellement d'autres) ;
- Une classe peut implémenter plusieurs interfaces (permettant un héritage multiple, en les séparant par des virgules après le mot implements) ;
- Toutes les méthodes d'une interface sont implicitement abstraites ;
- Une interface n'a pas de constructeurs ;
- Une interface ne peut avoir de champs sauf si ceux-ci sont statiques ;
- Une interface peut être étendue par une ou plusieurs autre(s) interface(s).
Exemple
[modifier | modifier le wikicode]Définition de l'interface ;
package org.wikibooks.fr;
public interface Vehicule
{
public void rouler(int vitesse);
public void freiner();
public int getVitesse();
}
La présence du modificateur public
est implicite et n'est donc pas obligatoire.
Cependant, sa présence est recommandée et permet de montrer que ces méthodes seront publiques, de plus cela permet de copier la signature de la méthode dans la classe qui l'implémente.
La présence du modificateur abstract
est implicite également.
Sa présence n'est cependant pas recommandée afin de permettre la copie de la signature de la méthode dans la classe qui l'implémente.
On a défini ici ce qu'on attend d'un objet de type véhicule.
On peut maintenant donner une ou plusieurs implémentations de cette interface grâce à l'utilisation du mot clef implements :
package org.wikibooks.fr;
public class Velo implements Vehicule
{
// Champs
private String marque;
private int rayonRoue, vitesse;
// Constructeurs
public Velo(String marque, int rayonRoue)
{
this.marque = marque;
this.rayonRoue = rayonRoue;
}
// Méthodes
public int getVitesse()
{
// Retourner la vitesse actuelle du vélo
return vitesse;
}
public void rouler(int vitesse)
{
// Coder ici la manière dont le vélo roule
if (vitesse < 0 || vitesse > 80) throw new IllegalArgument("Vitesse incorrecte pour un vélo.");
this.vitesse = vitesse;
}
public void freiner()
{
// Coder ici la manière dont le vélo freine
this.vitesse = 0;
}
// ... Autres méthodes propres à Velo
}
package org.wikibooks.fr;
public class Auto implements Vehicule
{
//Champs
private String marque;
private int poids, vitesse;
// Constructeurs
public Auto(String marque, int poids)
{
this.marque = marque;
this.poids = poids;
}
// Méthodes
public int getVitesse()
{
// Retourner la vitesse actuelle de l'auto
return vitesse;
}
public void rouler(int vitesse)
{
//Coder ici la manière dont l'auto roule
if (vitesse < 0 || vitesse > 160) throw new IllegalArgument("Vitesse incorrecte pour une auto.");
this.vitesse = vitesse;
}
public void freiner()
{
// Coder ici la manière dont l'auto freine
this.vitesse = 0;
}
// ... Autres méthodes propres à Auto.
}
Dans cet exemple, nous avons donné deux implémentations de Vehicule.
Conséquences :
- Ces 2 objets peuvent être vus comme des véhicules, c'est ce qu'on appelle le polymorphisme.
- Partout où on attend un objet de type Vehicule, on peut mettre un de ces deux objets.
- Par ce biais, on introduit une couche d'abstraction dans notre programmation ce qui la rend beaucoup plus flexible.
Abstraction
[modifier | modifier le wikicode]Si, par exemple, nous avons une classe Personne possédant une méthode conduire(Vehicule v), on peut alors écrire :
Personne p = new Personne();
p.conduire(new Velo()); //comme la méthode attend un Vehicule en argument, on peut passer tout objet implémentant cette interface.
p.conduire(new Auto()); //idem
On peut "instancier" un Vehicule
par le biais de ses implémentations :
Vehicule v = new Auto();
Vehicule t = new Velo();
Dans ce cas v et t sont vus comme des Vehicule
et, par conséquent, on ne peut appeler sur ces objets que les méthodes définies dans l'interface Vehicule
.
Implémentation partielle
[modifier | modifier le wikicode]Une classe peut n'implémenter qu'une partie de l'interface.
Dans ce cas, il s'agit d'une classe abstraite et doit utiliser le mot-clé abstract
.
Dans l'exemple de ce chapitre, les deux classes concrètes implémentent certaines méthodes de la même façon. Ce comportement commun peut être déplacé dans une classe abstraite intermédiaire :
package org.wikibooks.fr;
public abstract class VehiculeConcret implements Vehicule
{
// Attributs accessible par les sous-classes
protected int vitesse;
// Méthodes implémentant l'interface Vehicule
public int getVitesse()
{
// Retourner la vitesse actuelle du véhicule concret
return vitesse;
}
public void freiner()
{
this.vitesse = 0;
}
// La méthode rouler(int vitesse) n'est pas implémentée par cette classe abstraite.
}
package org.wikibooks.fr;
public class Velo extends VehiculeConcret
{
// Champs
private String marque;
private int rayonRoue, vitesse;
// Constructeurs
public Velo(String marque, int rayonRoue)
{
this.marque = marque;
this.rayonRoue = rayonRoue;
}
// Méthodes complétant l'implémentation de l'interface Vehicule
public void rouler(int vitesse)
{
// Coder ici la manière dont le vélo roule
if (vitesse < 0 || vitesse > 80) throw new IllegalArgument("Vitesse incorrecte pour un vélo.");
this.vitesse = vitesse;
}
// ... Autres méthodes propres à Velo
}
package org.wikibooks.fr;
public class Auto extends VehiculeConcret
{
//Champs
private String marque;
private int poids, vitesse;
// Constructeurs
public Auto(String marque, int poids)
{
this.marque = marque;
this.poids = poids;
}
// Méthodes complétant l'implémentation de l'interface Vehicule
public void rouler(int vitesse)
{
//Coder ici la manière dont l'auto roule
if (vitesse < 0 || vitesse > 160) throw new IllegalArgument("Vitesse incorrecte pour une auto.");
this.vitesse = vitesse;
}
// ... Autres méthodes propres à Auto.
}
Instanciation
[modifier | modifier le wikicode]L'exemple suivant semble instancier l'interface :
Vehicule quelqu_un_en_rollers = new Vehicule()
{
private int vitesse = 0;
// Méthodes
public int getVitesse()
{
// Retourner la vitesse actuelle
return vitesse;
}
public void rouler(int vitesse)
{
if (vitesse < 0 || vitesse > 20) throw new IllegalArgument("Vitesse incorrecte pour des rollers.");
this.vitesse = vitesse;
}
public void freiner()
{
// Coder ici la manière dont l'auto freine
this.vitesse = 0;
}
}; // Fin de déclaration d'objet.
En réalité, une classe anonyme est créée et définie, implémentant l'interface indiquée.
Cette syntaxe est utilisée notamment pour créer des instances de listeners pour l'écoute d'évènements, comme en Swing.
Classes internes
Une classe interne est déclarée à l'intérieur d'une autre classe. Elle peut donc accéder aux membres de la classe externe.
Classe interne statique
[modifier | modifier le wikicode]Une classe interne statique ne peut accéder qu'aux membres statiques de sa classe contenante, représentée par ClassExterne dans l'exemple suivant :
public class ClasseExterne
{
private int compteur = 0;
private static String nom = "Exemple";
static class ClasseInterne
{
private int index = 0;
public ClasseInterne()
{
System.out.println("Création d'un objet dans "+nom);
// compteur ne peut être accedé
}
}
}
La compilation du fichier ClasseExterne.java
produit deux fichiers compilés :
ClasseExterne.class
contient la classeClasseExterne
uniquementClasseExterne$ClasseInterne.class
contient la classeClasseInterne
Classe interne non statique
[modifier | modifier le wikicode]Une classe interne non statique peut accéder aux membres statiques de la classe ainsi qu'aux membres de l'objet qui l'a créée. En fait, le compilateur crée un membre supplémentaire dans la classe interne référençant l'objet qui l'a créé.
Une telle classe interne peut être déclarée de manière globale dans l'objet ; elle sera accessible par l'ensemble des méthodes de l'objet. Elle peut aussi être déclarée de manière locale à une méthode de l'objet. Elle sera alors accessible depuis cette seule méthode.
Exemple (Classe non statique globale) :
public class ClasseExterne
{
private int compteur = 0;
class ClasseInterne
{
private int index = 0;
public ClasseInterne()
{
compteur++;
}
}
}
Référence aux membres de la classe englobante
[modifier | modifier le wikicode]Depuis la classe interne, dans le cas où plusieurs variables ou méthodes portent le même nom dans la classe interne et la classe externe, le pointeur this
seul désigne l'instance de la classe interne, tandis que le pointeur this
précédé du nom de la classe externe désigne l'instance de la classe externe.
public class ClasseExterne
{
private int compteur = 10;
class ClasseInterne
{
private int compteur = 0;
public void count()
{
this.compteur++; // -> 1
ClasseExterne.this.compteur--; // -> 9
}
}
}
Instanciation
[modifier | modifier le wikicode]Les méthodes non-statiques de la classe englobante peuvent instancier la classe interne directement :
public class ClasseExterne
{
class ClasseInterne
{
// ...
}
public void action()
{
ClasseInterne obj = new ClasseInterne();
// ...
}
}
Tandis qu'une méthode statique (pas d'instance this
de la classe externe) ou une méthode définie dans une autre classe doit utiliser une instance de la classe externe pour préfixer l'opérateur new
:
public class ClasseExterne
{
class ClasseInterne
{
// ...
}
public static void main(String[] args)
{
ClasseExterne ext = new ClasseExterne();
ClasseInterne obj = ext.new ClasseInterne();
// ...
}
}
Les méthodes non-statiques de la classe englobante peuvent également instancier la classe interne de cette manière pour que l'instance soit liée à une instance de la classe externe autre que this
.
Accès
[modifier | modifier le wikicode]Comme tous les membres de la classe externe, la déclaration des classes internes peut être préfixée par un Modificateur d'accès :
public
: Toutes les classes peuvent utiliser la classe interne.protected
: Seules les sous-classes et les classes du même paquetage peuvent utiliser la classe interne.: (vide = paquetage) Seules les classes du même paquetage peuvent utiliser la classe interne.
private
: Seule la classe externe peut utiliser la classe interne.
public class ClasseExterne
{
private class ClasseInterne
{
// ...
}
public void action()
{
ClasseInterne obj = new ClasseInterne();
// ...
}
}
Classe anonyme
[modifier | modifier le wikicode]Une classe peut être déclarée au moment de l'instanciation de sa classe parente. On parle alors de classe anonyme. Exemple :
public class ClasseExterne
{
Bissextile b = new Bissextile()
{
public boolean evaluer(int annee)
{
if ((annee%4==0 && annee%100!=0) || annee%400==0)
return true;
else
return false;
}
};
public static void main(String args[])
{
int an = Integer.parseInt(args[0]);
if (b.evaluer(an))
System.out.println("L'année entrée est bissextile");
else
System.out.println("L'année entrée n'est pas bissextile");
}
}
La classe Bissextile est ici la classe mère d'une classe anonyme. Cette classe sera automatiquement nommée par le compilateur ClasseExterne$1
. Il convient d'observer le point-virgule qui suit l'accolade fermante de sa déclaration.
Cette méthode de création de sous-classe peut également s'appliquer aux interfaces. En utilisant la syntaxe suivante, la classe anonyme créée implémente l'interface spécifiée :
new Interface() { ...implémentation des méthodes de l'interface... }
Remarques :
- Une classe anonyme est implicitement considérée comme final (et ne peut donc pas être abstract, abstraite).
- Une classe anonyme ne peut pas contenir de constructeur. Le constructeur appelé est celui qui correspond à l'instruction d'instanciation. Toutefois, elle peut avoir des blocs d'initialisation.
- Une classe anonyme ne possède pas d'identificateur et ne peut donc être instanciée qu'une seule fois (d'où son nom).
Transtypage
Le transtypage (ou cast) est la conversion d'une expression d'un certain type en une expression d'un autre type.
Transtypage implicite
[modifier | modifier le wikicode]On peut affecter à un champ ou une variable d'un type donné une expression de type moins élevé dans la hiérarchie des types. De même, une méthode ou un constructeur attendant un argument d'un type donné peut recevoir en argument effectif une expression de type moins élevé que celui indiqué dans sa déclaration. L'expression fournie sera dans ce cas automatiquement convertie en le type attendu, sans que l'utilisateur ait besoin d'expliciter cette conversion. Toute tentative de conversion implicite d'un type vers un type qui n'est pas plus haut dans la hiérarchie des types déclenchera une erreur au moins à l'exécution, ou dès la compilation si elle est détectable statiquement.
Cas des types primitifs
[modifier | modifier le wikicode]Dans le cas des types primitifs, la hiérarchie est la suivante : byte est plus bas que short, short plus bas que int, char plus bas que int, int plus bas que long, long plus bas que float, float plus bas que double. Le type boolean est incomparable avec les autres types de base. On peut par exemple assigner à une variable de type float une expression de type int égale à 3 : l'expression 3 sera, avant affectation, convertie en float (3.0). Cette forme de conversion est réversible : on peut, après passage de int à float, reconvertir l'expression de type float résultante en int par une conversion explicite (voir ci-dessous) et retrouver la même valeur.
int n;
float f;
n = 3;
f = n; // 3 est converti en 3.0
Le type déterminé statiquement pour les arguments d'opérateurs arithmétiques est le premier type à partir de int dans lequel peuvent être convertis les types de tous les arguments. Les expressions constantes sont d'autre part typées statiquement en le premier type à partir de int permettant leur représentation.
short s;
s = 15; // <-- erreur générée, 15 est typé statiquement de type int
// s = (short) 15; fonctionne (conversion explicite)
s = s + s ; // <-- erreur générée, chaque sous-expression est convertie en int
// et l'expression s + s est typée de type int.
// s = (short) (s + s) fonctionne
int n;
long l = 10L;
n = s; // correct : conversion implicite
n = n + l; // <-- erreur générée, la sous-expression gauche est convertie en long
Typage par suffixe
[modifier | modifier le wikicode]Par défaut les entiers sont typés en Integer
mais un suffixe peut les spécifier Long
:
class Suffixes
{
public static void main(String[] args)
{
Object n = 1;
System.out.println(n.getClass()); // Integer
n = 1L;
System.out.println(n.getClass()); // Long
n = 1.1;
System.out.println(n.getClass()); // Double
n = 1.1F;
System.out.println(n.getClass()); // Float
}
}
Cas des types références
[modifier | modifier le wikicode]La classe d'un objet ne peut évidemment être convertie : durant toute sa durée de vie, il s'agit toujours de la classe dans laquelle est défini le constructeur employé lors de sa création. Le type d'une référence peut en revanche être converti selon les règles suivantes :
- Si A est ancêtre de la classe B, alors toute expression de type "référence vers B" peut être implicitement convertie en le type "référence vers A".
- Si I est une interface implémentée par la classe B, toute expression de type "référence vers B" peut être implicitement convertie en le type "référence vers I".
- Si J est une interface étendant l'interface I, toute expression de type "référence vers J" peut être implicitement convertie en le type "référence vers I".
Dans l'exemple ci-dessous, on fait pointer trois références a, i, j, b, c vers un même objet de classe C :
class A { ... }
interface I { ... }
interface J { ... }
class B extends A implements I { ... } // implémente I, descendante de A
class C extends B implements J { ... } // implémente I et J, descendante de A et B
...
A a; // de type "référence vers A"
I i; // de type "référence vers I"
J j; // de type "référence vers J"
B b; // de type "référence vers B"
C c; // de type "référence vers C"
c = new C(); // l'opérateur new renvoie une référence de type "référence vers C"
// vers l'objet créé, de classe C
// la suite d'affectations suivante est valide
i = c; // C implémente I
j = c; // C implémente J
b = c; // B est ancêtre de C
i = b; // B implémente I
a = b; // A est ancêtre de B
a = c; // A est ancêtre de C
// chaque affectation ci-dessous déclenchera statiquement une erreur
j = b // <-- B n'implémente pas J
i = j // <-- J n'est pas une extension de I
b = a // <-- B n'est pas ancêtre de A
La même règle permet de convertir toute référence en une référence de type Object :
class A extends ... implements ... { ... } // extends Object implicite
...
Object o = new A();
Visibilité des champs et méthodes après transtypage, liaison dynamique
[modifier | modifier le wikicode]Soit r une référence de type "référence vers X", pointant vers un certain objet de classe C. D'après les règles ci-dessus, X est donc soit une classe (concrète ou abstraite) ancêtre de C, soit une interface implémentée par C :
- Les seuls champs accessibles via r sont ceux visibles dans X (déclarés dans X ou hérités, et visibles dans le contexte de r). La valeur liée à r.champ est celle liée à this.champ dans X.
class A {
int x = 0:
int y = 1;
}
class B extends A {
int x = 2; // redéfinition du champ x
int z = 3; // nouveau champ
// le champ y est hérité
}
...
B b = new B();
A a = b;
// les expressions suivantes sont valides
... a.x... // la valeur est celle du champ a de A, soit 0
... b.x... // la valeur est celle du champ a de B, soit 2
... a.y... // valeur 1
... b.y... // y est hérité, valeur 1
... b.z... // valeur 3
// l'expression suivante est invalide
... a.z... // l'objet possède bien un champ z, mais il n'est
// pas visible dans A
- Seules les méthodes dont le nom est visible (par déclaration ou par héritage) dans X sont invocables sur r. L'implémentation exécutée lors d'une invocation de la forme r.méthode(..) sera l'implémentation de la méthode de même nom et de même signature dans la classe de l'objet, et non l'implémentation vue dans X. Le nom de la méthode est dit lié dynamiquement à l'implémentation de cette méthode dans la classe de l'objet.
class A {
void m() {
System.out.println ("implémentation de m dans A");
}
}
class B extends A {
// la méthode m est héritée
// nouvelle méthode :
void n() {
System.out.println ("implémentation de n dans B");
}
}
class C extends B {
// la méthode n est héritée
// redéfinition de m
void m() {
System.out.println ("implémentation de m dans C");
}
}
...
A a = new A();
a.m(); // affiche "implémentation de m dans A"
...
B b = new B();
b.m(); // affiche "implémentation de m dans A" (héritage)
b.n(); // affiche "implémentation de n dans B"
a = b;
a.m(); // affiche "implémentation de m dans A"
a.n(); // <--- erreur : la méthode n n'est pas visible dans A
...
C c = new C();
c.m(); // affiche "implémentation de m dans C"
c.n(); // affiche "implémentation de n dans B" (héritage)
b = c;
b.m(); // affiche "implémentation de m dans C" (liaison dynamique)
b.n(); // affiche "implémentation de n dans B"
a = c;
a.m(); // affiche "implémentation de m dans C" (liaison dynamique)
a.n(); // <--- erreur : la méthode n n'est pas visible dans A
Cas des conversions vers String
[modifier | modifier le wikicode]Toute expression peut être convertie implicitement (ou explicitement) dans le type "référence vers String". Dans le cas où cette expression n'est pas statiquement constante, il y a alors création dynamique d'un objet de classe String représentant la valeur de cette expression, et l'expression résultant devient une référence vers cet objet.
Transtypage explicite
[modifier | modifier le wikicode]Le type d'une expression peut également être explicitement converti avec la syntaxe suivante :
(nouveau_type)expression
Où expression
est l'expression à convertir. S'il s'agit d'une expression composée, il faut l'encadrer par des parenthèses. La conversion explicite d'une expression doit être utilisée à chaque fois que l'on souhaite convertir une expression dans un type qui n'est pas plus haut dans la hiérarchie des types. Dans le cas des types numériques, cette conversion n'est sans pertes que si le type cible permet de représenter la même valeur. Dans le cas contraire, la valeur choisie dépend du type initial et du type cible. Dans le passage de float à int, la valeur choisie est par exemple la valeur entière de la valeur initiale :
int n;
float f;
n = 3;
f = n; // f vaut 3.0
f = f + 1; // conversion de 1 en 1.0 et somme : f vaut 4.0
n = (int) f; // n vaut 4
f = f + 1.5; // f passe à 5.5
n = (int) f; // 5.5 est arrondi en 5 : n vaut 5.
Pour les types de références, la conversion est libre : une référence de type quelconque peut être explicitement convertie en toute référence dont le type permet de manipuler l'objet référencé, selon les règles ci-dessus. La non-validité de cette conversion n'est en général pas détectable avant l'exécution :
interface I { ... }
class A { ... }
class B extends A implements I { ... }
class C { ... }
...
Object o = new B(); // l'objet créé est de classe B
I i = (I) o; // valide : B implémente I
A a = (A) o; // valide : A est ancêtre de B
B b = (B) a; // valide
C c = (C) o; // invalide : C n'est pas ancêtre de B
Ces conversions "descendantes" sont bien sûr propices aux erreurs.
L'opérateur instanceof
permet de vérifier la validité d'une conversion avant de l'effectuer :
if (r instanceof C) {
c = (C) r;
// action sur les instances de C
...
}
else {
// action sur les instances d'une autre classe
...
}
L'opérateur instanceof
et les conversions supportent également les tableaux (à partir d'un objet de type Object
) :
Object r = getObject();
if (r instanceof int[]) {
int[] valeurs = (int[]) r;
...
}
else {
...
}
Autoboxing
[modifier | modifier le wikicode]Java 5 introduit un mécanisme permettant la simplification du transtypage, appelé autoboxing. Ce mécanisme permet d'utiliser indifféremment les types primitifs et les classes wrappers. Exemple :
Avant Java 5, il fallait écrire :
List integers = methodeRenvoyantDesIntegers();
for(int i = 0; i < integers.size(); i++) {
Integer integer = (Integer)integers.get(i);
int actuel = Integer.parseInt(integer);
methodNecessitantUnInt(actuel);
}
Alors qu'avec Java 5, il n'est plus nécessaire de passer par parseInt()
:
List integers = methodeRenvoyantDesIntegers();
for(int i = 0; i < integers.size(); i++) {
int actuel = (Integer)integers.get(i);
methodNecessitantUnInt(actuel);
}
On voit que les int
et les Integer
sont utilisés indifféremment.
Toutefois, il n'est pas possible de déclarer un type générique avec un type primitif. Il faut utiliser la classe englobante correspondante.
Exemple :
ArrayList<Integer> counters = new ArrayList<Integer>();
counters.add(500);
int n = counters.get(0);
Les limites de l'autoboxing est qu'il ne concerne que chaque type primitif et sa classe englobante respective. Par exemple le code suivant renvoie l'erreur inconvertible types
en voulant passer du String au Float :
public static void main(String[] args) {
for(int i = 0; i < args.length; i++) {
System.out.println((Float)args[i]);
}
}
Il faut donc écrire[1] :
public static void main(String[] args) {
for(int i = 0; i < integers.size(); i++) {
System.out.println((Float.valueOf(args[i])).floatValue());
}
Références
[modifier | modifier le wikicode]
Types génériques
Définition
[modifier | modifier le wikicode]Les génériques (de l'anglais generics) sont des classes qui sont typés au moment de la compilation. Autrement dit, ce sont des classes qui utilisent des typages en paramètres. Ainsi une liste chainée, qui peut contenir des entiers, des chaines ou autres, pourra être typée en liste de chaines ou liste d'entiers, et ceci permettra au programmeur de ne pas écrire systématiquement des transtypages, méthode qui pourrait s'avérer dangereuse, ce sera le compilateur qui vérifiera la cohérence des données.
Java 5 a introduit un principe de type générique, rappelant les templates (modèles) du C++, mais le code produit est unique pour tous les objets obtenus à partir de la même classe générique.
Avant Java 5 :
public class MaListe
{
private LinkedList liste;
public setMembre(String s)
{
liste.add(s);
}
public int getMembre(int i)
{
return (Int)liste.get(i);
}
}
Le transtypage est obligatoire, LinkedList manipule des objets Object, ici le compilateur ne peut détecter de problème, problème qui ne surviendra qu'à l'exécution (RunTimeError).
Dans la version avec génériques, on n'a plus besoin d'utiliser le transtypage donc le compilateur déclenchera deux erreurs durant la compilation, une sur la méthode, l'autre sur l'ajout d'un entier.
public class Famille < MaClasse >
{
private LinkedList < MaClasse > liste;
public setMembre(MaClasse m)
{
liste.add(m);
}
public MaClasse getMembre(int i)
{
return liste.get(i);
}
public Integer getInt(int i) //première erreur
{
return liste.get(i);
}
}
Utilisation :
Famille<String> famille = new Famille<String>();
famille.setMembre("essai");
famille.setMembre(210); //seconde erreur
Il est important de comprendre que dans la déclaration de la classe le paramètre placé entre les caractères < et > représente bien une classe qui ne sera déterminée que lors de la déclaration de la création de l'objet. Aussi une erreur de typage sera produite à la compilation si les types utilisés par les méthodes ne sont le ou les types attendus. Dans cet exemple, l'erreur sera signalée sur le second ajout.
Dans la déclaration de la classe, la liste membre est déclarée ne pouvant contenir que des objets de classe MaClasse. L'identifiant MaClasse n'est pas une classe existante dans le packages et il est préférable qu'il ne le soit pas pour qu'aucune confusion ne soit faite, c'est à la déclaration de l'objet Famille que l'identifiant MaClasse sera résolu.
Il est évidemment possible d'utiliser un objet d'une classe héritant de celle utilisée pour paramétrer le type générique. Ceci permet de plus d'assurer la compatibilité ascendante avec les versions antérieures de Java : si aucune classe de paramétrage n'est indiquée, la classe par défaut est java.lang.Object
.
Plusieurs paramètres
[modifier | modifier le wikicode]De la même façon que pour les classes basées sur les List, les déclarations de vos classes peuvent utiliser ces génériques. Cela permet de rendre le code plus souple et surtout réutilisable dans des contextes très différents. Plusieurs paramètres, séparés par des virgules, peuvent être utilisés entre les caractères < et >.
public class ListeDeTruc<Truc, Bidule>
{
private LinkedList <Truc> liste = new LinkedList<>();
private ArrayList <Bidule> tableau = new ArrayList<>();
public void accumule(Truc m)
{
liste.add(m);
}
public Bidule recherche(int i)
{
return tableau.get(i);
}
}
Déclaration possible :
ListeDeTruc<String,Integer> liste1 = new ListeDeTruc<String,Integer>();
ListeDeTruc<Thread,Date> liste2 = new ListeDeTruc<Thread,Date>();
Paramètres non spécifiés à l'initialisation
[modifier | modifier le wikicode]Dans la classe de la section précédente, la création des deux collections avec l'opérateur new
s'effectue avec une liste de paramètres génériques vide <>
juste après le nom des classes génériques :
private LinkedList <Truc> liste = new LinkedList<>();
private ArrayList <Bidule> tableau = new ArrayList<>();
Cette syntaxe (supportée depuis Java 8) indique que les paramètres sont les mêmes que ceux de la déclaration des membres. Cela équivaut à ceci :
private LinkedList <Truc> liste = new LinkedList<Truc>();
private ArrayList <Bidule> tableau = new ArrayList<Bidule>();
Les paramètres ne sont cependant pas toujours identiques à ceux de la déclaration : il est possible par exemple d'utiliser des sous-classes.
Génériques et héritages
[modifier | modifier le wikicode]Lorsqu'un type de base doit répondre à des spécifications précises, il est possible d'écrire des choses du genre :
public class ListeDeTruc<Truc extends Bidule, MaList<String>> implements Moninterface<Chose>
En revanche, créer une classe qui hérite de ces objets est plus délicat. Ici Chose et Bidule sont des classes existantes, Truc ne sera résolu qu'au moment de la déclaration de l'objet ListeDeTruc.
L'utilisation du mot clef super est possible dans une classe héritant d'une classe générique.
Tableau de génériques
[modifier | modifier le wikicode]La déclaration d'un tableau d'objets dont le type est générique peut se faire sans déclencher ni erreur, ni avertissements et sans utiliser l'annotation @SuppressWarnings("unchecked")
, en utilisant <?>
:
ArrayList<?>[] namelists = new ArrayList<?>[5];
Compatibilité entre les types
[modifier | modifier le wikicode]La relation d'héritage entre classe pour le type générique ne rend pas les classes compatibles.
La compatibilité est illustré ci-dessous, où la classe List
hérite de la classe Collection
et la classe Cat
hérite de la classe Animal
:
Exemple :
class Animal { /* ... */ }
class Cat extends Animal { /* ... */ }
List<Cat> les_chats_du_voisinage = /* ... */;
List<? extends Cat> list_1 = les_chats_du_voisinage; // OK
Collection<Cat> list_2 = les_chats_du_voisinage; // OK
Collection<? extends Animal> list_3 = les_chats_du_voisinage; // OK
List<Animal> list_4 = les_chats_du_voisinage; // ERROR
Conventions sur les noms des types
[modifier | modifier le wikicode]Bien qu'il soit tout à fait possible d'utiliser n'importe-quel identifiant suivant la convention de nommage des classes, il est plutôt recommandé d'utiliser un identifiant composé d'une seule lettre selon la convention[1] :
<E>
- « Element », utilisé abondamment pour le type des éléments d'une collection ;
<K>
et<V>
- « Key » et « Value », pour respectivement le type des clés et celui des valeurs d'une Map ou similaires ;
<N>
- « Number » ;
<T>
- « Type » ;
<S>
,<U>
,<V>
etc.- second, troisième, nème type.
Limitations
[modifier | modifier le wikicode]Malgré la similitude de syntaxe, les types génériques en Java sont différents des patrons (templates en anglais) du C++. Il faut plutôt les voir comme un moyen d'éviter de faire une conversion entre java.lang.Object et le type spécifié de manière implicite.
Parmi les limitations :
- il n'est pas possible d'implémenter plusieurs fois la même interface avec des paramètres différents,
- il n'est pas possible de créer deux versions surchargées d'une méthode (deux méthodes portant le même nom) l'une utilisant la classe Object et l'autre utilisant un type générique.
Références
[modifier | modifier le wikicode]
Instanciation et cycle de vie
Toute variable de type objet est une référence vers une instance de classe allouée en mémoire sur le tas.
La référence particulière null
pointe vers une instance non allouée, et constitue la valeur par défaut des références.
Il faut donc allouer une instance et l'assigner à une variable de type référence avant de pouvoir utiliser un objet et ses membres par l'opérateur de déréférencement point : objet.membre
.
L'opérateur de déréférencement sur une référence nulle déclenche une exception de type NullPointerException
.
MaClasse objet = null;
objet.toString(); // ---> NullPointerException
Un tableau est également un objet, pouvant stocker un nombre fixe d'éléments spécifié à l'instruction new
.
int[] tableau = null;
tableau[0] = 1; // ---> NullPointerException
L'instruction new
[modifier | modifier le wikicode]L'instruction new
permet d'instancier une classe en utilisant l'un des constructeurs de la classe.
Par exemple pour créer un objet de type MaClasse, on écrit :
MaClasse cl = new MaClasse("hello");
Cette instruction sert également à allouer les tableaux en spécifiant le nombre d'éléments entre crochets après leur type.
int[] entiers = new int[15]; // Alloue un tableau de 15 entiers initialisés à 0.
MaClasse[] mes_objets = new MaClasse[20]; // Alloue un tableau de 20 références de type MaClasse initialisées à null.
Les constructeurs
[modifier | modifier le wikicode]Un constructeur est une méthode particulière de la classe appelée lors de la création d'une instance de la classe. Son rôle est d'initialiser les membres de l'objet juste créé. Un constructeur a le même nom que sa classe et n'a pas de valeur de retour.
Dans l'exemple suivant la classe MaClasse dispose de deux constructeurs, l'un ne prenant aucun paramètre et l'autre prenant un paramètre de type String :
public class MaClasse
{
// Attributs
private String name;
// Constructeurs
public MaClasse()
{
name = "defaut";
}
public MaClasse(String s)
{
name = s;
}
}
Toute classe possède au moins un constructeur. Cependant, il n'est pas obligatoire de déclarer un constructeur pour une classe. En effet, si aucun constructeur n'est déclaré dans une classe, un constructeur sans paramètre est ajouté de manière implicite. Celui-ci ne fait rien, en apparence.
Un constructeur d'objet en appelle toujours un autre :
- Soit explicitement un autre constructeur de la même classe en utilisant
this
, - Soit explicitement un constructeur de la classe mère en utilisant
super
, - Soit implicitement le constructeur sans arguments de la classe mère.
Ces deux derniers points ne sont pas applicables à la classe java.lang.Object
racine de toutes les autres classes.
Le code ci-dessous est strictement équivalent à l'exemple précédent, avec les appels implicites mis explicitement
public class MaClasse extends Object /* Implicite */
{
// Attributs
private String name;
// Constructeurs
public MaClasse()
{
super(); /* Implicite */
name = "defaut";
}
public MaClasse(String s)
{
super(); /* Implicite */
name = s;
}
}
Quand une classe a plusieurs constructeurs qui définissent des valeurs par défaut, il vaut mieux que chaque constructeur appelle un autre constructeur de la classe. Cela permet de faciliter la maintenance en centralisant le comportement de construction et la définition des valeurs par défaut en un seul endroit du code.
public class MaClasse
{
// Attributs
private String name;
private String description;
// Constructeurs
public MaClasse()
{
this("Pas de nom");
}
public MaClasse(String name)
{
this(name, "Pas de description");
}
public MaClasse(String name, String description)
{
// super(); /* Implicite */
this.name = name;
this.description = description;
}
}
Initialisation d'un objet
[modifier | modifier le wikicode]L'appel à l'instruction new
crée un nouvel objet de la classe spécifiée initialisé de la façon suivante :
- Juste après l'allocation mémoire, tous les membres sont initialisés à leur valeur par défaut dépendant du type (
null
pour les références,false
pour les booléens, et0
pour les types numériques). - Le constructeur nommé en interne
<init>
correspondant aux types des arguments de l'instructionnew
est appelé.
Le code de chaque constructeur <init>
est constitué de plusieurs parties concaténées :
- l'appel explicite au constructeur de la classe de base dans le fichier source, ou un appel implicite au constructeur par défaut (sans arguments) ;
- le code commun de construction définit par les initialisations de membres et les blocs de code non précédés du mot
static
- et le code définit explicitement dans le fichier source.
Exemple
[modifier | modifier le wikicode]public class MaClasse extends ClasseDeBase
{
// Attributs : un nom et 4 lignes de description
private String name;
private DescriptionLine[] description = new String[4]; // (1)
{
description[0] = new DescriptionLine("Exemple d'objet"); // (2)
}
private DescriptionLine firstline = description[0]; // (3)
// Constructeur
public MaClasse(String name)
{
super("Exemple"); // (0)
this.name = name; // (4)
}
}
Le code du constructeur <init>(String name)
contient alors cette séquence générée par le compilateur :
super("Exemple"); // (0)
description = new String[4]; // (1)
description[0] = new DescriptionLine("Exemple d'objet"); // (2)
firstline = description[0]; // (3)
this.name = name; // (4)
Les lignes d'initialisation (de (1) à (3) pour l'exemple précédent) sont donc insérées juste après l'appel au constructeur de la classe de base, dans tous les constructeurs de la classe.
L'ordre des initialisations est très important.
Dans l'exemple précédent, si les lignes (1) et (3) sont interverties, une exception de type NullPointerException
est lancée lors de la construction de l'objet.
Le bloc d’initialisation ne peut pas envoyer explicitement une exception, même s'il s'agit d'une sous-classe de RuntimeException
.
Dans le cas contraire, le compilateur retourne l'erreur Initializer does not complete normally
.
public class MaClasseNonImplementee extends ClasseDeBase
{
{
throw new RuntimeException("Non implémentée");
// Erreur à la compilation : Initializer does not complete normally
}
}
Par contre le constructeur peut en lancer :
public class MaClasseNonImplementee extends ClasseDeBase
{
public MaClasseNonImplementee()
{
throw new RuntimeException("Non implémentée");
}
}
Initialisation d'une classe
[modifier | modifier le wikicode]Avant la première instanciation, la classe est initialisée par la méthode statique spéciale nommée <clinit>
.
Le code de cette méthode est construit par le compilateur en concaténant les instructions d'initialisations des membres statiques et des blocs de code précédés du mot static
.
Il s'agit donc du même principe que les constructeurs <init>
vu dans la section précédente mais appliqué aux membres statiques de la classe.
Toutefois aucun code de constructeur n'est ajouté à la fin (pas de constructeur statique), et le code appelle la méthode statique spéciale <clinit>
de la classe de base plutôt qu'un constructeur.
Nettoyage
[modifier | modifier le wikicode]Ramasse-miettes (Garbage Collector)
[modifier | modifier le wikicode]Le ramasse-miettes garde un compteur du nombre de références pour chaque objet. Dès qu'un objet n'est plus référencé, celui-ci est marqué. Lorsque le ramasse-miettes s'exécute (en général quand l'application ne fait rien), les objets marqués sont libérés.
Son exécution se produit toujours à un moment qui ne peut être déterminé à l'avance. Il s'exécute lors des évènements suivants :
- périodiquement, si le processeur n'est pas occupé,
- quand la quantité de mémoire restante est insuffisante pour allouer un nouveau bloc de mémoire.
Il est donc recommandé de libérer toute référence (affecter null
) à des objets encombrants (tableaux de grande taille, collection d'objets, ...) dès que possible, ou au plus tard juste avant l'allocation d'une grande quantité de mémoire.
Exemple : Pour le code suivant, il faut 49 152 octets disponibles, le ramasse-miettes ne pouvant rien libérer durant l'allocation du deuxième tableau.
byte[] buffer = new byte[16384];
// -> 1. allocation de 16384 octets
// 2. affecter la référence à la variable buffer
// ...
buffer = new byte[32768];
// -> 1. allocation de 32768 octets
// 2. affecter la référence à la variable buffer
Une fois le code corrigé, il ne faut plus que 32 768 octets disponibles, le ramasse-miettes pouvant libérer le premier tableau avant d'allouer le deuxième.
byte[] buffer = new byte[16384];
// -> 1. allocation de 16384 octets
// 2. affecter la référence à la variable buffer
// ...
buffer = null; // Le ramasse-miettes peut libérer les 16384 octets du tableau si besoin.
buffer = new byte[32768];
// -> 1. allocation de 32768 octets
// 2. affecter la référence à la variable buffer
Mettre à null
les références devenues inutiles au plus tôt permet donc de réduire la quantité de mémoire libre nécessaire à l'allocation de tableaux ou d'objets.
Finalisation
[modifier | modifier le wikicode]Lors de la libération des objets par le ramasse-miettes, celui-ci appelle la méthode finalize()
afin que l'objet libère les ressources qu'il utilise.
Cette méthode peut être redéfinie afin de libérer des ressources système non Java. Dans ce cas, la méthode doit se terminer en appelant la méthode de la classe parente :
super.finalize();
Comme l'exécution du ramasse-miettes se produit toujours à un moment qui ne peut être déterminé à l'avance, l'appel à cette méthode par le ramasse-miettes n'est pas garanti. C'est pourquoi cette méthode est rarement implémentée afin d'éviter des effets de bords.
Représentation des objets dans la mémoire
[modifier | modifier le wikicode]L'instruction new
alloue la mémoire sur le tas pour les objets et les tableaux.
La création d'un objet de classe C qui hérite de la classe B, elle-même héritant de la classe A, se déroule à peu près de la manière suivante :
- Pour chacune des classes de la hiérarchie en partant de la classe racine (ordre A, B, C pour l'exemple), si cela n'a pas déjà été fait auparavant :
- Charger la classe (Class, code des méthodes, ...), en lui allouant également l'espace nécessaire pour ses variables membres statiques.
- Initialiser les variables membres statiques de la classe en appelant la méthode spéciale
<clinit>
qui contient la concaténation des assignations des membres statiques et des blocs de code statiques.
- Allocation de l'espace mémoire pour les membres d'instance de la classe C incluant les membres déclarés ainsi que ceux hérités des classes B et A.
- Appel au constructeur (méthode spéciale
<init>
) de la classe C correspondant aux arguments fournis en lui passant la référence à la zone mémoire allouée dansthis
:- Sa première instruction est un appel explicite ou implicite à un constructeur de la classe B (lui-même appelant un constructeur de la classe A).
- Les instructions suivantes sont la concaténation des assignations des membres d'instance et des blocs de code non statiques.
- Enfin, les instructions définies explicitement dans le constructeur sont appelées.
La création d'un tableau avec l'instruction new
se déroule de la même façon (sachant que tout type de tableau hérite de la classe java.lang.Object
), en allouant la place nécessaire pour le type des éléments multipliée par le nombre d'éléments voulu.
Le tas et la pile
[modifier | modifier le wikicode]Le tas est la zone mémoire où sont alloués les éléments de tableaux et les membres des objets. C'est une zone mémoire très grande, partagée par tous les processus légers (threads) de l'application Java. C'est pourquoi l'accès à un même objet ou tableau par plusieurs threads nécessite des moyens de synchronisation.
La pile est une zone mémoire locale limitée, alloué pour chaque processus léger (thread). Cette zone sert de stockage temporaire aux appels de méthodes pour y stocker la valeur des arguments, des variables locales, et de manière interne la valeur de retour.
public class Test
{
int a;
int b = 50;
String c;
String d = "test";
// Code inclus dans tous les constructeurs :
{
c = d.toUpperCase();
System.out.println("C = "+c);
}
public static Object creerTest()
{
Object o; // Une référence nulle, allouée sur la pile
o = new Test(); // Objet alloué sur le tas
return o; // La méthode retourne la référence à l'objet alloué dans le tas
}
}
this
Le mot-clé this désigne, dans une classe, l'instance courante de la classe elle-même. Il est utilisé à différentes fins décrites dans les sections suivantes.
Rendre univoque
[modifier | modifier le wikicode]Il peut être utilisé pour rendre le code explicite et non ambigu.
Par exemple, si dans une méthode, on a un paramètre ayant le même nom qu'un attribut de la classe dont la méthode fait partie, on peut désigner explicitement l'attribut grâce à this :
public class Calculateur
{
protected int valeur;
public void calcule(int valeur)
{
this.valeur = this.valeur + valeur;
}
}
Dans cet exemple, la méthode calcule additionne le paramètre valeur à l'attribut valeur et stocke le résultat dans l'attribut valeur de l'objet. L'attribut a été désigné explicitement par le mot-clé this, désignant l'instance de la classe, préfixé au nom de l'attribut.
S'auto-désigner comme référence
[modifier | modifier le wikicode]Le mot-clé this
peut être utilisé pour passer une référence à l'instance elle-même comme paramètre d'une méthode.
Par exemple : s'enregistrer comme écouteur d'évènement :
source.addListener(this);
Désigner l'instance de la classe qui encadre
[modifier | modifier le wikicode]Dans le cas de classes imbriquées, c'est-à-dire qu'une classe interne utilise l'instance de la classe externe, le mot-clé this
préfixé du nom de la classe externe permet de désigner l'instance de la classe externe. S'il n'est pas préfixé, il désigne l'instance de la classe interne.
Exemple :
public class Livre
{
String titre = "Le livre";
class Chapitre
{
String titre = "Chapitre 1";
public String toString()
{
return
Livre.this.titre + // "Le livre"
"/" +
this.titre ; // "Chapitre 1"
// ou Chapitre.this.titre ; // "Chapitre 1"
}
// -> "Le livre/Chapitre 1"
}
}
Appeler un autre constructeur de la classe
[modifier | modifier le wikicode]Un constructeur peut appeler un autre constructeur de la classe en utilisant le mot-clé this
comme nom appelé.
Il doit s'agir de la première instruction du constructeur.
Ce genre d'appel est utile pour définir des constructeurs alternatifs avec moins de paramètres, pour utiliser des valeurs par défaut.
Exemple :
public class Livre
{
String titre, auteur, editeur;
int pages, annee;
public Livre(String titre)
{
this(titre, "Moi", "fr.wikibooks.org");
}
public Livre(String titre, String auteur, String editeur)
{
this(titre, auteur, editeur, 800, 2021);
}
public Livre(String titre, String auteur, String editeur, int pages, int annee)
{
this.titre = titre;
this.auteur = auteur;
this.editeur = editeur;
this.pages = pages;
this.annee = annee;
}
}
Objets comparables et clés
Certaines collections d'objets nécessitent l'utilisation de classes dont les instances sont comparables pour pouvoir trier la collection. De plus, pour utiliser une instance d'une classe comme clé dans les tables associatives, la classe doit posséder des propriétés supplémentaires.
Objets comparables
[modifier | modifier le wikicode]Pour utiliser un objet dans une collection triée, on peut :
- soit utiliser des éléments instances d'une classe comparable,
- soit spécifier un comparateur d'éléments au constructeur de la collection.
interface Comparable
[modifier | modifier le wikicode]Une classe implémente l'interface java.lang.Comparable
en ajoutant la méthode suivante :
int compareTo(Object o)
ou (Java 5+) interface Comparable<T>
:
int compareTo(T o)
Dans cette méthode l'objet this
est comparé à l'objet o
. Cette méthode doit retourner le signe de la soustraction virtuelle this - o
. C'est à dire :
- -1 si
this < o
- 0 si
this == o
- +1 si
this > o
Cette méthode doit avoir les propriétés suivantes, pour toutes instances de la classe nommées o1
, o2
et o3
:
o1.compareTo(o1) == 0
,o1.compareTo(o2) == - o2.compareTo(o1)
,o1.compareTo(o2) > 0
eto2.compareTo(o3) > 0
impliqueo1.compareTo(o3) > 0
.
De plus, pour utiliser une instance de la classe dans une collection, le comportement de cette méthode doit être consistant avec celle de la méthode equals
:
- Pour toute instance de la classe nommés
o1
eto2
, les deux expressions booléenneso1.compareTo(o2)==0
eto1.equals(o2)
retournent la même valeur, - La comparaison avec
null
doit lancer une exceptionNullPointerException
.
Comparateur (Comparator
)
[modifier | modifier le wikicode]L'interface java.util.Comparator
permet de rendre comparable des instances dont la classe n'implémente pas elle-même l'interface java.lang.Comparable
vue précédemment.
Une classe implémentant cette interface doit déclarer deux méthodes :
int compare(Object o1, Object o2)
int equals(Object o)
ou (Java 5+) interface Comparator<T>
:
int compare(T o1, T o2)
int equals(Object o)
La méthode equals
compare les comparateurs (eux-mêmes) this
et o
.
La méthode compare
compare les deux objets spécifiés et retourne le signe de la soustraction virtuelle o1 - o2
, comme expliqué dans la section précédente. C'est-à-dire :
- -1 si
o1 < o2
- 0 si
o1 == o2
- +1 si
o1 > o2
Clé de table associative
[modifier | modifier le wikicode]Pour utiliser une instance de classe comme clé d'une table associative (Map en anglais), la classe doit posséder les propriétés suivantes :
- La classe doit posséder une méthode
equals
cohérente, - La classe doit posséder une méthode
hashCode
cohérente.
Dans le cas contraire (par défaut), il est toujours possible d'utiliser des instances de la classe comme clé, mais il faudra utiliser cette instance seulement. C'est à dire que pour retrouver une valeur dans une table associative, il ne sera pas possible d'utiliser une autre instance dont les attributs sont égaux à ceux de l'instance utilisée pour réaliser l'association. Une énumération ayant des instances prédéterminées et ne pouvant avoir d'autres instances convient donc pour être utilisée comme clé.
Exemple : un tableau d'entiers utilisé comme clé.
int[] key1 = { 1, 2 };
int[] key2 = { 1, 2 }; // même contenu que key1
HashMap hmap = new HashMap();
// association key1 -> chaîne de caractère
hmap.put(key1, "Valeur pour la suite (1,2)");
// tentative pour retrouver la valeur
String s1 = (String)hmap.get(key1); // retourne "Valeur pour la suite (1,2)"
// tentative pour retrouver la valeur
String s2 = (String)hmap.get(key2); // retourne null
La tentative échoue car un tableau n'a pas toutes les propriétés nécessaires. La méthode equals
est correcte, mais la méthode hashCode
retourne deux valeurs différentes.
Méthode equals
[modifier | modifier le wikicode]Une classe doit implémenter la méthode equals
de manière cohérente, c’est-à-dire, pour toutes instances de la classe nommées o1
, o2
et o3
:
o1.equals(o1) == true
,o1.equals(o2) == o2.equals(o1)
,o1.equals(o2)
eto2.equals(o3)
impliqueo1.equals(o3)
.
Méthode hashCode
[modifier | modifier le wikicode]Pour utiliser une classe comme clé d'une table associative, il faut que la classe implémente la méthode hashCode()
de manière cohérente. Pour comprendre cela, il faut aborder le fonctionnement interne des tables associatives indexée par des objets :
- Les méthodes de la table associative appellent la méthode
hashCode()
de la clé pour obtenir un entier, - Cet entier est transformé en index dans un tableau par reste de la division par la taille du tableau,
- Ce tableau contient une liste de clés comparées avec celle fournie à la méthode appelée en utilisant la méthode
equals
vue auparavant.
Il est donc essentiel que la méthode hashCode
ait les propriétés suivantes :
- Cette méthode doit toujours retourner la même valeur si l'objet n'a pas été modifié,
- Pour deux objets égaux (méthode
equals
retournetrue
), la méthodehashCode
doit retourner deux valeurs égales. C'est à dire, pour toute instance de la classe nomméso1
eto2
,o1.equals(o2)
impliqueo1.hashCode() == o2.hashCode()
, - Il est recommandé que pour deux objets différents, la méthode
hashCode
retourne deux valeurs distinctes.
La méthode par défaut retourne la même valeur que la méthode System.identityHashCode(this)
qui se base sur la référence de l'objet (0 si la référence est null
).
C'est à dire que deux objets différents mais avec des contenus identiques auront tout de même une valeur différente.
Objets utilisables comme clé
[modifier | modifier le wikicode]Comme expliqué dans la section d'introduction, il est possible d'utiliser n'importe quel type d'objet à condition d'utiliser exactement la même instance, ou bien que la classe possède des méthodes equals
et hashCode
cohérentes, comme la classe java.lang.String
par exemple.
String key1 = "Clé de test";
String key2 = "Clé" + " de " + "test";
HashMap hmap = new HashMap();
// association key1 -> chaîne de caractère
hmap.put(key1, "Valeur pour la clé");
// tentative pour retrouver la valeur
String s1 = (String)hmap.get(key1); // retourne "Valeur pour la clé"
// tentative pour retrouver la valeur
String s2 = (String)hmap.get(key2); // retourne "Valeur pour la clé"
Énumérations
Java 5 introduit une nouvelle structure de données appelée énumérations. Cette structure permet de contenir une série de données constantes ayant un type sûr, ce qui veut dire que ni le type, ni la valeur réelle de chaque constante n'est précisé. Il est possible de comparer des valeurs d'une énumération entre elles à l'aide des opérateurs de comparaison et de l'instruction switch
.
Exemple :
enum Animal { KANGOUROU, TIGRE, CHIEN, SERPENT, CHAT };
class Test
{
public static void main(String[] args)
{
String aniMsg;
Animal bebete = Animal.TIGRE;
switch(bebete)
{
case KANGOUROU :
aniMsg = "kangourou";
break;
case TIGRE :
aniMsg = "tigre";
break;
case CHIEN :
aniMsg = "chien";
break;
case SERPENT :
aniMsg = "serpent";
break;
case CHAT :
aniMsg = "chat";
break;
}
System.out.println("L'animal est un "+aniMsg);
}
}
Membres
[modifier | modifier le wikicode]Les énumérations sont en fait compilées sous forme de classes, éventuellement internes.
Une énumération peut donc avoir des constructeurs et méthodes. Ses constructeurs sont obligatoirement privés car aucune nouvelle instance ne peut être créée.
enum Animal
{
// Il faut appeler l'un des constructeurs déclarés :
KANGOUROU("kangourou", false),
TIGRE("tigre", false),
CHIEN("chien", true),
SERPENT("serpent", false, "tropical"),
CHAT("chat", true); // <- NB: le point-virgule pour mettre fin à la liste des constantes !
// Membres :
private final String environnement;
private final String nom;
private final boolean domestique;
Animal(String nom, boolean domestique)
{ this(nom, domestique, null); }
Animal(String nom, boolean domestique, String environnement)
{
this.nom = nom;
this.domestique = domestique;
this.environnement = environnement;
}
public String getNom(){ return this.nom; }
public String getEnvironnement(){ return this.environnement; }
public boolean isDomestique(){ return this.domestique; }
};
class Test
{
public static void main(String[] args)
{
Animal bebete = Animal.TIGRE;
System.out.print("L'animal est un "+bebete.getNom());
System.out.print(bebete.isDomestique()?" (domestique)":" (sauvage)");
String env = bebete.getEnvironnement();
if (env!=null)
System.out.print(" vivant dans un milieu "+env);
System.out.println();
}
}
Héritage
[modifier | modifier le wikicode]Les énumérations sont compilées sous forme de classes finales, ce qui signifie qu'aucune classe ou énumération ne peut en hériter.
Méthodes utiles
[modifier | modifier le wikicode]Les énumérations possèdent des méthodes communes permettant notamment la conversion entre un membre de l'énumération et un entier ou une chaîne de caractères.
ordinal()
- Obtenir l'index de la valeur selon l'ordre de déclaration (premier = 0).
- Exemple :
Animal.CHIEN.ordinal() /* -> 2 */
name()
- Obtenir le nom de la valeur.
- Exemple :
Animal.CHAT.name() /* -> "CHAT" */
valueOf(String s)
- (méthode statique) Obtenir la valeur dont le nom est spécifié en paramètre.
- Exemple :
Animal.valueOf("CHAT") /* -> Animal.CHAT */
values()
- (méthode statique) Obtenir un tableau contenant toutes les valeurs déclarées.
- Exemple :
Animal.values()[2] /* -> Animal.CHIEN */
Les énumérations implémentent l'interface java.lang.Comparable
et possède donc les méthodes equals
et compare
pour comparer deux valeurs (ordonnées selon ordinal()
par défaut).
Ensemble de valeurs énumérées
[modifier | modifier le wikicode]Les ensembles (set en anglais) font partie des classes de collection de l'API Java.
Il existe une classe spécifique pour les ensembles de valeurs énumérées nommée EnumSet
.
Cette classe hérite de la classe abstraite java.util.AbstractSet
.
Voir les ensembles.
Utilisation comme clé de table associative
[modifier | modifier le wikicode]Étant donné qu'une énumération ne peut avoir d'autres instances que celles déclarées, elle convient bien comme type de clé dans les tables associatives, comme les collections de classe Map
.
Exemple :
public void dialogueAnimal()
{
Map<Animal,String> cri_par_animal = new HashMap<>();
cri_par_animal.put(Animal.TIGRE, "Grr");
cri_par_animal.put(Animal.CHAT, "Miaou");
Animal a = Animal.TIGRE, b = Animal.CHAT;
System.out.println("Le "+a+" dit « "+cri_par_animal.get(a)+" ! »");
System.out.println("Le "+b+" répond « "+cri_par_animal.get(b)+" ! »");
}
Exceptions
Une exception est un signal qui se déclenche en cas de problème. Les exceptions permettent de gérer les cas d'erreur et de rétablir une situation stable (ce qui veut dire, dans certains cas, quitter l'application proprement). La gestion des exceptions se décompose en deux phases :
- La levée d'exceptions,
- Le traitement d'exceptions.
En Java, une exception est représentée par une classe. Toutes les exceptions dérivent de la classe Exception qui dérive de la classe Throwable.
Levée d'exception
[modifier | modifier le wikicode]Une exception est levée grâce à l'instruction throw :
if (k<0)
throw new Exception("k négatif");
Une exception peut être traitée directement par la méthode dans laquelle elle est levée dans un bloc catch
, ou bien sans traitement dans la méthode, elle est envoyée à la méthode appelante auquel cas l'instruction throws (à ne pas confondre avec throw) doit être employée pour indiquer les exceptions non traitées :
import java.io.IOException;
public void maMethode(int entier) throws IOException
{
// code de la méthode
}
Dans cet exemple, si une exception de type IOException non traitée est levée durant l'exécution de maMethode, l'exception sera envoyée à la méthode appelant maMethode, qui devra la traiter.
Certaines exceptions sont levées implicitement par la machine virtuelle :
NullPointerException
quand une référence nulle est déréférencée (accès à un membre),ArrayIndexOutOfBoundsException
quand l'indice d'un tableau dépasse sa capacité,ArithmeticException
quand une division par zéro ou une autre erreur arithmétique a lieu.
Celles-ci n'ont pas besoin d'être déclarées avec l'instruction throws car elles dérivent de la classe RuntimeException
, une classe d'exceptions qui ne sont pas censées être lancées par une méthode codée et utilisée correctement.
Traitement d'exception
[modifier | modifier le wikicode]Le traitement des exceptions se fait à l'aide de la séquence d'instructions try...catch...finally.
- L'instruction try indique qu'une instruction (ou plus généralement un bloc d'instructions) susceptible de lever des exceptions débute.
- L'instruction catch indique le traitement pour un type particulier d'exceptions. Il peut y avoir plusieurs instructions catch pour une même instruction try.
- L'instruction finally, qui est optionnelle, sert à définir un bloc de code à exécuter dans tous les cas, exception levée ou non.
Il faut au moins une instruction catch ou finally pour chaque instruction try.
Exemple :
public String lire(String nomDeFichier) throws IOException
{
try
{
// La ligne suivante est susceptible de lever une exception
// de type FileNoFoundException
FileReader lecteur = new FileReader(nomDeFichier);
char[] buf = new char[100];
// Cette ligne est susceptible de lever une exception
// de type IOException
lecteur.read(buf,0,100);
return new String(buf);
}
catch (FileNotFoundException fnfe)
{
fnfe.printStackTrace(); // Indique l'exception sur le flux d'erreur standard
}
finally
{
System.err.println("Fin de méthode");
}
}
Le bloc catch (FileNotFoundException fnfe)
capture toute exception du type FileNotFoundException
(cette classe dérive de la classe IOException
).
Le bloc finally
est exécuté quel que soit ce qui se passe (exception ou non).
Toute autre exception non capturée (telle IOException
) est transmise à la méthode appelante, et doit toujours être déclarée pour la méthode, en utilisant le mot clé throws
, sauf les exceptions dérivant de la classe RuntimeException
.
S'il n'y avait pas cette exception à la règle, il faudrait déclarer throws ArrayIndexOutOfBoundsException
chaque fois qu'une méthode utilise un tableau, ou throws ArithmeticException
chaque fois qu'une expression est utilisée, par exemple.
Ne jamais ignorer une exception
[modifier | modifier le wikicode]Il est tentant de vouloir ignorer une ou des exceptions en la capturant pour ne faire aucun traitement et en poursuivant l'exécution, comme dans cet exemple tiré des fichiers sources de Java :
if (formatName != null) {
try {
Node root = inData.getAsTree(formatName);
outData.mergeTree(formatName, root);
} catch(IIOInvalidTreeException e) {
// ignore
}
}
Cependant, c'est une très mauvaise pratique, car l'exception indique que les données d'entrée comportent une erreur mais l'utilisateur ou le développeur n'est pas informé. La poursuite de l'exécution aboutira alors à la production d'un résultat mauvais dont l'origine sera très difficile à remonter.
Il vaut mieux :
- retirer le bloc catch et permettre de remonter l'exception à un niveau plus haut,
- ou retransmettre l'exception avec un type plus précis indiquant le problème de manière plus détaillée.
Remonter l'exception permet un traitement approprié (correction de code, message d'erreur à l'utilisateur pour qu'il corrige les valeurs d'entrées, essayer une autre solution, ...).
Classes et sous-classes d'exception
[modifier | modifier le wikicode]L'héritage entre les classes d'exceptions peut conduire à des erreurs de programmation. En effet, une instance d'une sous-classe est également considérée comme une instance de la classe de base.
Ordre des blocs catch
[modifier | modifier le wikicode]L'ordre des blocs catch
est important : il faut placer les sous-classes avant leur classe de base. Dans le cas contraire le compilateur génère l'erreur exception classe_exception has already been caught
.
Exemple d'ordre incorrect :
try
{
FileReader lecteur = new FileReader(nomDeFichier);
}
catch(IOException ioex) // capture IOException et ses sous-classes
{
System.err.println("IOException capturée :");
ioex.printStackTrace();
}
catch(FileNotFoundException fnfex) // <-- erreur ici
// FileNotFoundException déjà capturé par catch(IOException ioex)
{
System.err.println("FileNotFoundException capturée :");
fnfex.printStackTrace();
}
L'ordre correct est le suivant :
try
{
FileReader lecteur = new FileReader(nomDeFichier);
}
catch(FileNotFoundException fnfex)
{
System.err.println("FileNotFoundException capturée :");
fnfex.printStackTrace();
}
catch(IOException ioex) // capture IOException et ses autres sous-classes
{
System.err.println("IOException capturée :");
ioex.printStackTrace();
}
Sous-classes et clause throws
[modifier | modifier le wikicode]Une autre source de problèmes avec les sous-classes d'exception est la clause throws
. Ce problème n'est pas détecté à la compilation.
Exemple :
public String lire(String nomDeFichier) throws FileNotFoundException
{
try
{
FileReader lecteur = new FileReader(nomDeFichier);
char[] buf = new char[100];
lecteur.read(buf,0,100);
return new String(buf);
}
catch (IOException ioe) // capture IOException et ses sous-classes
{
ioe.printStackTrace();
}
}
Cette méthode ne lancera jamais d'exception de type FileNotFoundException
car cette sous-classe de IOException
est déjà capturée.
Relancer une exception
[modifier | modifier le wikicode]Une exception peut être partiellement traitée, puis relancée. On peut aussi relancer une exception d'un autre type, cette dernière ayant l'exception originale comme cause.
Dans le cas où l'exception est partiellement traitée avant propagation, la relancer consiste simplement à utiliser l'instruction throw
avec l'objet exception que l'on a capturé.
Exemple:
public String lire(String nomDeFichier) throws IOException
{
try
{
FileReader lecteur = new FileReader(nomDeFichier);
char[] buf = new char[100];
lecteur.read(buf,0,100);
return new String(buf);
}
catch (IOException ioException) // capture IOException et ses sous-classes
{
// ... traitement partiel de l'exception ...
throw ioException; //<-- relance l'exception
}
}
Une exception d'un autre type peut être levée, par exemple pour ne pas propager une exception de type SQLException à la couche métier, tout en continuant à arrêter l'exécution normale du programme :
...
catch (SQLException sqlException) // capture SQLException et ses sous-classes
{
throw new RuntimeException("Erreur (base de données)...", sqlException);
}
...
La pile d'appel est remplie au moment de la création de l'objet exception.
C'est à dire que les méthodes printStackTrace()
affiche la localisation de la création de l'instance.
Pour mettre à jour la pile d'appel d'une exception pré-existante (réutilisation pour éviter une allocation mémoire, ou relancer une exception), la méthode fillInStackTrace()
peut être utilisée :
...
catch (IOException ioException) // capture IOException et ses sous-classes
{
// ... traitement partiel de l'exception ...
ioException.fillInStackTrace(); // <-- pile d'appel mise à jour pour pointer ici
throw ioException; // <-- relance l'exception
}
...
Catégorie d'objet lancé
[modifier | modifier le wikicode]Le chapitre traite des exceptions, mais en fait tout objet dont la classe est ou dérive de la classe Throwable
peut être utilisé avec les mots-clés throw
, throws
et catch
.
Classes dérivées de Throwable
[modifier | modifier le wikicode]Il existe deux principales sous-classes de la classe Throwable
:
Exception
signale une erreur dans l'application,Error
signale une erreur plus grave, souvent au niveau de la machine virtuelle (manque de ressource, mauvais format de classe, ...).
Créer une classe d'exception
[modifier | modifier le wikicode]Il est également possible d'étendre une classe d'exception pour spécialiser un type d'erreur, ajouter une information dans l'objet exception, ...
Exemple :
public class HttpException extends Exception
{
private int code;
public HttpException(int code,String message)
{
super(""+code+" "+message);
this.code=code;
}
public int getHttpCode()
{ return code; }
}
Une instance de cette classe peut ensuite être lancée de la manière suivante :
public void download(URL url) throws HttpException
{
...
throw new HttpException ( 404, "File not found" );
}
et capturée comme suit :
try
{
download( ... );
}
catch(HttpException http_ex)
{
System.err.println("Erreur "+http_ex.getHttpCode());
}
Fermeture des ressources
[modifier | modifier le wikicode]Les objets de ressource alloue de la mémoire ou maintienne une ressource ouverte (un flux de fichier, une socket, ...). Il est important d'assurer la libération de la mémoire ou la fermeture de la ressource, quoi qu'il se passe, exception ou non. Dans le cas contraire, la ressource ne serait libérée qu'à la fin de l'application, qui peut donc cumuler les ressources ouvertes pouvant provoquer une fuite mémoire, une pénurie de ressource système, ...
Bloc finally
[modifier | modifier le wikicode]La première solution pour libérer une ressource est de le faire dans un bloc finally
.
Celui-ci doit être précédé du bloc try
contenant le code utilisant la ressource ouverte.
FileInputStream in = new FileInputStream(new File("/tmp/config.ini"));
// la ligne précédente peut échouer et lancer une exception,
// mais rien à fermer dans ce cas car le flux n'a pas pu être ouvert.
// Donc le bloc try commence juste après :
try
{
// ... lecture du fichier en utilisant in pour lire les données ...
}
finally
{
// Quoi qu'il se passe, fermeture après utilisation :
in.close();
}
Fermeture automatique
[modifier | modifier le wikicode]La seconde solution s'applique aux classes implémentant l'interface java.io.Closeable
.
La déclaration des instances de ces classes peut être employée en paramètre du mot-clé try
.
try(FileInputStream in = new FileInputStream(new File("/tmp/config.ini")))
{
// ... lecture du fichier en utilisant in pour lire les données ...
}
// Quoi qu'il se passe, la fermeture après utilisation
// est effectuée implicitement à la fin du bloc try.
Ce code est équivalent à l'exemple de la section précédente, en moins de lignes de code.
Le bloc try
pour cette syntaxe peut accepter des blocs catch
pour traiter des exceptions ou finally
pour libérer des ressources, mais sont optionnels.
Exceptions non capturées
[modifier | modifier le wikicode]Une exception lancée explicitement ou implicitement depuis une méthode peut ne pas être capturée.
Dans ce cas, elle est remontée au niveau de la méthode appelante.
La classe de cette exception (ou une classe parente) doit être déclarée dans une clause throws
à la fin de la déclaration de la méthode appelée, à moins que la classe soit une sous-classe de java.lang.RuntimeException
ou java.lang.Error
.
La méthode main
peut aussi déclarer des exceptions.
Quand la méthode main
ne capture pas une exception, celle-ci remonte à l'appelant (code interne de la JVM) qui affiche l'exception dans la console (flux d'erreur standard), et provoque donc l'arrêt de l'application si aucun thread non démon ne tourne.
Pour un thread, la méthode run()
des implémentations de l'interface java.lang.Runnable
ne peut pas déclarer d'exceptions lancées.
Cela ne l'empêche pas de remonter les exceptions non déclarées (java.lang.RuntimeException
, java.lang.Error
, et leurs sous-classes).
Dans ce cas, le thread est interrompu et la JVM affiche l'exception dans la console (flux d'erreur standard).
Il est possible de traiter les exceptions non capturées par un thread.
L'interface java.lang.Thread.UncaughtExceptionHandler
définie dans la classe java.lang.Thread
a une méthode uncaughtException
notifiée lorsqu'un thread n'a pas capturé une exception.
Exemple d'implémentation :
UncaughtExceptionHandler eh = new UncaughtExceptionHandler()
{
@Override
public void uncaughtException(Thread t, Throwable e)
{
System.out.println("Une exception "+e);
System.out.println("a eu lieu dans le thread "+t);
}
};
Ce gestionnaire peut ensuite être assigné à différents niveaux :
- au niveau global pour tous les threads de l'application en appelant la méthode statique
setDefaultUncaughtExceptionHandler
de la classejava.lang.Thread
:Thread.setDefaultUncaughtExceptionHandler(eh);
- au niveau d'un thread en appelant la méthode d'instance
setUncaughtExceptionHandler
:thread_telechargement.setUncaughtExceptionHandler(eh);
- au niveau d'un groupe de threads
java.lang.ThreadGroup
car cette classe implémente l'interfaceUncaughtExceptionHandler
:ThreadGroup tg_telechargeurs = new ThreadGroup() { @Override public void uncaughtException(Thread t, Throwable e) { System.out.println("Une exception "+e); System.out.println("a eu lieu dans le thread "+t); } }; Thread telecharge_html = new Thread(tg_telechargeurs, run_get_html); Thread telecharge_css = new Thread(tg_telechargeurs, run_get_css); Thread telecharge_js = new Thread(tg_telechargeurs, run_get_js);
Voir aussi
[modifier | modifier le wikicode]
Paquetages
Les paquetages, ou packages, permettent de grouper ensemble des classes rattachées, à la manière des dossiers qui permettent de classer des fichiers. En java, un paquetage particulier correspond à un répertoire. Les classes d'un projet en java devraient toutes être dans un paquetage particulier afin de les catégoriser. Cela a plusieurs avantages :
- retrouver les classes plus facilement
- Par exemple, la fenêtre d'édition
EditorWindow
dans le packageapp.editeur.fenetre
.
- Par exemple, la fenêtre d'édition
- différencier deux classes ayant un nom identique ou similaire mais au rôle différent et éviter un conflit de nom.
- Par exemple, trois classes
Adresse
différentes :- dans un package
courrier.client
pour la gestion d'une adresse postale ; - dans un package
réseau.hôte
pour la gestion d'une adresse IP ; - dans un package
personnage.propriété
pour la gestion de la caractéristique d'adresse d'un personnage de jeu de rôle.
- dans un package
- Par exemple, trois classes
Utilisation
[modifier | modifier le wikicode]Le fichier à inclure dans un paquetage doit contenir le mot-clé 'package
' suivi du nom du paquetage.
Ce nom peut être composé de plusieurs mots séparés par un point ( .
).
Exemple
[modifier | modifier le wikicode]pour inclure la classe Toto dans le paquetage 'mesPackages.sousPackage1
', écrire au début du fichier Toto.java :
package mesPackages.sousPackage1;
// ne pas oublier le point-virgule en fin de ligne
La structure des répertoires doit suivre le nom du paquetage, c'est-à-dire que le fichier Toto.java doit se situer dans un sous-répertoire mesPackages/sousPackage1/Toto.java.
Lorsqu'ensuite on désire utiliser la classe Toto depuis une autre classe, il faudra au préalable écrire :
import mesPackages.sousPackage1.Toto;
ou
import mesPackages.sousPackage1.*;
// importation de toutes les classes
// du paquetage mesPackage.sousPackage1
ou utiliser directement une référence au paquetage :
mesPackages.sousPackage1.Toto toto
= new mesPackages.sousPackage1.Toto();
Conventions
[modifier | modifier le wikicode]En Java, les programmeurs attribuent généralement un nom qui commence par une minuscule pour un paquetage, et un nom qui commence par une capitale pour une classe.
Les bibliothèques Java destinées à être distribuées regroupent leurs classes dans un ou plusieurs paquetages dont le nom est normalement précédé par un nom de domaine dans l'ordre inverse, par exemple :
package org.wikibooks.exemple;
Cela permet également d'éviter les conflits de nom de paquetages et de classes venant de différents groupes et entités.
Par exemple, une classe org.wikibooks.fr.Test
pourra cohabiter avec une classe org.wikipedia.fr.Test
sans conflit de noms de classe.
Compilation
[modifier | modifier le wikicode]L'utilisation d'un paquetage nécessite une structure des répertoires correspondant au nom du paquetage. Un projet est composé de plusieurs paquetages, dont la racine est le répertoire qu'il faut passer à java dans le classpath pour qu'il puisse charger les classes des paquetages du projet.
Par exemple, le fichier Toto.java
définit la classe Toto
du paquetage org.wikibooks.exemple
débute par :
package org.wikibooks.exemple;
class Toto ...
et doit se situer dans le répertoire org/wikibooks/exemple
.
Supposons que le chemin du fichier soit /home/me/javaprog/org/wikibooks/exemple/Toto.java
.
La compilation se fait en spécifiant le chemin du package racine (répertoire parent de org) comme classpath, et en spécifiant ensuite le chemin relatif à ce répertoire :
javac -classpath /home/me/javaprog org/wikibooks/exemple/Toto.java
Quand un programme Java utilise cette classe, il doit être compilé et exécuté en spécifiant /home/me/javaprog
(package racine) pour le paramètre classpath, et le nom de la classe doit inclure le nom du package :
java -classpath /home/me/javaprog org.wikibooks.exemple.Toto
Import statique
[modifier | modifier le wikicode]Pour utiliser les membres statiques publiques d'une classe, il faut nommer la classe où ils sont définis.
Exemple 1 :
double r = Math.cos(Math.PI * theta);
L'import statique permet d'importer les membres statiques d'une classe afin de ne pas nommer la classe en question.
Exemple 1 :
// importer le membre statique PI seulement
import static java.lang.Math.PI;
...
double r = Math.cos(PI * theta);
Exemple 2 :
// importer tous les membres statiques de la classe java.lang.Math
import static java.lang.Math.*;
...
double r = cos(PI * theta);
L'abus d'import statique n'est pas conseillé car le code ne contient plus de référence à la classe définissant le membre statique utilisé. Il ne faut l'utiliser que si les membres statiques d'un petit nombre de classes sont utilisés fréquemment.
Importation de paquetages depuis une archive jar
[modifier | modifier le wikicode]Pour importer un paquetage d'un fichier .jar
, il faut s'assurer que le fichier est dans le classpath courant (à compile- et execution-time). Ensuite, l'import se déroule comme si le .jar était décompressé.
Par exemple, pour compiler et lancer une classe d'un projet du dossier parent (contenant deux répertoires : /source
et /libraries
) compiler :
$ javac -classpath libraries/lib.jar source/MainClass.java
Puis le lancer :
$ java -classpath libraries/lib.jar source/MainClass
Cela nécessite que MainClass
soit le package par défaut, ce qui n'est pas très explicite.
Il vaut mieux que la classe principale soit également dans un paquetage.
Classes de base
Java est livré avec un ensemble de bibliothèques de base proposant des classes qui s'avèrent vite nécessaires. Tout programmeur Java doit avoir un bonne connaissance de toutes les possibilités offertes par ces composants. Il n'est pas nécessaire d'apprendre quoi que ce soit mais simplement d'avoir une idée générale de ce qui est disponible pour savoir quelles parties de la bibliothèque utiliser pour résoudre un problème donné.
Dorénavant, vous aurez toujours besoin d'avoir avoir la documentation complète sous le coude, via Internet ou sur votre ordinateur.
Paquetage java.lang
[modifier | modifier le wikicode]Le paquetage (le package) java.lang est le paquetage de base de Java. Il n'est même pas nécessaire de faire un import de ce paquetage : il est implicite.
Ce paquetage regroupe les classes concernant les types (Boolean
, Byte
, Character
, Short
, Integer
, Long
, Float
, Double
, String
), les classes (Object
classe de base de toutes les autres, Class
), les threads (Thread
, ThreadGroup
), …
Les chaînes de caractères
[modifier | modifier le wikicode]Les chaînes de caractères ne sont pas des types primitifs et sont des instances de la classe java.lang.String
. Les constantes sont entourées de guillemets.
Exemple :
String message = "Test des chaînes de caractères";
La concaténation se fait avec l'opérateur +
:
String message = "Test des chaînes de caractères"
+ " et de la concaténation";
La concaténation d'une chaîne de caractères avec un type primitif ajoute sa représentation nominale (décimal pour les types entiers, le caractère pour char
, "true"
ou "false"
pour boolean
) :
int a = 100;
String message = "A vaut " + a; // A vaut 100
La concaténation d'une chaîne de caractères avec un objet appelle sa méthode toString()
retournant une chaîne de caractères :
Object o=new Object();
String message = "L'objet " + o; // L'objet java.lang.Object@1fe081a3
Par défaut, la méthode toString()
définie dans la classe java.lang.Object
retourne le nom de la classe de l'objet, suivi du caractère arobase @
, suivi de la valeur retournée par la méthode hashCode()
en hexadécimal.
La méthode length()
permet d'obtenir la longueur d'une chaîne de caractères :
String message = "Test des chaînes de caractères";
System.out.print ( "Le message contient " );
System.out.print ( message.length() );
System.out.println( " caractères" );
Equivaut à :
System.out.print ( "Le message contient " );
System.out.print ( "Test des chaînes de caractères".length() );
System.out.println( " caractères" );
Nous pouvons utiliser des guillemets à l'intérieur d'une chaine de caractères en utilisant le caractère d'échappement \
String ma_string = "le mot \"mot\" contient 3 lettres";
Nous pouvons également utiliser des caractères spéciaux tels que \n (nouvelle ligne), \t (tabulation) pour formater la sortie.
String ma_string_formatee = "colonne 1\tcolonne 2\nvaleur 1\tvaleur 2\n"
Enfin, nous pouvons afficher des caractères particuliers (tels les caractères accentués, par exemple) en utilisant le préfixe \u suivi du code Unicode du caractère sur 4 chiffres hexadécimaux :
String chaine_unicode = "\u002D"; // le signe moins -
Cependant, la séquence \u
est également utilisable en dehors des chaînes de caractères pour encoder le code source.
Exemple :
int a = 200 \u002D 50; // le signe moins - -> 150
équivaut à :
int a = 200 - 50; // 150
Paquetage java.io
[modifier | modifier le wikicode]Ce paquetage concerne les flux d'entrées et sorties en général (fichiers, réseau, buffer).
Les deux classes abstraites InputStream
et OutputStream
gèrent les entrées et sorties binaires, utilisant des tableaux d'octets.
Les sous-classes de celles-ci sont plus spécialisées :
FileInputStream
etFileOutputStream
gèrent les fichiers,ByteArrayInputStream
etByteArrayOutputStream
lisent/écrivent depuis/vers un tableau d'octets.
Les classes Reader
et Writer
utilisent les tableaux de caractères.
Leurs sous-classes sont également plus spécialisées :
FileReader
etFileWriter
gèrent les fichiers,StringReader
etStringWriter
lisent/écrivent depuis/vers une chaîne de caractères.
Paquetage java.nio
[modifier | modifier le wikicode]Paquetage java.text
[modifier | modifier le wikicode]Paquetage java.util
[modifier | modifier le wikicode]Ce paquetage contient les classes permettant la gestion de collections (liste chaînées, table de hachage, tableau extensible, …).
Elle contient également une classe de générateur aléatoire (classe Random
), deux classes gérant le lancement de tâches périodiques ou à un moment donné (Timer
et TimerTask
), la gestion des dates (Date
, Calendar
, GregorianCalandar
), …
Paquetage java.net
[modifier | modifier le wikicode]Ce paquetage contient les classes permettant de communiquer à travers un réseau en utilisant des sockets TCP ou UDP. Il existe également des classes pour gérer les adresses IP (version 4 et 6).
Paquetage java.awt
[modifier | modifier le wikicode]Ce paquetage regroupe les classes graphiques de base.
Les composants graphiques utilisent les composants natifs de la platforme de lancement. Ils sont nommés Heavyweight, par opposition à Lightweight qui désigne les sous-classes de la classe Component
.
Bien que beaucoup plus rapide, il tend à être remplacé par swing, bien plus fourni.
Paquetage javax.swing
[modifier | modifier le wikicode]C'est un paquetage de gestion d'interface graphique très fournie.
Les composants sont entièrement développés en Java à partir de la classe java.awt.Component
.
Quelle que soit la plateforme de lancement de l'application, ils ont donc le même comportement et la même apparence, qui peut d'ailleurs être configurée.
-
Metal
-
Nimbus
Collections
Principe
[modifier | modifier le wikicode]Les collections en Java sont des classes permettant de manipuler les structures de données usuelles : listes, piles, files (ou queues).
Une manière standard de représenter une structure de données en Java consiste à regrouper dans une interface l'ensemble des noms des opérations applicables sur celle-ci (ajout, suppression, effacement, etc.), c'est-à-dire l'ensemble des opérations mentionnées dans le type de données abstrait implémenté par cette structure (sa spécification, indépendamment du choix d'implémentation).
Cette interface sera implémentée par chaque classe représentant cette sorte de structure : par exemple, l'interface List
regroupe un ensemble de noms de méthodes génériques permettant la manipulation de listes, et est implémentée à la fois par les classes concrètes ArrayList
(implémentation des listes par tableaux extensibles) et LinkedList
(implémentation des listes par chaînage).
Les interfaces et classes citées dans les exemples ci-dessous sont livrées dans le SDK standard.
Elles se trouvent dans le package java.util
.
Utilisation
[modifier | modifier le wikicode]List<String> ma_liste = new LinkedList<String>();
Dans cet exemple, List définit l'interface des listes, c'est-à-dire les opérations ou méthodes applicables aux listes. Une liste est une collection d'éléments ordonnés, éventuellement répétés. Dans l'exemple ci-dessus, on a choisi d'utiliser l'implémentation par une liste chaînée (classe LinkedList) où chaque élément de la liste pointe l'élément suivant.
Le <String>
entre chevrons représente le type générique des éléments de la liste.
Listes (List)
[modifier | modifier le wikicode]Cette interface est implémentée par un certain nombre de collections, et garantit que ces classes implémenteront l'ensemble des méthodes. Elle dérive de l'interface Collection
. Les éléments sont indexés (i.e. numérotés de la même façon qu'un tableau est indicé).
Méthodes sur les listes
[modifier | modifier le wikicode]Type | Méthode[1] | Rôle |
---|---|---|
boolean |
add(int index, Object o) |
Ajouter un objet à l'index indiqué. |
boolean |
addAll(int index, Collection c) |
Ajouter tous les objets d'une autre collection à l'index indiqué. |
Object |
get(int index) |
Retourner l'objet à l'index indiqué. |
int |
indexOf(Object o) |
Retourner le premier index de l'objet indiqué. |
int |
lastIndexOf(Object o) |
Retourner le dernier index de l'objet indiqué. |
Object |
remove(int index) |
Supprimer l'objet à l'index indiqué. |
Object |
set(int index, Object o) |
Remplacer l'objet à l'index indiqué. L'objet précédent est retourné. |
int |
size() |
Retourner le nombre d'éléments de la liste. |
List |
subList(int fromIndex,int toIndex) |
Retourner une sous-liste de celle-ci. |
Les différentes implémentations
[modifier | modifier le wikicode]On peut utiliser des tableaux redimensionnables (ArrayList) ou des listes chaînées (LinkedList)
Listes chaînées (LinkedList
)
[modifier | modifier le wikicode]Cette classe implémente l'interface List
en chaînant les éléments (liste doublement chaînée).
Les méthodes ajoutées sont :
void addFirst(Object o)
- Ajoute un élément en début de liste.
void addLast(Object o)
- Ajoute un élément en fin de liste.
Object getFirst()
- Retourne l'élément en début de liste.
Object getLast()
- Retourne l'élément en fin de liste.
Object removeFirst()
- Supprime et retourne l'élément en début de liste.
Object removeLast()
- Supprime et retourne l'élément en fin de liste.
Tableau redimensionnable (ArrayList
)
[modifier | modifier le wikicode]Cette classe est un tableau dont la taille croît lorsque des éléments sont ajoutés.
Tableau redimensionnable (Vector
)
[modifier | modifier le wikicode]Cette classe est un tableau dont la taille croît lorsque des éléments sont ajoutés.
Cette classe implémente les méthodes de l'interface List
et les suivantes :
int indexOf(Object o,int index)
- Retourne l'index de l'objet indiqué, en partant de l'index indiqué.
int lastIndexOf(Object o,int index)
- Retourne l'index de l'objet indiqué, en partant de l'index indiqué et en allant vers le début (index 0).
void setSize(int newSize)
- Tronquer/Agrandir le vecteur à la taille indiquée.
Cette classe a été créée avant la classe ArrayList
. Elle est synchronisée : durant l'appel à une méthode de cette classe par un thread, un autre thread ne peut modifier le tableau.
Files (Queue)
[modifier | modifier le wikicode]Files avec priorités (PriorityQueue)
[modifier | modifier le wikicode]Pour utiliser une file avec priorités, les éléments doivent être des instances d'une classe qui implémente l'interface Comparator. Il faudra écrire la méthode int compare(Object, Object) qui compare les deux instances et renvoie un entier.
Si vous ne voulez, ou ne pouvez pas, modifier la classe qui décrit les éléments, vous pouvez créer, dans la classe où vous utilisez votre file avec priorité, une classe interne qui hérite de la classe des éléments et implémente l'interface Comparator. Exemple, si vous voulez créer une file avec priorité de Bidules :
import mon_package.Bidule;
public class MaClasseQuiUtiliseUneFileDeBidules
{
/**
* Classe permettant de créer une file avec priorité de Bidules
*
*/
public class ComparableBidule implements Comparator<Bidule>
{
public int compare(Bidule b1, Bidule b2)
{
// On retourne le signe de la soustraction abstraite b1 - b2
// Si b1 < b2 (ou b1 à classer avant b2) ---> retourner -1
// Si b1 == b2 (ou b1 équivaut à b2) ---> retourner 0
// Si b1 > b2 (ou b1 à classer après b2) ---> retourner +1
...
}
}
public void une_methode()
{
PriorityQueue<ComparableBidule> f = new LinkedList<ComparableBidule>();
Bidule b = new Bidule();
f.add(b)
}
}
Piles (Stack
)
[modifier | modifier le wikicode]Une pile contient une liste d'objets. Il est possible d'ajouter un objet au sommet de la pile (empiler, ou push en anglais), et de retirer l'objet situé au sommet de la pile (dépiler, ou pop en anglais).
Exemple: En partant d'une pile vide, on effectue les opérations suivantes :
État de la pile Opération (vide) empiler A A empiler B A B empiler C A B C dépiler -> C A B dépiler -> B A empiler D A D
La classe Stack
est une implémentation de pile qui dérive de la classe Vector
et ajoute les méthodes suivantes pour gérer les objets comme une pile :
boolean empty()
- Retourne vrai si la pile est vide.
Object peek()
- Retourne l'objet au sommet de la pile sans l'enlever.
Object pop()
- Retourne l'objet au sommet de la pile et l'enlève.
Object push(Object o)
- Ajoute l'objet au sommet de la pile.
int search(Object o)
- Retourne l'index de l'objet depuis le sommet de la pile (1 = sommet de la pile, -1 = non trouvé).
Ensembles (Set)
[modifier | modifier le wikicode]Conformément à l'idée mathématique, les ensembles représentent plusieurs éléments non triés, sans répétitions. Les ajouts d'éléments déjà présents sont donc ignorés. Un élément est déjà présent si un test equals
sur un des éléments de l'ensemble renvoie vrai.
Cette interface est implémentée par un certain nombre de collections, et garantit que ces classes implémenteront l'ensemble des méthodes. Elle dérive de l'interface Collection
, sans ajouter de nouvelles méthodes. Elle sert seulement à indiquer informellement que la collection implémentant cette interface ne contient aucun doublon d'objet (objets comparés par la méthode equals
). Cette interface correspond donc aux ensembles mathématiques.
Les différentes implémentations
[modifier | modifier le wikicode]- La classe
HashSet
implémente l'interfaceSet
en utilisant une table de hachage. TreeSet
utilise un arbre de recherche. Pour pouvoir utiliser unTreeSet
, il faut que les éléments soit comparables. Cette fonction est plus lente queHashSet
[2].LinkedHashSet
diffère deHashSet
car il maintient une liste doublement liée à travers toutes ses entrées, permettant de retrouver l'ordre d'insertion (mais pas pour les réinsertions).
Ensembles triés (SortedSet)
[modifier | modifier le wikicode]Les ensembles triés sont identiques aux ensembles simples excepté qu'ils peuvent être triés par défaut à leur création selon un tri dit naturel ou selon un motif de tri. La méthode comparator() de cette interface permet de retourner le motif de tri utilisé ou retourne null si le tri est effectué de façon naturelle en fonction du type des données.
Range view
- Permet des opérations sur les plages d'ensembles triés.
Endpoints
- Renvoie le premier ou dernier élément d'un ensemble trié.
Comparator access
- Renvoie le comparateur utilisé pour classer l'ensemble.
Tableaux associatifs (Map)
[modifier | modifier le wikicode]Cette interface est implémentée par les collections qui associent une clé à un objet. L'accès aux objets est donc effectué par une clé unique.
Il est possible d'utiliser n'importe quelle instance de classe comme clé. Cependant si cette classe ne possède pas les propriétés nécessaires, il faut utiliser exactement la même instance pour accéder à une valeur (voir Objets comparables et clés).
Les principales méthodes de cette interface sont :
void clear()
- Vider la collection.
boolean containsKey(Object key)
- Teste si la clé existe, c'est à dire associée à une valeur.
boolean containsValue(Object value)
- Teste si la valeur existe.
Set entrySet()
- Retourne l'ensemble des associations clés-valeurs.
Set keySet()
- Retourne l'ensemble des clés.
Collection values()
- Retourne la collection de valeurs.
Object put(Object key, Object value)
- Associe la clé à la valeur spécifiée, et retourne la valeur précédemment associée.
boolean putAll(Map m)
- Ajouter tous les objets d'une autre collection à celle-ci.
Object get(Object key)
- Retourne la valeur associée à la clé spécifiée, ou
null
si non trouvé. Object remove(Object key)
- Supprime l'objet associé à la clé, et retourne cet objet.
boolean isEmpty()
- Tester si la collection est vide.
int size()
- Retourne le nombre de clés.
Les implémentations
[modifier | modifier le wikicode]La classe Hashtable
implémente l'interface Map
de manière synchronisée.
La classe HashMap
implémente l'interface Map
, et permet d'utiliser la clé null
.
Les tableaux triés (SortedMap)
[modifier | modifier le wikicode]Exactement comme SortedSet :
Range view
- Permet des opérations sur les plages de tableaux triés.
Endpoints
- Renvoie le premier ou dernier élément d'un tableau trié.
Comparator access
- Renvoie le comparateur utilisé pour classer le tableau.
Parcourir une collection
[modifier | modifier le wikicode]Les itérateurs
[modifier | modifier le wikicode]Les itérateurs sont des outils puissants pour parcourir le contenu d'une collection.
List<String> ma_liste = new LinkedList<String>();
ma_liste.add("Bonjour");
ma_liste.add(" le ");
ma_liste.add("monde");
Iterator<String> it = ma_liste.iterator(); // On paramètre Iterator par le type des éléments de la collection qui sera parcourue
while (it.hasNext())
{
System.out.println(it.next()); // Affiche "Bonjour le monde"
}
Attention, l'appel de next() renvoie l'élément courant dans le parcours et passe l'itérateur à l'élément suivant.
Pour pouvoir itérer sur une collection, il faut qu'elle soit itérable, c'est à dire qu'elle hérite de Iterable. C'est le cas la plupart du temps mais il y a des exceptions (HashMap).
L'utilisation des itérateurs requiert la classe Iterator définie dans le package java.util.
Boucles « for each »
[modifier | modifier le wikicode]// Parcours d'une collection
Collection<ObjetX> collection = ......;
for(ObjetX objetX : collection)
{
objetX.methodeY(p1,p2);
}
Attention aux parcours des Map
[modifier | modifier le wikicode]Le code suivant convient tout a fait si vous avez besoin de parcourir les clés de la Map sans vous préoccuper des valeurs
Map<Key, Value> map;
for (Key key : map.keySet())
{
// ...
}
Le code suivant est à proscrire si vous parcourrez l'ensemble des clés et des valeurs qui leurs sont associées
for (Key key : map.keySet())
{
// l'appel de get engendre un temps coûteux en accès à chaque itération
Value value = map.get(key);
}
Dans le cas précédent, préférez le code suivant
for (Map.Entry<Key, Value> entry : map.entrySet())
{
Key key = entry.getKey();
Value value = entry.getValue();
// ...
}
Modification durant l'itération
[modifier | modifier le wikicode]Si la collection est modifiée au cours d'une itération, l'itérateur se retrouve dans un état perturbé et lance une exception de classe java.util.ConcurrentModificationException
lors de l'itération suivante.
Exemple :
for(Element elem : elements_by_id.values())
{
if (elem.usecount==0)
{
// Retirer un élément inutilisé
elements_by_id.remove(elem.id);
// --> ConcurrentModificationException à l'itération suivante !
}
}
Le cas de la suppression au cours d'une itération (comme l'exemple précédent) peut être géré par l'itérateur en appelent la méthode remove()
.
Cela suppose donc d'utiliser explicitement l'itérateur et de changer le type de boucle :
Iterator<Element> it_elements = elements_by_id.values().iterator();
while (it_elements.hasNext())
{
Element elem = it_elements.next();
if (elem.usecount==0)
{
// Retirer un élément inutilisé
it_elements.remove(); // OK car géré par l'itérateur
}
}
Interface commune
[modifier | modifier le wikicode]La plupart des structures évoquées ci-dessus implémentent l'interface Collection et possèdent donc un ensemble de méthodes communes. Les principales méthodes sont :
boolean add(Object o)
- Ajouter un objet à la collection.
boolean remove(Object o)
- Retirer un objet de la collection.
boolean contains(Object o)
- Tester si la collection contient l'objet indiqué.
boolean addAll(Collection c)
- Ajouter tous les objets d'une autre collection à celle-ci.
boolean removeAll(Collection c)
- Retirer tous les objets d'une autre collection de celle-ci.
boolean retainAll(Collection c)
- Retirer tous les objets qui ne sont pas dans la collection spécifiée de celle-ci. À la fin les deux collections contiennent les mêmes objets.
boolean containsAll(Collection c)
- Tester si la collection contient tous les objets de la collection indiquée.
void clear()
- Vider la collection.
boolean isEmpty()
- Tester si la collection est vide.
Iterator iterator()
- Retourne un itérateur permettant de faire une boucle sur tous les objets contenus dans la collection.
int size()
- Retourne le nombre d'objets de la collection.
Object[] toArray()
- Convertit la collection en tableau d'objets.
Object[] toArray(Object[] a)
- Convertit la collection en tableau d'objets de classe spécifiée. Si le tableau fourni n'est pas assez grand, un nouveau tableau est alloué en utilisant la même classe d'élément.
Tableau récapitulatif
[modifier | modifier le wikicode]Interface | Implémentations | |||
Tableau dynamique | Chaînage | Arborescence[3] | Table de hachage | |
Liste (List) | ArrayList, Vector | LinkedList | ||
Ensemble (Set) | TreeSet | HashSet | ||
File (Queue) | ||||
Tableau associatif (Map) | TreeMap | HashMap |
Synchronisation
[modifier | modifier le wikicode]Si une collection est accédée par plusieurs threads, il faut utiliser la synchronisation afin que les modifications soient cohérentes, c'est à dire exécutées sans être interrompues par une modification effectuée par un autre thread.
Une collection synchronisée garantit que l'appel d'une méthode de la collection sera effectuée sans qu'aucun autre thread ne modifie cette collection entre-temps.
Pour obtenir une collection synchronisée, vous devez appeler une méthode de la classe Collections
, dont le nom dépend du type de collection :
type variable = Collections.synchronizedtype( new type(...) );
Exemple :
Set myset = Collections.synchronizedSet( new HashSet() );
Cependant, le thread peut être interrompu entre deux appels de méthode.
Dans ce cas, vous devez synchroniser l'ensemble des instructions appelant les méthodes de la collection (mot-clé synchronized
). Voir le chapitre "Threads et synchronisation".
Les collections ne sont pas thread-safe, aucune méthode n'est atomique, sauf pour les collections synchronisées. Mais seulement durant l'appel à une méthode. Les sources de ces classes sont d'ailleurs consultables si vous avez accès au JDK.
La solution précédente peut résoudre certains problèmes de synchronisation, par exemple celui ci :
Map<String,String> map=new HashMap<String,String>();
map.put("toto", "valeur exemple 1");
map.put("titi", "valeur exemple 1");
Cependant dans le code ci-dessous, une modification peut avoir lieu, par un autre thread, entre l'appel à containsKey et celui à put.
if (!map.containsKey("titi"))
map.put("titi", "valeur exemple 3");
Il faut donc englober les deux appels dans un même bloc synchronized :
synchronized(map)
{
if (!map.containsKey("titi"))
map.put("titi", "valeur exemple 3");
}
La solution la plus efficace est d'utiliser les listes de la bibliothèque java.util.concurrent qui possède un putIfAbsent. En outre, les méthodes de cette bibliothèque peuvent être considérées comme toujours plus rapides en mode multi-thread que celle d'une liste synchronisée,
Tableaux génériques
[modifier | modifier le wikicode]Il n'est pas possible d'instancier des tableaux d'un type générique, notamment à cause du mécanisme d'effacement.
Chapitre<String> [] livre = new Chapitre<String> [12]; // Erreur
Il existe cependant une astuce peu connu basé sur l'utilisation d'expression lambda pour palier à cette contrainte.
IntFunction <int[]> factoryTab = int[]::new ;
int[] tableau = factoryTab.apply(5) ;
Classes squelettes
[modifier | modifier le wikicode]Une interface possède souvent une longue série de méthodes à implémenter. Certaines sont implémentées généralement de la même manière par beaucoup de classes.
Plutôt que de répéter le code de ces méthodes dans les différentes implémentations, il est préférable de rassembler leur code dans une classe abstraite (car toutes les méthodes de l'interface ne sont pas implémentées).
Les classes de l'API Java utilisent de telles classes dont dérivent les différentes implémentations.
Ces classes sont :
AbstractCollection
pour l'interfaceCollection
,AbstractList
pour l'interfaceList
,AbstractSequentialList
pour l'interfaceList
,AbstractSet
pour l'interfaceSet
,AbstractMap
pour l'interfaceMap
.
Elles ne sont pas instanciables directement car abstraites, mais servent de classes de base. Il est utile de connaître ces classes pour rechercher dans la documentation, ou réaliser une implémentation alternative à celle proposée par l'API.
Notes
[modifier | modifier le wikicode]- ↑ http://docs.oracle.com/javase/7/docs/api/java/util/List.html
- ↑ http://docs.oracle.com/javase/7/docs/api/java/util/LinkedHashSet.html
- ↑ L'utilisation des arbres de recherche requiert que les éléments soient triables (sortable)
Nombre de taille arbitraire
Java permet de manipuler des nombres de taille arbitraire pour utiliser de l'arithmétique multiprécision. Ces nombres permettent d'aller au-delà des limites des types numériques primitifs, sans perte de précision, ou avec perte contrôlée. Ils sont utilisés dans des domaines où le calcul avec tous les chiffres est nécessaire (la finance, calcul mathématique de précision, calcul de clés cryptographiques, ...).
Deux classes du paquetage java.math
sont utilisées pour représenter deux types de nombres de taille arbitraire :
- La classe
BigInteger
représente un nombre entier de taille arbitraire, - La classe
BigDecimal
représente un nombre réel de taille arbitraire.
La précision est arbitraire mais reste soumise aux contraintes du système :
- La quantité de mémoire limite le nombre de chiffres stockable. Même si cette limite peut sembler haute, il ne faut pas oublier de multiplier par le nombre d'instances utilisées à un instant donné pour se rendre compte de la quantité de mémoire utilisée.
- La vitesse du processeur limite la quantité de calcul sur les nombres de très grande précision. Les algorithmes de calcul comme l'addition ou la soustraction s'effectuent de manière linaire par rapport à la précision employée ; mais d'autres calculs comme la multiplication, la division, le calcul de racine, ... ont besoin de confronter chaque chiffre à chacun des autres, et le temps de calcul est alors quadratique ou exponentiel par rapport à la précision employée.
Nombre entier
[modifier | modifier le wikicode]La classe BigInteger
représente un nombre entier de taille arbitraire.
Un tel entier peut être créé à partir d'une chaîne de caractères, comme dans l'exemple suivant :
// À partir de la représentation en décimal :
BigInteger nombre_d_atomes = new BigInteger("7312154503213716790123144675124456301892787921502480260380150468277");
// À partir de la représentation en hexadécimal :
BigInteger cle = new BigInteger("A87B31697C6A9d9363B275E610F245C246A932B14569D273AE54CEC171CAFEFADE", 16);
Il peut aussi être créé à partir d'un tableau d'octets d'une représentation en complément à deux, dans l'ordre big-endian : l'octet le plus significatif est le premier.
// À partir d'un tableau d'octets de la représentation
// en complément à deux du nombre -17179869184 :
BigInteger nombre = new BigInteger(new byte[]{ 0xFC, 0x00, 0x00, 0x00, 0x00 });
Il peut aussi être créé à partir d'un nombre de type long
en utilisant la méthode statique valueOf
.
Comme les instances sont immuables, la méthode statique permet de retourner la même instance pour les valeurs fréquemment utilisées.
// À partir d'un nombre de type long :
BigInteger total_heures = BigInteger.valueOf(24L);
La classe définit aussi quelques instances comme membres statiques pour les valeurs classiques :
BigInteger.ZERO
pour le nombre zéro,BigInteger.ONE
pour le nombre un,BigInteger.TEN
pour le nombre dix.
Opérations arithmétiques
[modifier | modifier le wikicode]Les opérateurs en Java ne s'appliquent qu'aux types primitifs, excepté le plus +
permettant de concaténer des chaînes de caractères.
Pour effectuer des opérations sur des instances de BigInteger
, il faut appeler les méthodes résumées dans le tableau ci-dessous.
Une instance de BigInteger
étant immuable, les méthodes ne modifie pas l'objet lui-même mais retournent une nouvelle instance pour le résultat.
Si ce n'était pas le cas, il serait possible de modifier la valeur de BigInteger.ONE
par exemple, ce qui fausserait les calculs utilisant la constante.
Opérateur équivalent | BigInteger a.
|
Description |
---|---|---|
- |
negate() | Retourne le résultat de l'inversion de signe -a
|
+ |
add(BigInteger b) | Retourne le résultat de l'addition a+b
|
- |
subtract(BigInteger b) | Retourne le résultat de la soustraction a-b
|
* |
multiply(BigInteger b) | Retourne le résultat de la multiplication a*b
|
/ |
divide(BigInteger b) | Retourne le résultat de la division a/b
|
% |
remainder(BigInteger b) | Retourne le reste de la division a%b
|
mod(BigInteger b) | Retourne le modulo de a et b. Par rapport au reste de la division, le modulo n'est jamais négatif. | |
/ % |
divideAndRemainder(BigInteger b) | Retourne un tableau de deux BigInteger : le quotient et le reste de la division {a/b}, a%b}
|
Comme les expressions complexes sur ces nombres entraînent la création de multiples instances, il n'est pas utile de garder les résultats intermédiaires, sauf de manière temporaire pour déboguer l'expression. Il est donc courant d'enchaîner les méthodes :
BigInteger
a = new BigInteger("1500000000"),
b = new BigInteger("578000057"),
c = new BigInteger("1234567");
// d = ( a+b ) * c / 10
BigInteger d = a.add(b).multiply(c).divide(BigInteger.TEN);
Autres opérations arithmétiques sur ces nombres :
BigInteger a.
|
Description |
---|---|
abs() | Retourne la valeur absolue Math.abs(a)
|
signum() | Retourne le signe sous la forme d'un entier Math.signum(a) (-1 négatif, 0 zéro, +1 positif)
|
gcd(BigInteger b) | Retourne le plus grand diviseur commun entre les valeurs absolues des deux nombres. |
max(BigInteger b) | Retourne le plus grand des deux nombres. |
min(BigInteger b) | Retourne le plus petit des deux nombres. |
pow(int n) | Retourne le calcul de la puissance n du nombre . |
Opérations sur les bits
[modifier | modifier le wikicode]Les bits des entiers de taille arbitraire peuvent aussi être manipulés.
Opérateur équivalent | BigInteger a.
|
Description |
---|---|---|
~ |
not() | Retourne le résultat de l'inversion de bits ~a
|
& |
and(BigInteger b) | Retourne le résultat du et bit à bit a & b
|
& ~ |
andNot(BigInteger b) | Retourne le résultat du et bit à bit avec inversion a & ~b .
Cette méthode est un raccourci pour la combinaison des opérateurs |
| |
or(BigInteger b) | Retourne le résultat du ou bit à bit a | b
|
^ |
xor(BigInteger b) | Retourne le résultat du ou exclusif bit à bit a ^ b
|
<< |
shiftLeft(int n) | Retourne le résultat du décalage de n bits vers la gauche a << n
|
>> |
shiftRight(int n) | Retourne le résultat du décalage de n bits vers la droite a >> n
|
Autres opérations sur les bits de ces nombres :
BigInteger a.
|
Description |
---|---|
bitLength() | Retourne le nombre minimal de bits pour la représentation en complément à deux, sans le bit de signe. |
bitCount() | Retourne le nombre de bits différents du bit de signe, c'est-à-dire le nombre de 1 pour un nombre positif, le nombre de 0 pour un nombre négatif. |
clearBit(int n) | Retourne le nombre avec le bit n mis à zéro. |
setBit(int n) | Retourne le nombre avec le bit n mis à un. |
flipBit(int n) | Retourne le nombre avec le bit n inversé. |
testBit(int n) | Retourne un booléen indiquant si le bit n est à un. |
getLowestSetBit() | Retourne le numéro du bit à 1 de plus faible poids, ou -1 si aucun (pour le nombre zéro). |
Opérations arithmétiques modulaires
[modifier | modifier le wikicode]La classe BigInteger
a également des méthodes pour l'arithmétique modulaire.
Le modulo m détermine l'intervalle des entiers utilisés : de 0 (inclus) à m (exclus).
BigInteger a.
|
Description |
---|---|
mod(BigInteger m) | Réduit le nombre par reste de la division par m et retourne un nombre entier positif ou nul inférieur à m. |
modPow(BigInteger exponent, BigInteger m) | Retourne le calcul de |
modInverse(BigInteger m) | Retourne l'inverse du nombre modulo m .
Multiplier le résultat par le nombre Une exception de type |
Générer un nombre probablement premier
[modifier | modifier le wikicode]Les algorithmes de cryptographie utilisent souvent des problèmes mathématiques difficiles à résoudre en un temps raisonnable, dont celui de la décomposition des grands nombres entiers en facteur premiers.
new BigInteger(int bitLength, int certainty, Random rnd)
- Crée un nombre positif, probablement premier.
- La probabilité que le nombre créé soit premier est :
BigInteger.probablePrime(int bitLength, Random rnd)
- Retourne un nombre positif, probablement premier.
- La probabilité que le nombre créé soit premier est :
Paramètres :
bitLength
est le nombre de bits voulu.certainty
représente la mesure d'incertitude sur la primalité tolérée. Plus sa valeur est grande, plus le calcul prend du temps.rnd
est une instance de la classejava.util.Random
pour la génération des nombres dont la primalité est testée.
Conversions
[modifier | modifier le wikicode]La classe BigInteger
est une sous-classe de Number
, comme les classes englobant les nombres primitifs.
Elle possède donc les mêmes méthodes de conversions, qui tronque le résultat s'il ne loge pas dans le type demandé :
byte byteValue()
,short shortValue()
,int intValue()
,long longValue()
,float floatValue()
,double doubleValue()
.
Pour les types entiers, il y a également une variante retournant une valeur exacte ou une exception de type java.lang.ArithmeticException
si le nombre ne loge pas dans le type demandé :
byte byteValueExact()
,short shortValueExact()
,int intValueExact()
,long longValueExact()
.
Nombre décimal
[modifier | modifier le wikicode]La classe BigDecimal
représente un nombre décimal de taille arbitraire défini par un entier de taille arbitraire (type BigInteger
) et un nombre entier de type int
définissant la position de la virgule.
Il y a donc une limite (celle du type int
32 bits) sur l'échelle.
// À partir de la représentation en décimal :
BigDecimal pi = new BigDecimal("3.14159265358979323846264338327950288419716939937510582097494459230781640628");
Il est possible de créer une instance de BigDecimal
à partir d'une valeur de type double
.
Cependant, il peut y avoir une perte de précision :
BigDecimal un_dixieme = new BigDecimal(0.1D);
// 0.1000000000000000055511151231257827021181583404541015625
L'utilisation de la méthode statique valueOf
permet d'éviter cette perte de précision car la valeur est convertie en chaîne de caractère en appelant la méthode Double.toString
:
BigDecimal un_dixieme = BigDecimal.valueOf(0.1D); // 0.1
// Équivalent :
BigDecimal un_dixieme = new BigDecimal(Double.toString(0.1D)); // 0.1
Cependant la précision possible du nombre construit par ce moyen est limitée par celle du type double
.
La classe définit aussi quelques instances comme membres statiques pour les valeurs classiques :
BigDecimal.ZERO
pour le nombre zéro,BigDecimal.ONE
pour le nombre un,BigDecimal.TEN
pour le nombre dix.
Précision et échelle
[modifier | modifier le wikicode]La méthode precision()
retourne la précision, c'est-à-dire le nombre de chiffres décimaux du nombre.
La méthode scale()
retourne la position de la virgule par rapport au dernier chiffre de précision.
La méthode unscaledValue()
retourne l'entier de type BigInteger
représentant la valeur entière sans échelle ; son nombre de chiffres vaut precision()
.
Exemples :
Nombre | .precision()
|
.scale()
|
.unscaledValue()
|
---|---|---|---|
new BigDecimal("1250000")
|
7
|
0
|
1250000
|
new BigDecimal("125E4")
|
3
|
-4
|
125
|
new BigDecimal("0.000125")
|
3
|
6
|
125
|
new BigDecimal("0.0001250000")
|
7
|
10
|
1250000
|
La valeur représentée par un nombre de type BigDecimal
est donc .
Contexte
[modifier | modifier le wikicode]La classe MathContext
est utilisée par plusieurs méthodes comme contexte pour spécifier la précision voulue et le mode d'arrondi à appliquer.
Arrondi
[modifier | modifier le wikicode]La méthode round(MathContext mc)
arrondit le nombre selon le mode et à la précision indiqués dans l'objet MathContext
donné en argument.
La précision spécifie le nombre total de chiffres voulu, quel que soit la position de la virgule.
BigDecimal a = new BigDecimal("12345.6789")
.round(new MathContext(2, RoundingMode.HALF_EVEN));
a.toPlainString() // 12000
a.scale() // -3
Tandis que la méthode setScale(int new_scale, RoundingMode rounding_mode)
change l'échelle du nombre :
BigDecimal a = new BigDecimal("12345.6789")
.setScale(2, RoundingMode.HALF_EVEN);
a.toPlainString() // 12345.68
a.scale() // 2
La modification directe de la position de la virgule avec une grande différence allouera une énorme quantité de mémoire pour ajouter les zéros, peut détériorer les performances du processeur. Il faut donc veiller à ne pas faire de grand changements d'échelle.
Opérations arithmétiques
[modifier | modifier le wikicode]Les opérateurs en Java ne s'appliquent qu'aux types primitifs, excepté le plus +
permettant de concaténer des chaînes de caractères.
Pour effectuer des opérations sur des instances de BigDecimal
, il faut appeler les méthodes résumées dans le tableau ci-dessous.
Ces méthodes sont similaires à celles de la classe BigInteger
, avec une variante acceptant un objet MathContext
pour arrondir le résultat, et un comportement différent pour la division.
Une instance de BigDecimal
étant immuable, les méthodes ne modifie pas l'objet lui-même mais retournent une nouvelle instance pour le résultat.
Si ce n'était pas le cas, il serait possible de modifier la valeur de BigDecimal.ONE
par exemple, ce qui fausserait les calculs utilisant la constante.
Opérateur équivalent | BigDecimal a.
|
Description |
---|---|---|
- |
negate() | Retourne le résultat de l'inversion de signe -a
|
- |
negate(MathContext mc) | Retourne le résultat arrondi de l'inversion de signe -a
|
+ |
plus() | Retourne le nombre +a (méthode pour la symétrie avec negate)
|
+ |
plus(MathContext mc) | Retourne le nombre arrondi +a
|
Math.round |
round(MathContext mc) | Retourne le nombre arrondi Math.round(a) .
|
+ |
add(BigDecimal b) | Retourne le résultat de l'addition a+b
|
+ |
add(BigDecimal b, MathContext mc) | Retourne le résultat arrondi de l'addition a+b
|
- |
subtract(BigDecimal b) | Retourne le résultat de la soustraction a-b
|
- |
subtract(BigDecimal b, MathContext mc) | Retourne le résultat arrondi de la soustraction a-b
|
* |
multiply(BigDecimal b) | Retourne le résultat de la multiplication a*b
|
* |
multiply(BigDecimal b, MathContext mc) | Retourne le résultat arrondi de la multiplication a*b
|
/ |
divide(BigDecimal b) | Retourne le résultat de la division a/b .
Comme cette opération n'effectue aucun arrondi, si le résultat génère un nombre infini de chiffres, une exception ArithmeticException est lancée. |
/ |
divide(BigDecimal b, MathContext mc) | Retourne le résultat arrondi de la division a/b
|
% |
remainder(BigDecimal b) | Retourne le reste de la division a%b
|
% |
remainder(BigDecimal b, MathContext mc) | Retourne le reste arrondi de la division a%b
|
/ % |
divideAndRemainder(BigDecimal b) | Retourne un tableau de deux BigInteger : le quotient et le reste de la division {a/b, a%b}
|
/ % |
divideAndRemainder(BigDecimal b, MathContext mc) | Retourne un tableau de deux BigInteger : le quotient et le reste arrondis de la division {a/b, a%b}
|
Comme les expressions complexes sur ces nombres entraînent la création de multiples instances, il n'est pas utile de garder les résultats intermédiaires, sauf de manière temporaire pour déboguer l'expression. Il est donc courant d'enchaîner les méthodes :
BigDecimal
a = new BigDecimal("1500000000"),
b = new BigDecimal("5780.00057"),
c = new BigDecimal("12345.67");
// d = ( a+b ) * c / 10 Arrondi à 8 chiffres de précision
BigDecimal d = a.add(b).multiply(c).divide(BigDecimal.TEN, 8, BigDecimal.ROUND_HALF_EVEN);
Si la division génère un nombre infini de chiffres, il faut préciser l'arrondi voulu :
BigDecimal.ONE.divide(BigDecimal.valueOf(5L));
// --> 0.2
BigDecimal.ONE.divide(BigDecimal.valueOf(7L));
// --> java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.
BigDecimal.ONE.divide(BigDecimal.valueOf(7L), 8, BigDecimal.ROUND_HALF_EVEN);
// --> 0.14285714
// Ou en utilisant un contexte d'arrondi réutilisable :
MathContext mc = new MathContext(8, RoundingMode.HALF_EVEN);
BigDecimal.ONE.divide(BigDecimal.valueOf(7L), mc);
// --> 0.14285714
Autres opérations arithmétiques sur ces nombres :
BigDecimal a.
|
Description |
---|---|
abs() | Retourne la valeur absolue Math.abs(a)
|
signum() | Retourne le signe sous la forme d'un entier Math.signum(a) (-1 négatif, 0 zéro, +1 positif)
|
max(BigDecimal b) | Retourne le plus grand des deux nombres. |
min(BigDecimal b) | Retourne le plus petit des deux nombres. |
pow(int n) | Retourne le calcul de la puissance n du nombre . |
pow(int n, MathContext mc) | Retourne le calcul arrondi de la puissance n du nombre . |
Comparaison
[modifier | modifier le wikicode]
La comparaison avec la méthode equals
compare tous les champs des objets BigDecimal
, et peut retourner false
même si les deux nombres comparés représentent la même valeur.
Exemple :
BigDecimal a = new BigDecimal("1"); // entier = 1, scale = 0
BigDecimal b = new BigDecimal("1.0"); // entier = 10, scale = 1
a.equals(b) // false <!>
a.compareTo(b) == 0 // true
La méthode compareTo
est celle qu'il faut utiliser pour comparer les nombres de type BigDecimal
.
Appel de la méthode compareTo
|
Comparaison équivalente pour les types primitifs |
---|---|
a.compareTo(b) == 0
|
a == b
|
a.compareTo(b) < 0
|
a < b
|
a.compareTo(b) > 0
|
a > b
|
Conversions
[modifier | modifier le wikicode]La classe BigDecimal
est une sous-classe de Number
, comme les classes englobant les nombres primitifs.
Elle possède donc les mêmes méthodes de conversions, qui tronquent le résultat s'il ne loge pas dans le type demandé :
byte byteValue()
,short shortValue()
,int intValue()
,long longValue()
,float floatValue()
,double doubleValue()
.
Pour les types entiers, il y a également une variante retournant une valeur exacte ou une exception de type java.lang.ArithmeticException
si le nombre ne loge pas dans le type demandé :
byte byteValueExact()
,short shortValueExact()
,int intValueExact()
,long longValueExact()
.
Performances
[modifier | modifier le wikicode]Comme expliqué en introduction, la précision est arbitraire mais reste soumise aux contraintes du système et a un impact sur les performances de l'application.
Plus la précision est grande, plus la quantité de mémoire utilisée est importante. Cela impacte aussi la vitesse d'exécution des opérations car elle est dépendante de la précision employée (temps proportionnel/quadratique/exponentiel).
Dans les expressions longues, certaines opérations peuvent augmenter la précision des résultats. Il peut être important de réduire la précision des résultats intermédiaires avant de poursuivre les opérations, afin de réduire la mémoire utilisée et permettre un temps d'exécution moindre.
Notes
[modifier | modifier le wikicode]
Tags Javadoc
Java permet de documenter les classes et leurs membres en utilisant une syntaxe particulière des commentaires. Ils sont alors interprétés par le générateur automatique de documentation Javadoc, fourni par le JDK.
La commande javadoc
sans argument donne la syntaxe complète de la commande.
Ces commentaires commencent par la séquence /**
et se terminent par */
. Le contenu décrit l'entité qui suit (classe, interface, méthode ou attribut), suivi d'une série d'attributs dont le nom commence par un arobase @
.
La documentation générée étant au format HTML, il est possible d'insérer des balises dans la description.
Exemples
[modifier | modifier le wikicode]Exemple 1
[modifier | modifier le wikicode]/**
Une classe pour donner un <b>exemple</b> de documentation HTML.
*/
public class Exemple {
/** ...Documentation du membre de type entier nommé exemple... */
public int exemple;
Le commentaire de documentation se place juste avant l'entité commentée (classe, constructeur, méthode, champ).
Dans un commentaire de documentation, la première partie est un texte de description au format HTML. La seconde partie est une liste d'attributs spéciaux dont le nom commence par un arobase (@). Ces derniers sont interprétables par le compilateur et appelés tags.
Exemple 2
[modifier | modifier le wikicode]/**
Une classe pour illustrer les commentaires Javadoc.
@author Moi :-)
*/
public class Exemple {
/**
Une méthode <b>main</b> qui ne fait rien.
@param args Les arguments de la ligne de commande.
*/
public static void main(String[] args) {
}
}
En fait, il existe un attribut Javadoc qui est pris en compte par le compilateur : l'attribut @deprecated
. Cet attribut marque une classe, une méthode ou une variable comme obsolète. Ce qui signifie que si une autre classe l'utilise un avertissement est affiché à la compilation de cette classe.
Exemple :
/**
Une méthode obsolète. Il faut utiliser get() à la place.
@deprecated
*/
public Object getElement(int index)
{ ... }
/**
Une nouvelle méthode.
*/
public Object get(int index)
{ ... }
Cet attribut permet de modifier une bibliothèque de classe utilisée par plusieurs applications, en la laissant compatible avec les applications utilisant l'ancienne version, mais en indiquant que les anciennes classes / méthodes / variables ne doivent plus être utilisées et pourraient ne plus apparaître dans une version ultérieure.
Exemple 3
[modifier | modifier le wikicode]Exemple pour la méthode suivante :
/**
Obtenir la somme de deux entiers.
@param a Le premier nombre entier.
@param b Le deuxième nombre entier.
@return La valeur de la somme des deux entiers spécifiés.
*/
public int somme(int a, int b) {
return a + b;
}
Obtenir la somme de deux entiers.
- Description de la méthode somme.
@param a Le premier nombre entier.
- Attribut de description du paramètre a de la méthode.
@param b Le deuxième nombre entier.
- Attribut de description du paramètre b de la méthode.
@return La valeur de la somme des deux entiers spécifiés.
- Attribut de description de la valeur retournée par la méthode.
Voici une liste non exhaustive des attributs spéciaux :
Attribut et syntaxe | Dans un commentaire de ... | Description |
---|---|---|
@author auteur | classe | Nom de l'auteur de la classe. |
@version version | classe | Version de la classe. |
@deprecated description | classe, constructeur, méthode, champ | Marquer l'entité comme obsolète (ancienne version), décrire pourquoi et par quoi la remplacer.
Si l'entité marquée comme obsolète par cet attribut est utilisée, le compilateur donne un avertissement. |
@see référence | classe, constructeur, méthode, champ | Ajouter un lien dans la section "Voir aussi". |
@param description de l'id | constructeur et méthode | Décrire un paramètre de méthode. |
@return description | méthode | Décrire la valeur retournée par une méthode. |
@exception description du type | constructeur et méthode | Décrire les raisons de lancement d'une exception du type spécifié (clause throws ).
|
Exemple 4
[modifier | modifier le wikicode]Exemple pour une classe nommée Exemple
définie dans un package nommé org.wikibooks.fr
dans le fichier C:\ProgJava\org\wikibooks\fr\Exemple.java
:
package org.wikibooks.fr;
/**
Une classe d'exemple.
*/
public class Exemple {
/**
Obtenir la somme de deux entiers.
@param a Le premier nombre entier.
@param b Le deuxième nombre entier.
@return La valeur de la somme des deux entiers spécifiés.
*/
public int somme(int a, int b) {
return a + b;
}
}
Références
[modifier | modifier le wikicode]Dans la documentation, il est souvent nécessaire de faire référence à une autre entité (méthode, champ, classe) appartenant à la même classe, une autre classe ou un autre package. Certains tags Javadoc sont dédiés aux références :
@see
permet d'ajouter des références dans une section « Voir aussi » ("See also" en anglais) de la documentation de la méthode, du champ, de la classe ou du package.@link
permet d'ajouter une référence au milieu du texte de la description.@value
permet d'ajouter la valeur d'une constante au milieu du texte de la description.
Les deux derniers tags s'insérant au milieu du texte de la description doivent être encadrés par des accolades.
Chaque référence est un lien vers la documentation de l'entité référencée, et doit suivre la syntaxe suivante :
[classe][#membre]
Elle est composée de deux parties optionnelles :
- classe
- Le nom de la classe (avec le nom du package sauf si importé) est optionnel quand le membre est défini dans la même classe.
- #membre
- Le nom du membre de la classe (champ ou méthode) est absent dans les références à une classe.
- Pour les champs, le nom est suffisant.
- Pour les méthodes, le nom doit être suivi du type des paramètres entre parenthèses.
/**
* Cette méthode appelle {@link #toString()} pour obtenir...
* @see Object#equals(Object)
* @see #uneAutreMethod(int, java.util.Map)
*/
public void uneMethodeQuelconque()
{ /* ... */ }
public void uneAutreMethod(int index, java.util.Map mapping)
{ /* ... */ }
Générer la documentation
[modifier | modifier le wikicode]La documentation peut être générée dans un répertoire spécifique (C:\ProgDoc par exemple) avec la commande suivante :
javadoc -locale fr_FR -use -classpath C:\ProgJava -sourcepath C:\ProgJava -d C:\ProgDoc org.wikibooks.fr
Les options de cette commande sont décrites ci-dessous :
-locale fr_FR
- La documentation est en français.
-use
- Créer les pages sur l'utilisation des classes et paquetages (packages).
-classpath C:\ProgJava
- Le chemin des classes compilées (*.class).
-sourcepath C:\ProgJava
- Le chemin des classes sources (*.java).
-d C:\ProgDoc
- Le chemin où la documentation doit être générée.
org.wikibooks.fr
- Le nom du paquetage (package) à documenter. Il est possible de spécifier plusieurs paquetages, ou un ou plusieurs noms de classe pour ne documenter que celles-ci.
Niveau de documentation
[modifier | modifier le wikicode]Par défaut, javadoc ne génére pas la documentation pour les commentaires au-dessus d'une méthode ou d'un champ possédant le modificateur « private ». Ce qui est logique pour une documentation destinée à ceux qui vont utiliser les classes de manière externe, sans accès aux membres privés depuis leur propres classes.
Cependant, la documentation des membres privés peut être utile aux développeur des classes concernées.
De même, les développeurs qui utilisent les classes d'un package depuis leurs propres packages n'ont pas besoin de la documentation des membres protégés.
La commande javadoc
possède donc des options pour le niveau de documentation voulu :
-public
: ne documenter que les membres publics.-protected
: ne documenter que les membres publics et protégés.-package
: ne documenter que les membres publics, protégés et package (sans mot-clé).-private
: documenter tous les membres (publics, protégés, package, privés).
Description du package
[modifier | modifier le wikicode]La page de description d'un paquetage copie le texte de description à partir d'un fichier nommé package.html
qui doit se situer dans le répertoire correspondant. Dans notre exemple, il faut documenter le paquetage dans le fichier C:\ProgJava\org\wikibooks\fr\package.html
.
Dans les versions récentes de Java, le fichier package.html
peut être remplacé par un fichier Java spécial nommé package-info.java
contenant uniquement la déclaration du paquetage (package) précédée d'un commentaire de documentation.
Exemple (C:\ProgJava\org\wikibooks\fr\package-info.java
) :
/**
Ce paquetage fictif sert à illustrer le livre sur Java
de <i>fr.wikibooks.org</i>.
*/
package org.wikibooks.fr;
Annotations
Un nouveau principe introduit par Java 5 est la gestion des méta-données grâce à un mécanisme appelé annotations.
Ces annotations sont ajoutées dans le code devant les classes et leurs membres (méthodes et champs) pour :
- Ajouter des informations de documentation supplémentaires aux commentaires Javadoc[1],
- Ajouter des contraintes de compilation (voir @Override par exemple),
- Associer des méta-données qui peuvent être retrouvées par réflexion.
Malgré la similarité de syntaxe (arobase + nom) et de nom (@deprecated par exemple), il ne faut pas les confondre avec les tags Javadoc qui ne sont utilisés que par l'outil javadoc du JDK. De plus, une annotation correspond à un type de données (comme les classes, interfaces et énumérations). Ceci explique que la documentation d'un package possède une section supplémentaire nommée « Annotation Types » pour les annotations définies dans le package.
Toute entité (classe, interface, méthode ou champ) peut être précédée d'une ou plusieurs annotations contenant des méta-données renseignant le compilateur ou l'application elle-même.
Ces annotations sont accessibles à l'exécution, en utilisant la réflexion, à partir de nouvelles méthodes de la classe java.lang.Class
.
Syntaxe
[modifier | modifier le wikicode]La syntaxe d'une annotation est la suivante :
@annotation-type[ (name=value) * ]
La liste des paramètres entre parenthèses est optionnelle (vide par défaut). Elle contient une série de valeurs associées à un nom de champ définit par l'annotation.
Exemples
[modifier | modifier le wikicode]Exemple 1 :
@Author(
name = "Moi",
date = "02/01/2009"
)
class MyClass() { }
Exemple 2 :
@Override
void uneMethode() { }
Annotations existantes
[modifier | modifier le wikicode]Beaucoup d'annotations complètent les balises spéciales pour la documentation Javadoc.
@Deprecated
[modifier | modifier le wikicode]java.lang.Deprecated
Cette annotation marque une entité obsolète. Son utilisation génère un avertissement à la compilation, contrairement au tag @deprecated des commentaires Javadoc.
@Override
[modifier | modifier le wikicode]java.lang.Override
Cette annotation marque une méthode redéfinie.
Il ne s'agit pas d'une annotation de documentation mais d'un ajout de contrainte vérifiée à la compilation : une méthode marquée avec cette annotation doit obligatoirement être une méthode redéfinie de la classe mère. Dans le cas contraire (méthode non définie dans la classe mère), le compilateur génère une erreur annotation type not applicable to this kind of declaration.
À partir de Java 6, cette annotation peut aussi être utilisée pour les méthodes implémentant une interface.
@SuppressWarnings
[modifier | modifier le wikicode]java.lang.SuppressWarnings
Cette annotation signale au compilateur de supprimer certains avertissements à la compilation de l'entité.
Exemple 1 : pour supprimer les avertissements d'utilisations de méthodes obsolètes :
@SuppressWarnings("deprecation")
void uneMethode() { methodeObsolete(); }
Exemple 2 : pour supprimer les avertissements d'utilisations de méthodes obsolètes et d'utilisation de méthodes sans vérification de types (une version de la méthode avec types générique est préférable afin que le type d'élément soit vérifié) :
@SuppressWarnings({"unchecked", "deprecation"})
void uneMethode()
{
Vector v=new Vector();
methodeObsolete();
}
Créer de nouvelles annotations
[modifier | modifier le wikicode]La création est similaire à celle d'une interface.
Syntaxe
[modifier | modifier le wikicode]@interface identifiant { type champ() [ default valeur ]; }
Exemple de définition:
@interface InfoClasse
{
String auteur();
int revision() default 1;
String[] references();
}
Exemple d'utilisation:
@InfoClasse( auteur="Moi", references={"Reference1","Reference2"} )
class UneClasse { }
Cette nouvelle annotation peut elle-même être taguée avec des annotations du package java.lang.annotation
indiquant l'utilisation qui en est faite.
Documentation pour Javadoc
[modifier | modifier le wikicode]Si l'annotation définit des informations à afficher dans la documentation générée par Javadoc, la définition de l'annotation doit utiliser l'annotation @Documented
(java.lang.annotation.Documented
).
Correction de l'exemple précédent :
@Documented
@interface InfoClasse
{
String auteur();
int revision() default 1;
String[] references();
}
Informations disponibles à l'exécution
[modifier | modifier le wikicode]Pour que les informations d'une annotation soient disponibles à l'exécution, il faut annoter sa définition avec @Retention(RetentionPolicy.RUNTIME)
.
Exemple :
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@interface AnnotationPourExecution
{
// Éléments d'information disponibles à l'exécution
}
Restreindre l'utilisation d'une annotation
[modifier | modifier le wikicode]La définition d'une annotation peut être annotée avec @Target
pour spécifier avec quels types d'éléments l'annotation peut être utilisée.
La classe java.lang.annotation.ElementType
définit les différents types qu'il est possible d'annoter.
Exemple :
import java.lang.annotation.*;
@Target(ElementType.ANNOTATION_TYPE)
@interface PourAutreAnnotation
{
}
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE})
@interface InfoMembreOuType
{
// Type = class/interface/enum
}
Accès aux annotations d'une classe
[modifier | modifier le wikicode]L'API de réflexion de Java permet d'accéder aux annotations d'une classe, méthode ou champ.
Exemple :
import java.lang.annotation.*;
// ...
Class clas = objet.getClass(); // ou à partir d'une classe connue : UneClasseConnue.class
Annotation[] annotations = clas.getAnnotations();
for (Annotation annotation : annotations)
{
if (annotation instanceof InfoClasse)
{
InfoClasse infoClasse = (InfoClasse) annotation;
System.out.println("auteur : " + infoClasse.auteur());
System.out.println("revision : " + infoClasse.revision());
}
}
Références
[modifier | modifier le wikicode]
Entrées Sorties
Les opérations d'entrées-sorties concernent la lecture (entrée) et l'écriture (sortie) de données à travers différents types de flux...
En Java, les opérations d'entrées-sorties de base sont gérées par les classes du package java.io
. Ces classes obéissent au patron de conception décorateur. Ces objets sont souvent créés au sein d'objets correspondant au patron de conception fabrique.
Flux d'entrée-sortie
[modifier | modifier le wikicode]Le package java.io
possède deux classes principales :
InputStream
: cette classe abstraite définit les fonctions de lecture (entrée ou input en anglais),OutputStream
: cette classe abstraite définit les fonctions d'écriture (sortie ou output en anglais).
Ces deux classes abstraites définissent des fonctions bas-niveau et sont implémentées dans différentes sous-classes concrètes. Il y a plusieurs types de flux dont : les flux sur fichiers, les flux sur tubes, et les flux sur zones de mémoire. Par exemple on a :
FileInputStream
: lecture d'un fichier,FileOutputStream
: écriture d'un fichier.
La classe java.net.Socket
possèdent des méthodes retournant des instances concrètes des classes InputStream
et OutputStream
pour lire et écrire depuis/vers la socket TCP.
java.io.InputStream
[modifier | modifier le wikicode]La classe java.io.InputStream
possèdent des méthodes lisant une série d'octets (byte).
int InputStream.read()
: lit un octet et retourne sa valeur (0 à 255) ou -1 pour signaler la fin de flux.int InputStream.read(byte[] b)
: litb.length
octets pour remplir le tableau, et retourne le nombre d'octets lus.int InputStream.read(byte[] b, int off, int len)
: litlen
octets, et les dépose en commençant àb[off]
, et retourne le nombre d'octets lus
Les deux dernières méthodes lisent une série d'octets dans un tableau, mais peuvent retourner un nombre inférieur à celui demandé pour différentes raisons :
-1
indique qu'aucun octet n'a pu être lu car la fin du flux a été atteinte.0
indique qu'aucun octet n'a pu être lu temporairement, pas de données disponible (par exemple depuis une connexion réseau). Cela se produit pour les flux non bloquants.- Un nombre positif mais inférieur à celui demandé s'il n'y en a pas plus pour le moment (connexion réseau) ou définitivement (fin du flux).
java.io.OutputStream
[modifier | modifier le wikicode]La classe java.io.OutputStream
possède des méthodes écrivant une série d'octets (byte).
OutputStream.write()
: écrit un byteOutputStream.write(byte[] b)
: écritb.length
bytes.OutputStream.write(byte[] b, int off, int len)
: écritlen
bytes, commencer àb[off]
.
java.io.PrintStream
[modifier | modifier le wikicode]La classe java.io.PrintStream
hérite de la classe java.io.OutputStream
, et permet d'afficher tous les types de données sous forme textuelle.
La sortie standard et l'erreur standard impriment sur la console et sont des instances de la classe java.io.PrintStream
.
Exemple :
system.out.println("Bonjour !"); // Affiche une chaîne avec retour à la ligne
int count = 100;
system.out.print(count); // Affiche un entier sans retour à la ligne
system.out.print(' '); // Affiche un caractère
Tout objet peut être affiché car les méthodes print
et println
appellent la méthode ToString()
définie dans la classe java.lang.Object
racine de l'arbre hiérarchique de tous les types d'objets.
Exemple :
class NombreComplexe
{
double n_real, n_img;
public NombreComplexe(double r, double i)
{
this.n_real = r;
this.n_img = i;
}
public String toString()
{
return n_real + " + i*"+n_img;
}
}
NombreComplexe a = new NombreComplexe(1.0, 0.5);
system.out.println(a); // Appelle println(Object) pour afficher :
// 1.0 + i*0.5
La méthode toString()
est également appelée implicitement lors de la concaténation de chaînes de caractères :
String resultat = "Solution complexe : " + a;
// -> Solution complexe : 1.0 + i*0.5
Il est donc important que cette méthode n'ait aucun effet de bord (modification d'un objet, synchronisation, ...).
Lecture-écriture haut niveau
[modifier | modifier le wikicode]Le package java.io
possède des classes permettant la lecture et l'écriture de différents types de données.
Reader, Writer, PrintStream, Scanner
[modifier | modifier le wikicode]Pour intéragir avec l'utilisateur dans la console[1].
Exemple de saisie d'un tableau de noms utilisant la classe Scanner
:
import java.util.*;
public class ArrayExample
{
static Scanner input = new Scanner(System.in);
public static void main(String[] args)
{
int nombre_de_noms = getInt("Nombre d'entrées du tableau ?");
String[] noms = new String[nombre_de_noms];
// Saisie
for (int i = 0; i < noms.length; i++)
noms[i] = getString("Entrée n°" + (i+1));
// Affichage
for (int i = 0; i < noms.length; ++i)
System.out.println(noms[i]);
}
public static int getInt(String prompt)
{
System.out.print(prompt + " ");
int entier = input.nextInt();
input.nextLine(); // Ignorer la fin de la ligne jusqu'au retour à la ligne.
return entier;
}
public static String getString(String prompt)
{
System.out.print(prompt + " ");
return input.nextLine();
}
}
Gestion des répertoires et des fichiers
[modifier | modifier le wikicode]Type de chemin
[modifier | modifier le wikicode]Un chemin est une instance de la classe java.io.File
et peut désigner :
- un fichier normal (La méthode
isFile()
retournetrue
), - un répertoire (La méthode
isDirectory()
retournetrue
), - un fichier spécial spécifique au système d'exploitation (pipe, device, chemin réseau UNC, ...).
Résolution de chemin
[modifier | modifier le wikicode]Pour construire une instance de java.io.File
à partir d'un chemin, utilisez le constructeur File(String path)
:
File f = new File("/home/fr-wikibooks-org/mes_fichiers/fichier.txt"); // sous Linux
Il peut s'agir d'un chemin relatif :
File f = new File("source\\fichiers\\exemple.txt"); // sous Windows
Pour créer un chemin relatif à celui d'un répertoire, utilisez le constructeur File(File dir, String path)
:
File f_dir = new File("/app/test/fichiers"); // Répertoire de base File f_config = new File(f_dir, "premier/info.cfg"); // Relatif au répertoire de base // --> /app/test/fichiers/premier/info.cfg File f_autre = new File(f_dir, "../autre/alternative.cfg"); // Relatif au répertoire de base // --> /app/test/fichiers/../autre/alternative.cfg // C'est-à-dire /app/test/autre/alternative.cfg
Pour obtenir le chemin absolu, appelez la méthode getAbsoluteFile()
pour l'obtenir sous la forme d'un objet File
, ou la méthode getAbsolutePath()
pour l'obtenir sous la forme d'une chaîne de caractères String
.
File f_autre = new File(f_dir, "../autre/alternative.cfg"); // Relatif au répertoire de base // --> /app/test/fichiers/../autre/alternative.cfg f_autre = f_autre.getAbsoluteFile() // --> /app/test/autre/alternative.cfg // Répertoire courant : File f_curdir = new File(".").getAbsoluteFile();
Pour obtenir le chemin canonique, utilisez la méthode getCanonicalFile()
pour l'obtenir sous la forme d'un objet File
, ou la méthode getCanonicalPath()
pour l'obtenir sous la forme d'une chaîne de caractères String
.
Le chemin canonique résout les liens symboliques et les deux méthodes peuvent donc lancer une exception en cas d'erreur (lien cassé).
Lister les fichiers d'un répertoire
[modifier | modifier le wikicode]Pour les chemins désignant un répertoire, la méthode listFiles()
retourne un tableau des fichiers contenu dans le répertoire.
Ce tableau n'inclut pas d'entrée pour .
(répertoire courant) ni pour ..
(répertoire parent).
Lister les chemins racines
[modifier | modifier le wikicode]La méthode statique listRoots()
retourne un tableau des chemins racines du système de fichiers.
Pour Linux, cette méthode retourne le répertoire racine /
.
Pour Windows, cette méthode retourne le répertoire racine \
de chaque lecteur disponible (ex: C:\
D:\
...).
Cette méthode est particulièrement utile pour créer un sélecteur de fichiers utilisant un arbre de répertoires.
Création d'un répertoire
[modifier | modifier le wikicode]Pour créer un dossier :
import java.io.File;
File mon_dossier = new File("mon_chemin");
mon_dossier.mkdir();
Pour créer un dossier récursivement (avec ses parents), remplacer mkdir
par mkdirs
.
Création d'un fichier temporaire
[modifier | modifier le wikicode]Pour créer un fichier temporaire, utilisez la méthode statique createTempFile(prefix, suffix, directory)
.
- prefix
- Préfixe du nom de fichier.
- suffix
- Suffixe du nom de fichier.
- directory
- Répertoire où créer le fichier, ou
null
pour le créer dans le répertoire temporaire du système.
La méthode retourne le chemin du fichier créé ; le nom contient un identifiant unique.
La variante createTempFile(prefix, suffix)
équivaut à appeler la même méthode avec null
pour directory
: elle crée un fichier dans le répertoire temporaire du système.
La suppression n'est pas gérée par cette méthode.
Utilisez la méthode deleteOnExit()
sur le fichier créé pour qu'il soit supprimé automatiquement à la fin de l'application.
Il n'est pas possible d'annuler la requête de suppression de fichier en fin d'exécution.
Exemple :
File tempfile = File.createFile("debuglog", ".tmp"); // Dans le répertoire temp du système tempfile.deleteOnExit(); // Suppression du fichier à la fin de l'exécution // ... utiliser le fichier temporaire
Copie
[modifier | modifier le wikicode]Pour copier un dossier (avec son contenu) :
import org.apache.commons.io.FileUtils;
FileUtils.copyDirectory('chemin/source', 'chemin/destination');
Sans la bibliothèque Apache :
public static boolean recursiveCopy(File f_from, File f_to)
{
// Récupérer les attributs du fichier/répertoire source
long time = f_from.lastModified();
boolean cr = f_from.canRead();
boolean cw = f_from.canWrite();
boolean cx = f_from.canExecute();
if (f_from.isDirectory())
{
f_to.mkdirs();
File[] files = f_from.listFiles();
for(File ff : files)
{
File ft = new File(f_to, ff.getName());
if (!recursiveCopy(ff, ft)) return false;
}
}
else if (f_from.isFile()) // Test permettant d'éviter la copie de certains fichiers spéciaux
{
File p = f_to.getParentFile();
if (!p.exists()) p.mkdirs();
try
{
InputStream in = new FileInputStream(f_from);
try
{
OutputStream out = new FileOutputStream(f_to);
byte[] b = new byte[8192];
int len;
try
{
while ((len = in.read(b))>0)
out.write(b, 0, len);
}
finally { out.close(); }
}
finally { in.close(); }
}
catch (IOException e)
{ return false; }
}
// Mettre les attributs sur le fichier/répertoire destination
f_to.setReadable(cr);
f_to.setExecutable(cx);
f_to.setWritable(cw);
f_to.setLastModified(time);
return true;
}
Suppression
[modifier | modifier le wikicode]Pour un répertoire vide, ou un fichier :
import java.io.File;
File mon_chemin = new File("mon_chemin");
mon_chemin.delete();
Pour un répertoire vide ou non, ou un fichier :
import java.io.File;
import org.apache.commons.io.FileUtils;
File mon_dossier = new File("mon_chemin");
FileUtils.deleteQuietly(mon_dossier);
Sans la bibliothèque Apache :
public static boolean recursiveDelete(File f)
{
if (f.isDirectory())
{
File[] files = f.listFiles();
for(File ff : files)
if (!recursiveDelete(ff)) return false;
}
return f.delete();
}
Exemples concrets de flux
[modifier | modifier le wikicode]Fichiers textes
[modifier | modifier le wikicode]Pour lire un fichier texte plusieurs fois en reprenant du début, il faut introduire une variable de type FileChannel
, uniquement pour repositionner le lecteur[2] :
import java.io.*;
import java.nio.channels.FileChannel;
public class LireDeuxFois {
public static void main(String[] args) throws Exception {
if (args.length == 1) {
FileInputStream fis = new FileInputStream(args[0]);
FileChannel fc = fis.getChannel();
int cc;
// Première lecture
while ((cc = fis.read()) != -1) {
System.out.println((char)cc);
}
fc.position(0); // Reset
// Deuxième lecture
while ((cc = fis.read()) != -1) {
System.out.println((char)cc);
}
fis.close();
}
}
}
Images
[modifier | modifier le wikicode]Le paquetage javax.imageio
contient des classes permettant la lecture et l'écriture d'images en utilisant des flux d'entrées-sorties génériques (java.io.InputStream
et java.io.OutputStream
), des flux d'images (javax.imageio.ImageInputStream
et javax.imageio.ImageOutputStream
) ou simplement un chemin de fichier (java.io.File
).
Exemple convertissant une image GIF en PNG :
import javax.imageio.ImageIO;
import java.io.File;
// Lecture d'une image à partir d'un fichier :
Image image = ImageIO.read(new File("Icone1.gif"));
// Écriture d'une image dans un fichier :
ImageIO.write(image, "png", new File("Icone2.png"));
La classe ImageIO
possède également une méthode read
acceptant un flux de classe java.io.InputStream
.
Ce flux peut être créé à partir d'une connexion réseau ou de ressources locales au paquetage d'une classe de l'application.
La lecture crée un fichier temporaire dans le répertoire par défaut (méthode createTempFile
de la classe java.nio.file.Files
).
Il est possible d'obtenir une exception avec le message can’t create cache file
si la création échoue.
Si l'application a été lancée par une autre application Java, le problème peut venir de la non transmission des variables d'environnement du système au processus créé, car celles-ci contiennent le chemin du répertoire des fichiers temporaires. Par défaut, sous Windows, le répertoire C:\Windows
est utilisé mais l'écriture n'y est pas autorisé.
Consoles
[modifier | modifier le wikicode]Les applications sur la plupart des systèmes d'exploitation (Windows, Linux, Android, ...) sont associées à trois flux standards connectés par défaut à la console, mais pouvant être redirigés depuis le shell ou par une application vers un autre flux (un fichier, un flux d'une autre application, ...).
En java ces flux sont disponibles dans la classe java.lang.System
:
in
- Flux d'entrée standard de classe
java.io.InputStream
, utilisé pour lire une valeur entrée par l'utilisateur si le flux est relié à la console. out
- Flux de sortie standard de classe
java.io.PrintStream
, que l'application utilise pour afficher un résultat. err
- Flux d'erreur standard de classe
java.io.PrintStream
, que l'application utilise pour afficher des messages d'erreur.
Réseaux
[modifier | modifier le wikicode]Références
[modifier | modifier le wikicode]
Processus légers et synchronisation
Les processus légers (threads), ou fils d'exécution, permettent l'exécution de plusieurs tâches en même temps.
Qu'est ce qu'un processus léger ?
[modifier | modifier le wikicode]Un processus léger est un contexte d'exécution d'une application. Ce processus possède sa propre pile et pointeur d'exécution.
Une application en cours d'exécution (un processus) peut avoir plusieurs sous-processus léger. Tous les processus légers d'un même processus partagent la même zone de données. Ce qui veut dire que toute variable membre d'une classe est modifiable par n'importe quel processus léger. Il faut donc un moyen de synchroniser l'accès aux variables (voir paragraphe Synchronisation).
Par défaut, une application possède un seul processus léger, créé par le système. Cependant, en Java, d'autres processus légers sont créés quand l'application utilise une interface graphique, notamment un processus léger gérant la boucle de lecture des messages systèmes.
Processus léger courant
[modifier | modifier le wikicode]En Java, tout processus léger est représenté par un objet de classe Thread
.
Le processus léger courant est retourné par la méthode statique currentThread
de la classe Thread
.
La classe Thread
possède quelques méthodes statiques agissant sur le processus léger courant :
- la méthode
sleep(long millis)
permet de suspendre le processus léger durant le temps donné en millisecondes; - la méthode
yield()
permet de laisser les autres processus légers s'exécuter; - la méthode
interrupted()
teste si le processus léger courant a été interrompu ; - la méthode
dumpStack()
affiche la pile d'appel du processus léger courant (déverminage, ou débuggage en franglais).
Créer un processus léger
[modifier | modifier le wikicode]La classe Thread
peut être dérivée pour créer un autre processus léger.
Dans ce cas, il faut surcharger la méthode run()
pour y mettre le code exécuté par le processus léger.
Exemple :
public class MyThread extends Thread
{
public void run()
{
try
{
System.out.println("Un nouveau processus léger");
Thread.sleep(1000); // suspendu pendant 1 seconde
System.out.println("Fin du nouveau processus léger");
}
catch(InterruptedException ex)
{
System.out.println("Processus léger interrompu");
}
}
}
Il est alors créé et démarré de la manière suivante :
MyThread myth=new MyThread();
System.err.println("Démarrer le processus léger ...");
myth.start();
System.err.println("Le processus léger est démarré.");
Il n'est pas toujours possible d'étendre la classe Thread
car Java n'autorise qu'une classe de base.
Mais il est permis d'utiliser plusieurs interfaces.
L'interface Runnable
permet de résoudre le problème.
Par défaut, la méthode run()
de la classe Thread
appelle la méthode run()
de l'interface Runnable
passé en paramètre du constructeur.
Exemple :
public class MyClass extends AnotherClass
implements Runnable
{
public void run()
{
try
{
System.out.println("Un nouveau processus léger");
Thread.sleep(1000); // suspendu pendant 1 seconde
System.out.println("Fin du nouveau processus léger");
}
catch(InterruptedException ex)
{
System.out.println("Processus léger interrompu");
}
}
}
Le processus léger est alors créé et démarré de la manière suivante :
MyClass myclass=new MyClass ();
Thread th=new Thread(myclass); // <-- processus léger créé
System.err.println("Démarrer le processus léger ...");
th.start();
System.err.println("Le processus léger est démarré.");
Actions sur un processus léger
[modifier | modifier le wikicode]Cycle de vie d'un processus léger
[modifier | modifier le wikicode]Un processus léger possède différents états gérés par le système :
- état prêt : le processus est prêt à être exécuté,
- état suspendu : le processus est suspendu (attente d'une ressource),
- état exécution : le processus est en cours d'exécution,
- état terminé : le processus a achevé son exécution ou a été interrompu.
InterruptedException
[modifier | modifier le wikicode]Cette classe d'exception est lancée par les méthodes de la classe Thread
et celle de la classe Object
demandant la suspension pour un temps indéterminé du processus léger courant (attente en général).
Cette exception est lancée quand le processus léger en attente est interrompu. Capturer cette exception permet d'interrompre l'attente, et libérer des ressources pour terminer proprement.
Attendre la fin d'un processus léger
[modifier | modifier le wikicode]La méthode join()
de la classe Thread
peut être appelée pour attendre la fin d'un processus léger.
Exemple :
th.join(); // InterruptedException à capturer
Interrompre un processus léger
[modifier | modifier le wikicode]La méthode interrupt()
de la classe Thread
peut être appelée pour interrompre un processus léger.
Cette méthode provoque le lancement d'une exception de type InterruptedException
quand le processus appelle une méthode d'attente.
Synchronisation
[modifier | modifier le wikicode]La synchronisation devient nécessaire quand plusieurs processus légers accèdent aux mêmes objets.
mot-clé synchronized
[modifier | modifier le wikicode]Le mot-clé synchronized
permet un accès exclusif à un objet.
La syntaxe est la suivante :
... code non protégé ...
synchronized(objet)
{
... code protégé ...
}
... code non protégé ...
Le code protégé n'est exécuté que par un seul processus léger à la fois, tant qu'il n'a pas terminé le bloc d'instruction.
Durant l'exécution de ce code protégé par un processus léger, un autre processus léger ne peut exécuter celui-ci, mais peut exécuter un autre bloc synchronized
si celui-ci n'utilise pas le même objet et qu'il n'est pas déjà en cours d'exécution.
Le mot-clé synchronized
peut également être utilisé dans la déclaration des méthodes :
public synchronized void codeProtege()
{
... code protégé ...
}
est équivalent à :
public void codeProtege()
{
synchronized(this)
{
... code protégé ...
}
}
Pour une méthode statique (méthode de classe) :
public class MyClass
{
public synchronized static void codeProtege()
{
... code protégé ...
}
}
est équivalent à :
public class MyClass
{
public static void codeProtege()
{
synchronized(MyClass.class)
{
... code protégé ...
}
}
}
Attente et signal
[modifier | modifier le wikicode]Quand le mot-clé synchronized
ne suffit pas (par exemple, permettre l'accès à deux processus légers simultanément au lieu d'un seul), il est possible de suspendre un processus léger et le réveiller.
La classe Object
possède les méthodes suivantes :
wait()
suspend le processus courant jusqu'à ce que la méthodenotify()
ounotifyAll()
de cet objet soit appelée ;wait(long timeout)
suspend le processus courant jusqu'à ce que la méthodenotify()
ounotifyAll()
de cet objet soit appelée, ou bien que le temps indiqué soit écoulé ;notify()
réveille l'un des processus en attente de cet objet,notifyAll()
réveille tous les processus en attente de cet objet.
Pour appeler l'une de ces quatre méthodes, il faut posséder l'objet.
Ce qui signifie utiliser l'instruction synchronized
.
Dans le cas contraire, l'exception suivante est levée :
java.lang.IllegalMonitorStateException: current thread not owner
Exemple :
synchronized(myobj)
{
myobj.wait();
}
Comme ces méthodes sont définies dans la classe Object
, il est possible de les utiliser avec n'importe quel type d'objet, et donc les chaînes de caractères et les tableaux.
Une bibliothèque spécialisée
[modifier | modifier le wikicode]Il est très courant d'être amené à protéger l'accès à une variable pour juste par exemple, une incrémentation sous condition, comparer et échanger deux valeurs, chercher et ajouter. Ces méthodes sont qualifiées d'atomiques. La solution la plus efficace consiste à utiliser les classes de la bibliothèque java.util.concurrent. Ces classes disposent de méthodes considérées comme toujours plus rapides en mode multi-thread que celles d'une liste synchronisée, prenons par exemple le cas des listes :
String str;
Map<String,String> map=new HashMap<String,String>();
map.put("toto", "valeur exemple 1");
map.put("titi", "valeur exemple 1");
...
synchronized(map) { str=map.get("tutu");}
...
synchronized(map)
{
if (!map.containsKey("titi"))
map.put("titi", "valeur exemple 3");
}
Un autre thread ne peut pas effectuer de modification entre containsKey et put, du fait du verrou pris par l'instruction synchronized sur l'objet map. Mais cette solution est en réalité peu efficace car elle contraint le plus souvent à protéger tous les accès à la liste, en lecture comme en écriture, alors que les accès en lectures multiples n'ont pas à être bloquants, seuls ceux en écriture devant l'être. La classe ConccurentHashMap possède un putIfAbsent. Il est possible de réimplanter cette caractéristique via la synchronisation sur deux valeurs, mais les classes fournies par java.util.concurrent sont, elles, exemptes de bugs.
Si la classe de liste ne vous satisfait pas, la pose de verrou via la classe ReentrantReadWriteLock est extrêmement simple.
Méthodes natives
Une classe peut posséder des méthodes natives. Une méthode native n'est pas implémentée en Java mais dans un autre langage de programmation (C ou C++ le plus souvent).
Le mot-clé native
marque les méthodes natives d'une classe Java.
Elles n'ont pas de corps d'implémentation (comme les méthodes abstraites).
L'outil javah du JDK permet de générer l'en-tête C/C++ (*.h) correspondant aux méthodes natives d'une classe.
Pour plus de détails, voir Développer en Java/Faire appel à du code natif.
Réflexion
La réflexion (reflection en anglais) permet l'introspection des classes, c'est-à-dire de charger une classe, d'en créer une instance et d'accéder aux membres statiques ou non (appel de méthodes, lire et écrire les attributs) sans connaître la classe par avance.
Java possède une API permettant la réflexion. Elle sert notamment à gérer des extensions (plug-in en anglais) pour une application.
L'API de réflexion
[modifier | modifier le wikicode]Le package java.lang
possède trois classes utilisées pour l'utilisation dynamique des classes :
java.lang.Class
- Cette classe permet d'accéder aux caractéristiques d'une classe, à ses membres (méthodes et attributs), à la classe mère.
java.lang.ClassLoader
- Cette classe permet de gérer le chargement de classe. Il existe des sous-classes dont notamment
java.net.URLClassLoader
permettant de charger des classes en les cherchant dans une liste d'URLs (donc de fichiers JAR et répertoires également, en convertissant le chemin du fichier ou répertoire en URL). java.lang.Package
- Cette classe permet d'accéder aux informations d'un package (informations de version, annotations, ...).
Les autres classes utiles sont définies dans le package java.lang.reflect
et permettent d'accéder aux détails d'une classe.
Les principales classes sont les suivantes :
java.lang.reflect.Constructor
- Référence à un constructeur d'une classe.
java.lang.reflect.Method
- Référence à une méthode d'une classe.
java.lang.reflect.Field
- Référence à un champ d'une classe.
java.lang.reflect.Modifier
- Attributs et méthodes statiques pour décoder les modificateurs des membres (public, private, protected, static, abstract, final, native, ...).
Les classes représentant des membres d'une classe (Constructor
, Method
, Field
) implémentent toutes l'interface java.lang.reflect.Member
comportant les méthodes suivantes :
Class getDeclaringClass()
- Retourne la classe définissant ce membre.
String getName()
- Retourne le nom.
int getModifiers()
- Retourne les modificateurs (public, protected, private, static, final, ...).
boolean isSynthetic()
- Teste si ce membre a été généré par le compilateur.
Charger une classe dynamiquement
[modifier | modifier le wikicode]La classe java.lang.Class
possède deux méthodes statiques pour obtenir une classe (après chargement si nécessaire) :
static Class forName(String name)
- Cette méthode équivaut à appeler la seconde méthode avec pour paramètres :
(name, true, this.getClass().getClassLoader())
. static Class forName(String name, boolean initialize, ClassLoader loader)
- Charge la classe dont le nom complet (incluant les packages) est spécifié, en utilisant l'instance du chargeur de classe fourni. Le paramètre
initialize
vauttrue
pour initialiser la classe (appeler le bloc d'initialisation statique), oufalse
pour ne pas l'initialiser.
Il est également possible d'obtenir une java.lang.Class
de manière statique :
- à partir d'un objet en appelant la méthode
getClass()
, - à partir d'une référence à la classe en utilisant le champ
class
.
Exemple :
package org.wikibooks.fr;
public class Exemple
{
String nom;
public String getNom()
{ return nom; }
}
...
Class c = Class.forName("org.wikibooks.fr.Exemple"); // sans référence statique à la classe
// ou
Class c = org.wikibooks.fr.Exemple.class; // référence statique à la classe
// ou
Class c = new Exemple().getClass(); // référence statique à la classe
Liste des membres d'une classe
[modifier | modifier le wikicode]Les méthodes suivantes permettent de lister les membres d'une classe :
getConstructors()
- Cette méthode retourne un tableau de
java.lang.reflect.Constructor
contenant tous les constructeurs définis par la classe. getMethods()
- Cette méthode retourne un tableau de
java.lang.reflect.Method
contenant toutes les méthodes définies par la classe. getFields()
- Cette méthode retourne un tableau de
java.lang.reflect.Field
contenant tous les attributs définis dans la classe.
Les méthodes ci-dessus retournent les membres publics de la classe, comprenant également ceux hérités des classes mères. Il existe une variante "Declared" de ces méthodes retournant tous les membres (publics, protégés, privés) déclarés par la classe uniquement (les membres hérités sont exclus).
Au lieu de lister tous les membres, puis en rechercher un en particulier, il est possible d'utiliser les méthodes spécifiques de recherche d'un membre précis d'une classe (publics et hérités, ou bien "Declared" pour tous ceux déclarés par la classe seule) :
getConstructor(Class... parameterTypes)
getDeclaredConstructor(Class... parameterTypes)
- Cette méthode retourne le constructeur déclaré avec les paramètres dont les types sont spécifiés.
getMethod(String name, Class... parameterTypes)
getDeclaredMethod(String name, Class... parameterTypes)
- Cette méthode retourne la méthode portant de nom spécifié et déclarée avec les paramètres dont les types sont spécifiés.
getField(String name)
getDeclaredField(String name)
- Cette méthode retourne l'attribut portant de nom spécifié.
Les membres retournés par toutes ces méthodes peuvent être d'instance ou statiques.
La méthode getAnnotations()
retourne un tableau de java.lang.Annotation
contenant toutes les annotations associées à la classe.
Instancier une classe et appel à un constructeur
[modifier | modifier le wikicode]La méthode newInstance()
de la classe java.lang.Class
permet de créer une nouvelle instance de la classe, en appelant le constructeur sans paramètre de la classe (qui doit donc en posséder un) :
Class c = Class.forName("org.wikibooks.fr.Exemple");
Object o = c.newInstance(); // équivaut à new org.wikibooks.fr.Exemple();
Une classe comme celle ci-dessous peut ne pas avoir de constructeur sans paramètres :
package org.wikibooks.fr;
public class Livre
{
String titre;
int nb_pages;
public Livre(String titre, int nb_pages)
{
this.titre = titre;
this.nb_pages = nb_pages;
}
}
Dans ce cas, il faut d'abord obtenir le constructeur, puis l'appeler :
Class c = Class.forName("org.wikibooks.fr.Livre"); // Accès à la classe Livre
Constructor constr = c.getConstructor(String.class, int.class); // Obtenir le constructeur (String, int)
Object o = constr.newInstance("Programmation Java", 120); // -> new Livre("Programmation Java", 120);
Pour les versions de Java antérieures à 5.0 où l'auto-boxing n'existe pas, et où il faut explicitement utiliser des tableaux :
Class c = Class.forName("org.wikibooks.fr.Livre"); // Accès à la classe Livre
Constructor constr = c.getConstructor(new Class[]{ String.class, Integer.TYPE }); // Obtenir le constructeur (String, int)
Object o = constr.newInstance(new Object{ "Programmation Java", Integer.valueOf(120) }); // -> new Livre("Programmation Java", 120);
Appel à une méthode
[modifier | modifier le wikicode]L'appel à une méthode de la classe est basé sur le même principe que l'appel à un constructeur vu juste avant.
Cependant, pour obtenir la référence à une méthode, il faut spécifier le nom.
Lors de l'invocation de la méthode, il faut spécifier l'instance (l'objet) auquel s'applique la méthode (null
pour une méthode statique).
Exemple :
package org.wikibooks.fr;
public class Livre
{
String titre;
int nb_pages;
public Livre(String _titre, int _nb_pages) {
this.titre = _titre;
this.nb_pages = _nb_pages;
}
public int getNombreDeFeuilles(int pages_par_feuille)
{
return (nb_pages+pages_par_feuille-1)/pages_par_feuille;
}
}
...
Class c = Class.forName("org.wikibooks.fr.Livre"); // Accès à la classe Livre
Constructor constr = c.getConstructor(String.class, int.class); // Obtenir le constructeur (String, int)
Object o = constr.newInstance("Programmation Java", 120); // -> new Livre("Programmation Java", 120);
Method method = c.getMethod("getNombreDeFeuilles", int.class); // Obtenir la méthode getNombreDeFeuilles(int)
int nb_feuilles = (int)method.invoke(o, 2); // -> o.getNombreDeFeuilles(2);
Accès à un attribut public
[modifier | modifier le wikicode]L'accès à un attribut public se fait en appelant les méthodes sur l'instance de java.lang.reflect.Field
obtenu auprès de la classe.
Exemple :
package org.wikibooks.fr;
public class Livre
{
public String titre;
public int nb_pages;
public Livre(String _titre, int _nb_pages) {
this.titre = _titre;
this.nb_pages = _nb_pages;
}
}
...
Class c = Class.forName("org.wikibooks.fr.Livre"); // Accès à la classe Livre
Constructor constr = c.getConstructor(String.class, int.class); // Obtenir le constructeur (String, int)
Object o = constr.newInstance("Programmation Java", 120); // -> new Livre("Programmation Java", 120);
Field f_titre = c.getField("titre"); // Obtenir l'attribut titre
String titre_du_livre = (String)f_titre.get(o); // -> o.titre
f_titre.set(o, "Java"); // -> o.titre = "Java";
Accès à un attribut privé
[modifier | modifier le wikicode]Il est également possible d'avoir accès aux champs privés grâce à la méthode setAccessible de la classe Field. Cela est cependant fortement déconseillé puisque modifier les valeurs d'un champs privée revient à violer le principe d'encapsulation.
Exemple :
package org.wikibooks.fr;
public class Livre
{
private String titre;
private int nb_pages;
public Livre(String _titre, int _nb_pages) {
this.titre = _titre;
this.nb_pages = _nb_pages;
}
}
...
Class c = Class.forName("org.wikibooks.fr.Livre"); // Accès à la classe Livre
Constructor constr = c.getConstructor(String.class, int.class); // Obtenir le constructeur (String, int)
Object o = constr.newInstance("Programmation Java", 120); // -> new Livre("Programmation Java", 120);
Field f_titre = c.getField("titre"); // Erreur: titre est privé
Field fields[] = c.getDeclaredFields();
fields[0].setAccessible(true); // titre désormais équivalent à un attribut publique
Field f_titre = fields[0]; // Obtenir l'attribut titre
String titre_du_livre = (String)f_titre.get(o); // -> o.titre
f_titre.set(o, "Java"); // -> o.titre = "Java";
Exemple concret : un gestionnaire d'extensions
[modifier | modifier le wikicode]L'extension d'une application Java peut se faire en utilisant la réflexion pour charger dynamiquement une classe. Le but de cet exemple est de permettre d'ajouter des fonctionnalités dynamiquement à une application, sans avoir à effectuer de changement autre que l'ajout d'une nouvelle archive JAR dans un répertoire.
Par défaut, une application est composée de plusieurs classes dont le chargement est effectué par la JVM.
Cependant, il faut que les chemins des répertoires de fichiers *.class
ou des archives *.jar
soit renseignés d'avance avant le lancement de l'application dans le classpath.
Chargeur de classes
[modifier | modifier le wikicode]Supposons que l'application doive être capable de charger des extensions fournies sous forme de fichiers *.jar
situés dans un sous-répertoire nommé « ext ».
Quand l'application est lancée, le classpath est déjà configuré pour les classes de l'application, mais ne contient pas le répertoire ext.
Mais comme les classes ne sont pas directement dans le répertoire, la JVM a besoin du chemin de chaque fichier *.jar
dans le classpath.
L'application doit donc lister les fichiers *.jar
du répertoire ext pour construire un classpath passé au constructeur d'un nouveau chargeur de classes.
public static ClassLoader creerChargeurArchives(File dir) throws IOException
{
if (!dir.isDirectory()) throw new FileNotFoundException("Répertoire non trouvé");
File[] fichiers = dir.listFiles();
ArrayList<URL> urls_fichiers = new ArrayList<>();
// Ajouter les fichiers *.jar
for(File f : fichiers)
{
if (f.isFile() && f.getName().toLowerCase().endsWith(".jar"))
urls_fichiers.add(f.toURI().toURL());
}
// Créer le chargeur de classes pour les URLS de fichiers *.jar
return new URLClassLoader(urls_fichiers.toArray(new URL[urls_fichiers.size()]));
}
Il est possible de créer un chargeur de classes par fichier *.jar
.
Cependant, chaque chargeur de classe a un chargeur de classe parent, permettant aux classes chargées d'utiliser des classes gérées par le chargeur parent.
En l’occurrence il s'agit de celui des classes de l'application elle-même.
Cela signifie que les classes d'extensions peuvent utiliser les classes de l'application (classes de base, interfaces d'API, ...).
En utilisant un chargeur par fichier *.jar
, chaque extension ne peut donc pas utiliser les classes d'une autre extension.
En utilisant un seul chargeur pour tous les fichiers *.jar
, les extensions peuvent utiliser des classes d'autres extensions.
Cette dernière solution permet de créer des extensions utiles à d'autres extensions.
Toutefois l'inconvénient est que toutes les extensions sont chargées en mémoire même si elles ne sont pas utilisées. De plus, le nom complet des classes doit être unique.
Chargement des classes d'extension
[modifier | modifier le wikicode]Le chargement des classes d'extension doit utiliser le chargeur de classes des fichiers *.jar
trouvés dans le répertoire ext.
Chaque extension doit utiliser un nom de paquetage unique afin que deux classes n'aient pas le même nom absolu.
Il faut aussi connaître la liste de noms des classes à charger.
Cette liste peut être fixe en calculant un nom de paquetage unique à partir du nom du fichier archive JAR (donc une seule classe chargée), ou elle peut être lue à partir d'un fichier inclut dans le fichier JAR (par exemple, un fichier nommé extension.ini
à la racine du fichier JAR).
public static void chargerClasses(ClassLoader cl, File f_jar) throws IOException
{
JarFile jf = new JarFile(f_jar);
try
{
JarEntry je = jf.getJarEntry("extension.ini");
if (je != null)
{
InputStream in = jf.getInputStream(je);
try
{
// Lecture du fichier texte
BufferedReader br = new BufferedReader(new InputStreamReader(in));
String line;
while ((line = br.readLine()) != null)
{
// Enlever l'éventuel commentaire de fin de ligne
// commençant par le caractère #
int i = line.indexOf('#');
if (i>=0) line = line.substring(0,i);
line = line.trim(); // Sans les espaces autour
// Ne pas traiter les lignes vides
if (line.length()==0) continue;
// Traitement de la ligne
// Il peut s'agir simplement de la classe à instancier immédiatement :
// Class<? extends Object> c = cl.loadClass(line);
// c.newInstance();
// Ou d'une classe instanciée lorsqu'elle est utilisée dans un script ou
// une commande de l'application en utilisant un nom associé à la classe :
i = line.indexOf('='); // <nom> = <classe>
if (i>=0)
{
String nom = line.substring(0,i).trim();
String nom_classe = line.substring(i+1).trim();
// Associer le nom à la classe instanciée plus tard
Class<? extends Object> c = cl.loadClass(nom_classe);
map_extensions.put(nom, c);
}
}
}
finally{ in.close(); }
}
}
finally{ jf.close(); }
}
public static void chargerExtensions(File dir) throws IOException
{
// Créer le chargeur de classes
ClassLoader cl = creerChargeurArchives(dir);
File[] fichiers = dir.listFiles();
// Charger les classes de chaque fichier *.jar
for(File f : fichiers)
{
if (f.isFile() && f.getName().toLowerCase().endsWith(".jar"))
chargerClasses(cl, f);
}
}
Regex
En informatique, une expression régulière ou expression rationnelle ou expression normale ou motif, est une chaîne de caractères, qui décrit, selon une syntaxe précise, un ensemble de chaînes de caractères possibles. Les expressions régulières sont également appelées regex (de l'anglais regular expression). Elles sont issues des théories mathématiques des langages formels. Les expressions régulières sont aujourd’hui utilisées pour la lecture, le contrôle, la modification, et l'analyse de textes ainsi que la manipulation des langues formelles que sont les langages informatiques.
L'exemple d'expression régulière suivant permet de valider qu'une chaîne de caractère correspond à la syntaxe d'un nombre entier non signé, c'est à dire une suite non vide de chiffres :
[0-9]+
En détails :
- Les crochets spécifient l'ensemble des caractères auquel doit appartenir le caractère courant de la chaîne. Dans cet exemple, l'ensemble est celui des chiffres de 0 à 9 inclus.
- Le caractère plus indique de répéter le motif précédent au moins une fois (suite non vide).
En Java, la validation d'une chaîne de caractères peut se faire en utilisant la méthode statique matches
de la classe java.util.regex.Pattern
:
import java.util.regex.*;
public class Exemple
{
public static void main(String[] args)
{
System.out.println(Pattern.matches("[0-9]+", "12345")); // true
System.out.println(Pattern.matches("[0-9]+", "12ABC")); // false
}
}
Syntaxe
[modifier | modifier le wikicode]Les expressions rationnelles peuvent être analysées et testées via un débogueur en ligne comme https://regex101.com/.
Caractère | Type | Explication |
---|---|---|
.
|
Point | N'importe quel caractère |
[...]
|
crochets | classe de caractères : tous les caractères énumérés dans la classe, avec possibilité de plages dont les bornes sont séparées par "-". Ex : [0-9a-z] pour tout l'alphanumérique en minuscule, ou [0-Z] pour tous les caractères de la table Unicode entre "0" et "Z", c'est-à-dire l'alphanumérique majuscule plus ":;<=>?@"[1].
|
[^...]
|
crochets et circonflexe | classe complémentée : tous les caractères sauf ceux énumérés. |
[...[...]]
|
union | Union des deux ensembles |
[...&&[...]]
|
intersection | Intersection des deux ensembles |
^
|
circonflexe | Marque le début de la chaîne ou de la ligne. |
$
|
dollar | Marque la fin de la chaîne ou de la ligne. |
|
|
barre verticale | Alternative - ou reconnaît l'un ou l'autre |
(...)
|
parenthèses | groupe de capture : utilisé pour limiter la portée d'un masque ou de l'alternative, grouper un motif répété ou capturer une séquence |
\n
|
référence | Même séquence que celle capturée précédemment par le nème groupe de capture |
(?<nom>pattern)
|
Sous-motif nommé | Nomme le résultat d'un groupe de capture par un nom. |
\k<nom>
|
référence | Même séquence que celle capturée précédemment par le groupe de capture nommé nom. |
Par défaut, les caractères et groupes ne sont pas répétés. Les quantificateurs permettent de spécifier le nombre de répétitions et sont spécifiés immédiatement après le caractère ou groupe concerné.
Caractère | Type | Explication |
---|---|---|
*
|
astérisque | 0, 1 ou plusieurs occurrences |
+
|
plus | 1 ou plusieurs occurrences |
?
|
interrogation | 0 ou 1 occurrence |
{...}
|
accolades | nombre de répétitions : spécifie le nombre de répétitions du motif précédent (minimum et maximum). Avec la présence de la virgule, quand le minimum est absent la valeur par défaut est zéro, quand le maximum est absent la valeur pas défaut est l'infini. Sans virgule (un seul nombre) il s'agit du nombre exact (minimum et maximum ont la même valeur). Exemples :
|
Par défaut les quantificateurs ne recherchent pas forcément la plus longue séquence de répétition possible. Il est possible de les suffixer avec un caractère pour modifier leur comportement.
Caractère | Type | Explication |
---|---|---|
?
|
réticent | Le quantificateur qui précède recherchera la plus petite séquence possible. |
+
|
possessif | Le quantificateur qui précède recherchera la plus grande séquence possible. |
Remarques :
- Les caractères de début et fin de chaîne (
^
et$
) ne fonctionnent pas dans[]
où ils ont un autre rôle. - Les opérateurs
*
et+
sont toujours avides, pour qu'ils laissent la priorité il faut leur apposer un?
à leur suite[2].
Classe | Signification |
---|---|
[[:alpha:]]
|
n'importe quelle lettre |
[[:digit:]]
|
n'importe quel chiffre |
[[:xdigit:]]
|
caractères hexadécimaux |
[[:alnum:]]
|
n'importe quelle lettre ou chiffre |
[[:space:]]
|
n'importe quel espace blanc |
[[:punct:]]
|
n'importe quel signe de ponctuation |
[[:lower:]]
|
n'importe quelle lettre en minuscule |
[[:upper:]]
|
n'importe quelle lettre capitale |
[[:blank:]]
|
espace ou tabulation |
[[:graph:]]
|
caractères affichables et imprimables |
[[:cntrl:]]
|
caractères d'échappement |
[[:print:]]
|
caractères imprimables exceptés ceux de contrôle |
Expression | Signification |
---|---|
\\ |
Antislash |
\C |
Caractère spécial C non interprété : [ ] { } ( ) ? * . : \ & - ^ $
|
\Q...\E |
Séquence littérale non interprétée |
\0xxx |
Caractère Unicode (1 à 3 chiffres octaux) |
\a |
Alarme (ASCII 07) |
\A |
Début de chaîne |
\b |
Caractère de début ou fin de mot |
\B |
Caractère qui n'est pas début ou fin de mot |
\cX |
Caractère de contrôle ASCII (X étant une lettre) |
\d |
Chiffre |
\D |
Non chiffre |
\e |
Escape (ASCII 1B) |
\f |
Form-feed (ASCII 0C) |
\G |
Fin de la correspondance précédente |
\h |
Espace blanc horizontal [ \t\xA0\u1680\u180e\u2000-\u200a\u202f\u205f\u3000]
|
\H |
Non espace blanc horizontal [^\h]
|
\n |
Fin de ligne |
\pL , \p{L} , \p{Letter} |
Lettre (dans tout langage) |
\r |
Retour charriot |
\R |
Retour à la ligne, équivaut à \u000D\u000A|[\u000A\u000B\u000C\u000D\u0085\u2028\u2029]
|
\s |
Caractères espace [ \t\n\x0B\f\r]
|
\S |
Non caractères espace [^\s]
|
\t |
Tabulation |
\uxxxx |
Caractère Unicode (4 chiffres hexadécimaux) |
\v |
Espace blanc vertical [\n\x0B\f\r\x85\u2028\u2029]
|
\V |
Non espace blanc vertical [^\v]
|
\w |
Caractère alphanumérique : lettre, chiffre ou underscore |
\W |
Caractère qui n'est pas lettre, chiffre ou underscore |
\xxx |
Caractère Unicode (2 chiffres hexadécimaux) |
\x{xx...x} |
Caractère Unicode (chiffres hexadécimaux) |
\z |
Fin de chaîne |
Constructeurs spéciaux : Ces fonctions précèdent l'expression à laquelle elles s'appliquent, et le tout doit être placé entre parenthèses.
?:
: groupe non capturant. Ignorer le groupe de capture lors de la numérotation des backreferences. Exemple :((?:sous-chaine_non_renvoyée|autre).*)
.- La présence d'un groupe capturant peut engendrer une allocation mémoire supplémentaire. Si une expression régulière particulièrement complexe provoque une erreur de mémoire, essayez de remplacer les groupes capturant non référencés et inutilisés par des groupes non-capturant en ajoutant
?:
juste après la parenthèse ouvrante, et en décalant les numéros des groupes référencés.
- La présence d'un groupe capturant peut engendrer une allocation mémoire supplémentaire. Si une expression régulière particulièrement complexe provoque une erreur de mémoire, essayez de remplacer les groupes capturant non référencés et inutilisés par des groupes non-capturant en ajoutant
?>
: groupe non capturant indépendant.?<=
: positive lookbehind, vérifier (sans consommer) que ce qui précède correspond au motif spécifié. Exemple :- Chercher une lettre u précédée d'une lettre q :
(?<=q)u
- Chercher une lettre u précédée d'une lettre q :
?<!
: negative lookbehind, vérifier (sans consommer) que ce qui précède ne correspond pas au motif spécifié.?=
: positive lookahead, vérifier (sans consommer) que ce qui suit correspond au motif spécifié.?!
: negative lookahead, vérifier (sans consommer) que ce qui suit ne correspond pas au motif spécifié. Exemples :
Options :
Les options d'interprétation sont en général spécifiées à part. Mais certaines API ne permettent pas de les spécifier. Il est possible d'insérer ces options dans l'expression régulière[7].
(?optionsactivées-optionsdésactivées)
Exemples :
- Chercher un mot composé de voyelles sans tenir compte de la casse :
(?i)[AEIOUY]+
- Chercher un mot composé de voyelles en tenant compte de la casse, ici en majuscules :
(?-i)[AEIOUY]+
Les options s'appliquent à partir de leur position dans l'expression et se termine en fin de groupe. Exemple :
- Chercher un mot composé de voyelles sans tenir compte de la casse, entre deux autres en majuscules :
[AEIOUY]+(?i)[AEIOUY]+(?-i)[AEIOUY]+
- Plutôt qu'activer des options pour un groupe puis les désactiver, il est possible de faire un groupe non capturant :
[AEIOUY]+(?i:[AEIOUY]+)[AEIOUY]+
Les expressions rationnelles en Java nécessitent le package java.util.regex.
Recherches
[modifier | modifier le wikicode]La classe Pattern offre la fonction matches qui renvoie un booléen : true (vrai) si la chaîne complète correspond à l'expression régulière, false (faux) sinon.
import java.util.regex.Pattern;
public class Regex
{
public static void main(String[] args)
{
String chaine1 = "Test regex Java pour Wikibooks francophone.";
System.out.println(Pattern.matches("[a-z]* Wikibooks",chaine1));
System.out.println(Pattern.matches("[a-zA-Z ]* francophone\\.",chaine1));
}
}
/*
Affiche :
false
true
*/
La classe Matcher permet de trouver les résultats d'une expression avec différentes méthodes :
- find() : cherche le motif suivant et retourne un booléen indiquant si le motif défini par l'expression régulière a été trouvé.
- group() : retourne la chaîne trouvée (groupe 0).
- group(int) : retourne le groupe d'index spécifié. Le groupe 0 correspond à la chaîne complète, les suivants correspondent à la paire de parenthèses capturante dans l'expression régulière.
Pattern.quote(ma_chaine);
.L'exemple ci-dessous affiche tous les mots en gras qu'il trouve dans l'extrait de texte au format HTML suivant :
Test <b>regex</b> <i>Java</i> pour <b>Wikibooks</b> francophone.
import java.util.regex.Pattern;
import java.util.regex.Matcher;
public class TestRegex
{
public static void main(String[] args)
{
String chaine = "Test <b>regex</b> <i>Java</i> pour <b>Wikibooks</b> francophone.";
Pattern p = Pattern.compile("<b>([^<]+)</b>"); // Capture du contenu entre <b> et </b> (groupe 1)
Matcher m = p.matcher(chaine);
while (m.find())
{
System.out.println(m.group()); // Tout le motif
System.out.println(m.group(1)); // Le contenu entre <b> et </b>
}
}
}
Affiche :
<b>regex</b> regex <b>Wikibooks</b> Wikibooks
Remplacements
[modifier | modifier le wikicode]On peut utiliser la méthode String.replaceAll()
. Exemple pour les retirer les espaces : ma_chaine.replaceAll("\\s+", "")
.
Par défaut elle ne remplace pas les caractères non-ASCII (ex "é"). Depuis Java 7 il vaut donc mieux utiliser Matcher.replaceAll()
.
Exemple avec Matcher :
import java.util.regex.Pattern;
import java.util.regex.Matcher;
public class Regex {
public static void main(String[] args) {
String chaine = "Test <b>regexé</b> <i>Java</i> pour <b>Wikibooks</b> francophone.";
Pattern p = Pattern.compile("<b>([^<]+)</b>");
Matcher m = p.matcher(chaine);
System.out.println(m.replaceAll(""));
}
}
/* Affiche :
Test <i>Java</i> pour francophone.
*/
Références
[modifier | modifier le wikicode]
JUnit
Ici, nous allons présenter succinctement JUnit afin d'illustrer l'utilisation d'un framework de type xUnit. Même si on ne pratique pas Java au quotidien, cela permet de voir les principes généraux qu'on retrouve dans toutes les implémentations de ce framework. Nous n'allons pas nous étendre plus longtemps sur la question étant donné que de nombreux tutoriels sont disponibles sur la Toile pour les différents langages.
JUnit 4 requiert Java 1.5 car il utilise les annotations. JUnit 4 intègre JUnit 3 et peut donc lancer des tests écrits en JUnit 3.
Écrire un test
[modifier | modifier le wikicode]Avec JUnit, on va créer une nouvelle classe pour chaque classe testée. On crée autant de méthodes que de tests indépendants : imaginez que les tests peuvent être passés dans n'importe quel ordre (i.e. les méthodes peuvent être appelées dans un ordre différent de celui dans lequel elles apparaissent dans le code source). Il n'y a pas de limite au nombre de tests que vous pouvez écrire. Néanmoins, on essaye généralement d'écrire au moins un test par méthode de la classe testée.
Pour désigner une méthode comme un test, il suffit de poser l'annotation @Test
.
import static org.junit.Assert.*;
import org.junit.Test;
public class StringTest {
@Test
public void testConcatenation() {
String foo = "abc";
String bar = "def";
assertEquals("abcdef", foo + bar);
}
@Test
public void testStartsWith() {
String foo = "abc";
assertTrue(foo.startsWith("ab"));
}
}
Les assertions
[modifier | modifier le wikicode]Via import static org.junit.Assert.*;
, vous devez faire appel dans les tests aux méthodes statiques assertTrue, assertFalse, assertEquals, assertNull, fail, etc. en fournissant un message :
?
Votre environnement de développement devrait vous permettre de découvrir leurs signatures grâce à l'auto-complétion. À défaut, vous pouvez toutes les retrouver dans la documentation de l'API JUnit[8].
Attention à ne pas confondre les assertions JUnit avec « assert
». Ce dernier est un élément de base du langage Java[9]. Par défaut, les assertions Java sont ignorées par la JVM à moins de préciser -ea
au lancement de la JVM.
Lancer les tests
[modifier | modifier le wikicode]Pour lancer les tests, vous avez plusieurs possibilités selon vos préférences :
- La plus courante : lancer les tests depuis votre IDE (ex : alt + F6 dans NetBeans).
- Utiliser l'outil graphique.
- Lancer les tests en ligne de commande.
- Utiliser un système de construction logiciel (comme Ant ou Maven pour Java. Ex :
mvn test
pour tous les test,mvn -Dtest=MaClasseDeTest#MaMethodDeTest
test pour un seul).
Factoriser les éléments communs entre tests
[modifier | modifier le wikicode]On peut déjà chercher à factoriser les éléments communs à tous les tests d'une seule classe. Un test commence toujours par l'initialisation de quelques instances de formes différentes pour pouvoir tester les différents cas. C'est souvent un élément redondant des tests d'une classe, aussi, on peut factoriser tout le code d'initialisation commun à tous les tests dans une méthode spéciale, qui sera appelée avant chaque test pour préparer les données.
Si on considère l'exemple ci-dessus, cela donne :
import static org.junit.Assert.*;
import org.junit.Test;
import org.junit.*; //or import org.junit.Before; + import org.junit.After;
public class StringTest {
private String foo;
private String bar;
@Before // avec cette annotation, cette méthode sera appelée avant chaque test
public void setup() {
foo = "abc";
bar = "def";
}
@After
public void tearDown() {
// dans cet exemple, il n'y a rien à faire mais on peut,
// dans d'autres cas, avoir besoin de fermer une connexion
// à une base de données ou de fermer des fichiers
}
@Test
public void testConcatenation() {
assertEquals("abcdef", foo + bar);
}
@Test
public void testStartsWith() {
assertTrue(foo.startsWith("ab"));
}
}
Remarquons que la méthode annotée @Before, est exécutée avant chaque test, ainsi, chaque test est exécuté avec des données saines : celles-ci n'ont pu être modifiées par les autres tests.
On peut également souhaiter factoriser des éléments communs à plusieurs classes de tests différentes, par exemple, écrire un test pour une interface et tester les différentes implémentations de ces interfaces. Pour cela, on peut utiliser l'héritage en écrivant les tests dans une classe de test abstraite ayant un attribut du type de l'interface et en écrivant ensuite une classe fille par implémentation à tester, chacune de ces classes filles ayant une méthode @Before différente pour initialiser l'attribut de la classe mère de façon différente.
Vérifier la levée d'une exception
[modifier | modifier le wikicode]Il peut être intéressant de vérifier que votre code lève bien une exception lorsque c'est pertinent. Vous pouvez préciser l'exception attendue dans l'annotation @Test. Le test passe si une exception de ce type a été levée avant la fin de la méthode. Le test est un échec si l'exception n'a pas été levée. Vous devez donc écrire du code qui doit lever une exception et pas qui peut lever une exception.
@Test(expected = NullPointerException.class)
public void methodCallToNullObject() {
Object o = null;
o.toString();
}
Désactiver un test temporairement
[modifier | modifier le wikicode]Au cours du processus de développement, vous risquez d'avoir besoin de désactiver temporairement un test. Pour cela, plutôt que de mettre le test en commentaire ou de supprimer l'annotation @Test
, JUnit propose l'annotation @Ignore
. En l'utilisant, vous serez informé qu'un test a été ignoré, vous pouvez en préciser la raison.
@Ignore("ce test n'est pas encore prêt")
@Test
public void test() {
// du code inachevé
}
Écrire de bon tests
[modifier | modifier le wikicode]Idéalement, les tests unitaires testent une classe et une seule et sont indépendants les uns des autres. En effet, si une classe A est mal codée et qu'elle échoue au test unitaire de A, alors B qui dépend de A (parce qu'elle manipule des instances de A ou hérite de A) va probablement échouer à son test alors qu'elle est bien codée. Grâce à cette propriété on assure, que si un test échoue, c'est bien la classe testée qui est fautive et pas une autre. Nous verrons qu'assurer cette propriété peut être complexe.
Selon les langages, on a souvent des conventions pour nommer les classes de tests. En Java, on choisit souvent d'appeler MaClassTest le test de la classe MaClass. Les tests peuvent être commentés si le déroulement d'un test est complexe.
Évidemment, un bon test assure la qualité de l'intégralité d'une classe, et pas seulement d'une partie de celle-ci. Nous verrons comment vérifier cela dans la partie « qualité des tests ».
Limites du test unitaire
[modifier | modifier le wikicode]Les classes abstraites
[modifier | modifier le wikicode]Comme l'écriture d'un test unitaire requiert d'instancier une classe pour la tester, les classe abstraites (qui sont par définition non-instantiables) ne peuvent être testées. Remarquons que cela n'empêche pas de tester les classes filles qui héritent de cette classe et qui sont concrètes.
Une solution consiste à créer, dans la classe de test, une classe interne héritant de la classe abstraite la plus simple possible mais qu'on définit comme concrète. Même si cette classe n'est pas représentative des classes filles qui seront finalement écrites, cela permet tout de même de tester du code de la classe abstraite.
Les attributs invisibles
[modifier | modifier le wikicode]Si un attribut est privé et qu'il ne propose pas d'accesseur public, il n'est pas possible de voir son état sans moyen détourné. Une façon consiste ici à créer une classe interne au test qui hérite de la classe à tester et y ajoute les accesseurs nécessaires. Il suffit alors de tester cette nouvelle classe.
Les méthodes privées
[modifier | modifier le wikicode]Comme vous ne pouvez pas faire appel à une méthode privée, vous ne pouvez vérifier son comportement facilement. Comme vous connaissez l'implémentation de la classe que vous testez (boîte blanche). Vous pouvez ruser et essayer de trouver une méthode publique qui fait appel à la méthode privée.
Une autre méthode, plus brutale et rarement utilisée, consiste à utiliser l'API de réflexion du langage (java.reflect pour Java) pour charger la classe, parcourir ses méthodes et toutes les passer en public.
Les classes ayant des dépendances
[modifier | modifier le wikicode]Une classe A qui dépend d'une autre classe B pour diverses raisons :
- A hérite de B
- A a un attribut de type B
- une méthode de A attend un paramètre de type B
Du point de vue UML, un trait relie A et B. Or, peut-être que B n'a pas encore été implémentée ou qu'elle n'est pas testée. En plus, il faut veiller à conserver l'indépendance des tests entre eux, et donc, que notre test de A ne dépende pas de la réussite des tests de B.
Pour cela, nous pouvons utiliser des doublures de test : elles permettent, le temps du test de A, de satisfaire les dépendances de A sans faire appel à B. De plus, l'utilisation d'une doublure permet de simuler un comportement de B vis-à-vis de A, qu'il serait peut-être très difficile à simuler avec la véritable classe B (par exemple, des cas d'erreurs rares comme une erreur de lecture de fichier).
À défaut, il faudrait pouvoir indiquer dans les tests qu'on écrit que la réussite du test de A dépend du test de B, parce que si les deux échouent : il ne faudrait pas chercher des erreurs dans A alors qu'elles se trouvent dans B. Ainsi, le framework, prenant en compte les dépendances, pourrait remonter à l'origine du problème sans tromper le développeur.
Des outils complémentaires
[modifier | modifier le wikicode]Nous avons vu quelques limitations que nous pouvons compenser par des astuces de programmation ou l'utilisation de doublures de tests que nous verrons prochainement dans ce livre. Toutefois, il existe des frameworks de tests qui essaient de combler les lacunes de xUnit. Ils méritent qu'on y jette un coup d'œil.
Citons l'exemple de TestNG, pour Java qui permet notamment d'exprimer des dépendances entre tests ou de former des groupes de tests afin de ne pouvoir relancer qu'une partie d'entre eux.
Le logiciel BlueJ représente les classes de tests sous la forme de rectangles verts, exécutables via un clic droit. Elles sont enregistrables à partir d'opérations effectuées à la souris, et héritent toujours de :
public class ClasseTest extends junit.framework.TestCase {
Ses méthodes de test doivent avoir un nom commençant par "test".
Références
[modifier | modifier le wikicode]- ↑ https://unicode-table.com/fr/
- ↑ https://docstore.mik.ua/orelly/webprog/pcook/ch13_05.htm
- ↑ https://www.regular-expressions.info/posixbrackets.html
- ↑ https://www.regular-expressions.info/unicode.html
- ↑ https://www.regextester.com/15
- ↑ Jan Goyvaerts, Steven Levithan, Regular Expressions Cookbook, O'Reilly Media, Inc., (lire en ligne)
- ↑ Les options sont appelées modificateurs (modifiers en anglais), voir https://www.regular-expressions.info/modifiers.html
- ↑ http://www.junit.org/apidocs/org/junit/Assert.html
- ↑ voir http://java.sun.com/j2se/1.4.2/docs/guide/lang/assert.html
Conclusion
Conclusion
[modifier | modifier le wikicode]Vous avez appris, au travers de la lecture de ce livre, les fondements du langage Java. La maîtrise de ces notions est indispensable pour produire des applications ou des bibliothèques convenables. Néanmoins, si vous voulez pleinement profiter des nombreuses autres possibilités offertes par Java, il faut dès maintenant se pencher sur les nombreuses facettes de Java.
Elles sont notamment présentées dans le wikilivre « Développer en Java » qui propose de découvrir de nombreux outils, bibliothèques, techniques et bonnes pratiques spécifiques à Java, telles que les Java Specification Requests (JSR) ou autre convention sur les bonnes pratiques[1].
Découvrez aussi le développement d'une interface graphique en Java avec les composants Swing, ou la récente bibliothèque JavaFX qui permet en plus l'affichage de scènes en 3D.
Références
[modifier | modifier le wikicode]
Générer un carré magique
Ce programme permet de générer des carrés magiques d'ordre impair.
import java.io.*;
/*
Nom : Carre.java
Rôle : Construction d'un carré magique d'ordre impair.
Compilation : javac Carre.java
Exécution : java Carre.java <ordre>
avec <ordre> : entier impair > 1
Résultat : dans un fichier texte nommé carre_ordre.txt
*/
public class Carre
{
public static void main(String args[])
throws Exception
{
int ordre, ligne, colonne;
int maxValue = (int)Math.sqrt((double)Integer.MAX_VALUE);
System.out.println("Début du programme.");
// test du paramètre obligatoire : > 1 et impair
if (args == null || args.length == 0)
{
System.out.println("Usage : java Carre <ordre>\n"+
"\tavec <ordre> : entier > 1 et impair.");
System.exit(1) ;
}
ordre = Integer.parseInt(args[0]);
if (ordre <= 1 || (ordre%2) == 0 || ordre > maxValue )
{
System.out.println(ordre + " n'est pas impair ou n'est pas supérieur a 1" +
" ou est trop grand : > " + maxValue) ;
System.exit(1) ;
}
System.out.println("Le paramètre " + ordre + " est correct : > 1 et impair");
// Création du tableau
int carre[][] = new int[ordre][ordre] ;
System.out.println("Début du calcul");
// Rangement 1er nombre n au milieu de la première ligne
// Puis rangement des autres nombres.
ligne = 0 ;
colonne = ordre/2 ;
for (int n=1; n<=(ordre*ordre); n++)
{
// Écriture dans le tableau dans la case calculée
carre[ligne][colonne] = n ;
// Détermination de la position de la prochaine case à écrire
if ((n%ordre) == 0)
{
// Si débordement à gauche du tableau
// Écriture dans la case sous le dernier nombre
ligne++ ;
}
else
{
// Écriture dans la case en haut à gauche
ligne = ((ligne == 0) ? ordre-1 : ligne-1);
colonne = ((colonne == 0) ? ordre-1 : colonne-1);
}
} // for (int n=1; n<=ordre*ordre; n++)
int sommeMagique = ordre * ( ordre * ordre + 1) / 2;
// Création du fichier résultat
String nomFic = "carre_" + ordre + ".txt";
System.out.println("Fin du calcul, écriture du fichier "+ nomFic + "...");
PrintWriter hFic = new PrintWriter(new BufferedWriter(new FileWriter(nomFic)));
// Impression du tableau
hFic.println("Carre magique d'ordre " + ordre);
for (ligne=0; ligne<ordre; ligne++)
{
for (colonne=0; colonne<ordre; colonne++)
{
hFic.print(carre[ligne][colonne] + " ");
}
hFic.println("") ;
}
hFic.println("La somme magique est " + sommeMagique);
hFic.close();
System.out.println("fin écriture, programme terminé.");
} // public static void main(String args[])
} // public class Carre
Générer un triangle de Sierpiński
Le programme ci-dessous dessine un triangle de Sierpiński dans une fenêtre en utilisant la bibliothèque Swing pour l'interface utilisateur.
La méthode récursive private void drawSierpinskiTriangle(Graphics g, int[] x, int[] y, int d)
accepte des coordonnées de trois points (coins du triangle principal) plus ou moins arbitraires.
Pour compiler (vous devez disposez du JDK Java) : javac SierpinskiTriangle.java
Source de SierpinskiTriangle.java :
/* Nom du fichier : SierpinskiTriangle.java */
package org.wikibooks.fr.swing;
import java.awt.*;
import javax.swing.*;
/**
* Triangle de Sierpiński
* @author fr.wikibooks.org
*/
public class SierpinskiTriangle extends JComponent
{
private final int size;
private final int d_min = 8;
public SierpinskiTriangle(int size)
{
this.size = size;
Dimension d = new Dimension(size, size);
setPreferredSize(d);
setMinimumSize(d);
}
@Override
protected void paintComponent(Graphics g)
{
g.setColor(getBackground());
g.clearRect(0, 0, size, size);
g.setColor(getForeground());
int x0 = 0; // distance de gauche
int y0 = 0; // distance du haut
int h = (int) (size * Math.sqrt(3) / 2); // hauteur
// adapté à un triangle équilatéral
// spécification du triangle principal: points A, B, C
int xA = x0, yA = y0 + h; // (bas-gauche)
int xB = x0 + size, yB = y0 + h; // (bas-droite)
// int xB=x0, yB=y0; // (haut-gauche)
// int xB=x0+d, yB=y0; // (haut-droite)
int xC = x0 + size / 2, yC = y0; // triangle équilatéral (haut-milieu)
// int xC=x0, yC=y0; // (haut-gauche)
// triangle perpendiculaire, angle droit près A
// int xC=x0+d, yC=y0; // (haut-droite)
// triangle perpendiculaire, angle droit près B
int[] x = { xA, xB, xC };
int[] y = { yA, yB, yC };
drawSierpinskiTriangle(g, x, y, size / 2); // démarrer la récursion
}
private void drawSierpinskiTriangle(Graphics g, int[] x, int[] y, int d)
{
if (d < d_min)
g.fillPolygon(x, y, 3); // fin de la récursion
else
{
// milieux des côtés du triangle:
int xMc = (x[0] + x[1]) / 2, yMc = (y[0] + y[1]) / 2;
int xMb = (x[0] + x[2]) / 2, yMb = (y[0] + y[2]) / 2;
int xMa = (x[1] + x[2]) / 2, yMa = (y[1] + y[2]) / 2;
int[] xNouveau1 =
{ x[0], xMc, xMb };
int[] yNouveau1 =
{ y[0], yMc, yMb };
drawSierpinskiTriangle(g, xNouveau1, yNouveau1, d / 2); // récursion
int[] xNouveau2 =
{ x[1], xMc, xMa };
int[] yNouveau2 =
{ y[1], yMc, yMa };
drawSierpinskiTriangle(g, xNouveau2, yNouveau2, d / 2); // récursion
int[] xNouveau3 =
{ x[2], xMb, xMa };
int[] yNouveau3 =
{ y[2], yMb, yMa };
drawSierpinskiTriangle(g, xNouveau3, yNouveau3, d / 2); // récursion
}
}
public static void main(String[] args)
{
JFrame f = new JFrame("Triangle de Sierpiński");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.getContentPane().add(new SierpinskiTriangle(1024));
f.pack();
f.setVisible(true);
}
}
XML
Il existe dans Java deux packages permettant de manipuler les documents XML :
- la manière SAX (Simple API for XML, navigation via un curseur) : une implémentation de l'interface
org.xml.sax.helpers.DefaultHandler
est notifiée de chaque balise lue. Aucune structure n'est construite en mémoire ; - et DOM (Document Object Model, navigation via un arbre) : une structure est construite au cours de la lecture, et peut être utilisée pour écrire un nouveau document XML.
Soit le document XML suivant :
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<Documentation>
<Site Wiki="Wikibooks">
<Livre Wikilivre="Java">
<Page1>Introduction</Page1>
<Page2>Bases du langage</Page2>
<Exemple>XML</Exemple>
</Livre>
</Site>
</Documentation>
Remarque : les espaces sont interdits dans les noms des balises car ils servent de séparateurs entre le nom et les attributs.
La méthode DOM
[modifier | modifier le wikicode]Le programme suivant génère ce fichier XML dans le même répertoire :
import java.io.File;
import javax.xml.parsers.*;
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.*;
public class XML_Creation_DOM
{
public static void main(String argv[])
{
try
{
DocumentBuilderFactory XML_Fabrique_Constructeur = DocumentBuilderFactory.newInstance();
DocumentBuilder XML_Constructeur = XML_Fabrique_Constructeur.newDocumentBuilder();
Document XML_Document = XML_Constructeur.newDocument();
Element documentation = XML_Document.createElement("Documentation");
XML_Document.appendChild(documentation);
Element site = XML_Document.createElement("Site");
documentation.appendChild(site);
Attr attribut1 = XML_Document.createAttribute("Wiki");
attribut1.setValue("Wikibooks");
site.setAttributeNode(attribut1);
Element livre = XML_Document.createElement("Livre");
site.appendChild(livre);
Attr attribut2 = XML_Document.createAttribute("Wikilivre");
attribut2.setValue("Java");
livre.setAttributeNode(attribut2);
Element page1 = XML_Document.createElement("Page1");
page1.appendChild(XML_Document.createTextNode("Introduction"));
livre.appendChild(page1);
Element page2 = XML_Document.createElement("Page2");
page2.appendChild(XML_Document.createTextNode("Bases du langage"));
livre.appendChild(page2);
Element example = XML_Document.createElement("Exemple");
example.appendChild(XML_Document.createTextNode("XML"));
livre.appendChild(example);
TransformerFactory XML_Fabrique_Transformeur = TransformerFactory.newInstance();
Transformer XML_Transformeur = XML_Fabrique_Transformeur.newTransformer();
DOMSource source = new DOMSource(XML_Document);
StreamResult resultat = new StreamResult(new File("XML_résultat.xml"));
XML_Transformeur.transform(source, resultat);
System.out.println("Le fichier XML a été généré !");
}
catch (ParserConfigurationException pce)
{
pce.printStackTrace();
}
catch (TransformerException tfe)
{
tfe.printStackTrace();
}
}
}
La méthode SAX
[modifier | modifier le wikicode]Pour lire le fichier ci-dessus, il faut le parser avec un handler dont on redéfinit les méthodes :
import java.io.*;
import javax.xml.parsers.*;
import org.xml.sax.*;
import org.xml.sax.helpers.*;
import org.xml.sax.helpers.DefaultHandler;
public class XML_Creation_SAX
{
public static void main(String[] args)
{
try
{
SAXParserFactory factory = SAXParserFactory.newInstance();
SAXParser parser = factory.newSAXParser();
parser.parse("XML_résultat.xml",
new DefaultHandler()
{
public void startDocument() throws SAXException
{ System.out.println("Début de document XML"); }
public void endDocument() throws SAXException
{ System.out.println("Fin de document XML"); }
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException
{ System.out.println("Début d'élément : " + qName); }
public void endElement(String uri, String localName, String qName) throws SAXException
{ System.out.println("Fin d'élément : " + qName); }
public void characters(char[] ch, int start, int length) throws SAXException
{ System.out.println("Caractère lus dans un élémént"); }
}
);
}
catch (IOException e)
{
System.err.println("Erreur de lecture : "+e);
System.exit(1);
}
catch (SAXException e)
{
System.err.println("Erreur d'interprétation : "+e);
System.exit(1);
}
catch (ParserConfigurationException e)
{
System.err.println("Pas d'interpréteur SAX pour la configuration demandée : "+e);
System.exit(1);
}
}
}
La méthode JDOM
[modifier | modifier le wikicode]JDOM utilise les deux bibliothèques précédentes. Si l'erreur suivante survient : error: package org.jdom does not exist, il suffit de télécharger la bibliothèque. Pour utilise la version 2, remplacer les import org.jdom
par des import org.jdom2
.
Swing
Swing est une bibliothèque graphique Java fournissant des composants légers.
- Interfaces graphiques avec Swing :
Pour apprendre à utiliser Swing, lire le livre consacré à la programmation Swing.
Swing/Hello
Interfaces graphiques avec swing
[modifier | modifier le wikicode]Un premier exemple
[modifier | modifier le wikicode]Le premier programme de tout informaticien.
Le fichier Hello.java
import java.awt.BorderLayout;
import java.awt.Container;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
public class Hello extends JFrame {
// Constructeur
public Hello() {
super("Titre de la JFrame");
// Si on appuie sur la croix, le programme s'arrete
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// On récupère le conteneur de la JFrame (this est
// une JFrame car Hello hérite de JFrame)
Container contentPane = this.getContentPane();
// Choix du gestionnaire de disposition
BorderLayout layout = new BorderLayout();
contentPane.setLayout(layout);
JPanel panel = new JPanel();
JLabel label = new JLabel(
"Bonjour, ceci est une JFrame qui contient"+
" un JPanel qui contient un JLabel");
panel.add(label);
// Ici ne sert pas car le panel est seul
contentPane.add(panel, BorderLayout.CENTER);
this.pack();
this.setVisible(true);
}
// Méthode principale : démarrage du programme
public static void main(String[] args) {
new Hello();
}
}
Swing/Boutons sur image
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import javax.imageio.ImageIO;
import javax.swing.*;
import java.io.*;
import java.net.*;
public class FormulaireImage {
public static void main(String[] args) {
JFrame f = new JFrame("Formulaire");
JLayeredPane l = new JLayeredPane();
l.setPreferredSize(new Dimension(150,150));
JPanel pi = new JPanel() {
public void paint(Graphics g) {
try {
URL ImageWB = new URL("http://upload.wikimedia.org/wikipedia/commons/6/64/Wikibooks-logo-fr.png");
BufferedImage image1 = ImageIO.read(ImageWB);
g.drawImage(image1, 0, 0, null);
} catch (IOException e) {
e.printStackTrace();
}
}
};
pi.setBounds(10,10,150,150); // Fenêtre 150*150 placée à aux coordonnées 10,10
l.add(pi,new Integer(0)); // Déposée sur la couche zéro
JPanel pb = new JPanel();
JButton b1 = new JButton("Bouton 1");
b1.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e) {
System.out.println("Clic bouton 1");
}
});
pb.add(b1);
JButton b2 = new JButton("Bouton 2");
b2.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e) {
System.out.println("Clic bouton 2");
}
});
pb.add(b2);
pb.setBounds(30,30,100,60);
l.add(pb,new Integer(1));
f.add(l);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.pack();
f.setVisible(true); // Ou show(); deprecated
}
}
Swing/Somme
Interfaces graphiques avec swing
[modifier | modifier le wikicode]Calculer la somme de 2 entiers
[modifier | modifier le wikicode]Le fichier Somme.java
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.border.*;
public class Somme extends JFrame implements ActionListener
{
private JLabel textX,textY,textS;
private JTextField editX,editY,editS;
private JButton buCompute;
private double x,y,s;
public static void main(String [] args)
{
Somme a=new Somme();
}
Somme()
{
x=0;
y=0;
setBounds(10,20,400,200);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Container c;
c=getContentPane();
BorderLayout bl=new BorderLayout();
c.setLayout(bl);
JPanel p=new JPanel();
p.setLayout(null);
textX= new JLabel("Valeur de X : ");
textX.setBounds(10,20,100,20);
p.add(textX);
textY= new JLabel("Valeur de Y : ");
textY.setBounds(10,50,100,20);
p.add(textY);
textS= new JLabel("X+Y vaut : ");
textS.setBounds(10,80,100,20);
p.add(textS);
editX=new JTextField(20);
editX.setBounds(120,20,100,20);
p.add(editX);
editY=new JTextField(20);
editY.setBounds(120,50,100,20);
p.add(editY);
editS=new JTextField(20);
editS.setBounds(120,80,100,20);
editS.setEditable(false);
p.add(editS);
Border bord=BorderFactory.createRaisedBevelBorder();
buCompute=new JButton("CALCULER");
buCompute.setActionCommand("CALCULER");
buCompute.addActionListener(this);
buCompute.setBorder(bord);
buCompute.setBounds(240,50,100,20);
p.add(buCompute);
c.add(p, BorderLayout.CENTER);
setVisible(true);
}
public void actionPerformed(ActionEvent e)
{
if (e.getActionCommand().equals("CALCULER"))
{
double vx,vy,d1;
try
{
vx=Double.parseDouble(editX.getText());
}
catch(RuntimeException ex)
{
vx=x;
editX.setText(String.valueOf(x));
}
try
{
vy=Double.parseDouble(editY.getText());
}
catch(RuntimeException ex)
{
vy=y;
editY.setText(String.valueOf(y));
}
x=vx;
y=vy;
s=x+y;
editS.setText(String.valueOf(s));
}
}
}
Swing/Distance
Interfaces graphiques avec swing
[modifier | modifier le wikicode]Calculer la distance entre 2 points
[modifier | modifier le wikicode]Le fichier Distance.java
package org.wikibooks.fr.swing;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.border.*;
/**
* Calcul de la distance entre 2 points.
* @author fr.wikibooks.org
*/
public class Distance extends JFrame implements ActionListener
{
private JLabel l_xa, l_ya, l_xb, l_yb, l_distance;
private JTextField t_xa, t_xb, t_ya, t_yb, t_distance;
private JButton b_edit_a, b_edit_b;
private Point
p_a = new Point(),
p_b = new Point();
public static void main(String[] args)
{
Distance app = new Distance();
app.setVisible(true);
}
public Distance()
{
setBounds(10, 10, 400, 300);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Container c = getContentPane();
c.setLayout(new BorderLayout());
JPanel p = new JPanel();
p.setLayout(null);
l_xa = new JLabel("Abscisse de A : ");
l_xa.setBounds(10, 20, 100, 20);
p.add(l_xa);
l_ya = new JLabel("Ordonnée de A : ");
l_ya.setBounds(10, 50, 100, 20);
p.add(l_ya);
l_xb = new JLabel("Abscisse de B : ");
l_xb.setBounds(10, 90, 100, 20);
p.add(l_xb);
l_yb = new JLabel("Ordonnée de B : ");
l_yb.setBounds(10, 120, 100, 20);
p.add(l_yb);
l_distance = new JLabel("La distance AB vaut : ");
l_distance.setBounds(10, 170, 150, 20);
p.add(l_distance);
t_xa = new JTextField(20);
t_xa.setBounds(120, 20, 100, 20);
t_xa.setEditable(false);
p.add(t_xa);
t_ya = new JTextField(20);
t_ya.setBounds(120, 50, 100, 20);
t_ya.setEditable(false);
p.add(t_ya);
t_xb = new JTextField(20);
t_xb.setBounds(120, 90, 100, 20);
t_xb.setEditable(false);
p.add(t_xb);
t_yb = new JTextField(10);
t_yb.setBounds(120, 120, 100, 20);
t_yb.setEditable(false);
p.add(t_yb);
t_distance = new JTextField(20);
t_distance.setBounds(150, 170, 150, 20);
t_distance.setEditable(false);
p.add(t_distance);
Border bord = BorderFactory.createRaisedBevelBorder();
b_edit_a = new JButton("MODIFIER A");
b_edit_a.setActionCommand("EDITA");
b_edit_a.addActionListener(this);
b_edit_a.setBorder(bord);
b_edit_a.setBounds(240, 35, 100, 20);
p.add(b_edit_a);
b_edit_b = new JButton("MODIFIER B");
b_edit_b.setActionCommand("EDITB");
b_edit_b.addActionListener(this);
b_edit_b.setBorder(bord);
b_edit_b.setBounds(240, 105, 100, 20);
p.add(b_edit_b);
c.add(p, BorderLayout.CENTER);
updateValues();
}
private void updateValues()
{
t_xa.setText(String.valueOf(p_a.getX()));
t_ya.setText(String.valueOf(p_a.getY()));
t_xb.setText(String.valueOf(p_b.getX()));
t_yb.setText(String.valueOf(p_b.getY()));
t_distance.setText(String.valueOf(p_a.distance(p_b)));
}
public void actionPerformed(ActionEvent e)
{
if (e.getActionCommand().equals("EDITA"))
{
PointDialog dialog = new PointDialog(this, "Modification du point A", p_a);
dialog.setVisible(true);
updateValues();
}
else if (e.getActionCommand().equals("EDITB"))
{
PointDialog dialog = new PointDialog(this, "Modification du point B", p_b);
dialog.setVisible(true);
updateValues();
}
}
}
Le fichier Point.java
package org.wikibooks.fr.swing;
/**
* Modélisation d'un point.
* @author fr.wikibooks.org
*/
public class Point
{
private double x, y;
public Point()
{
x = 0;
y = 0;
}
public double getX()
{
return x;
}
public double getY()
{
return y;
}
public void setXY(double x, double y)
{
this.x = x;
this.y = y;
}
public double distance(Point p)
{
double dx, dy;
dx = x - p.x;
dy = y - p.y;
return java.lang.Math.sqrt(dx * dx + dy * dy);
}
}
Le fichier PointDialog.java
package org.wikibooks.fr.swing;
import javax.swing.*;
import javax.swing.border.Border;
import java.awt.event.*;
import java.awt.*;
/**
* Dialogue pour modifier un point.
* @author fr.wikibooks.org
*/
public class PointDialog extends JDialog
{
private Point p;
private JButton b_ok, b_cancel;
private JLabel l_x, l_y;
private JTextField t_x, t_y;
public PointDialog(JFrame f, String s, Point P)
{
super(f, s, true);
this.p = P;
setLocationRelativeTo(f);
setBounds(500, 100, 400, 200);
setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
Container c = getContentPane();
c.setLayout(null);
l_x = new JLabel("Abscisse du point :");
l_x.setBounds(10, 20, 120, 20);
c.add(l_x);
l_y = new JLabel("Ordonnée du point :");
l_y.setBounds(10, 50, 120, 20);
c.add(l_y);
t_x = new JTextField(20);
t_x.setBounds(140, 20, 100, 20);
c.add(t_x);
t_y = new JTextField(20);
t_y.setBounds(140, 50, 100, 20);
c.add(t_y);
Border bord = BorderFactory.createRaisedBevelBorder();
b_ok = new JButton("OK");
b_ok.setActionCommand("OK");
b_ok.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
closeOk();
}
});
b_ok.setBorder(bord);
b_ok.setBounds(260, 25, 60, 20);
c.add(b_ok);
b_cancel = new JButton("Annuler");
b_cancel.setActionCommand("CANCEL");
b_cancel.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
close();
}
});
b_cancel.setBorder(bord);
b_cancel.setBounds(260, 50, 60, 20);
c.add(b_cancel);
updatePoint();
}
private void updatePoint()
{
t_x.setText(String.valueOf(p.getX()));
t_y.setText(String.valueOf(p.getY()));
}
private void closeOk()
{
double x, y;
boolean ok = true;
try
{
x = Double.parseDouble(t_x.getText());
}
catch (NumberFormatException ex)
{
x = p.getX();
t_x.setText(String.valueOf(x));
ok = false;
}
try
{
y = Double.parseDouble(t_y.getText());
}
catch (NumberFormatException ex)
{
y = p.getY();
t_y.setText(String.valueOf(y));
ok = false;
}
if (ok)
{
p.setXY(x, y);
dispose();
}
}
private void close()
{
setVisible(false);
}
}
JDK
Le JDK est un ensemble d'outil permettant de développer en Java.
Pour obtenir la liste des options d'un outil, il suffit de lancer l'outil sans aucun argument.
Compiler le code
[modifier | modifier le wikicode]javac est le compilateur qui convertit le code source .java en fichier .class (contenant le bytecode Java).
Supposons que vous avez :
- un dossier « src » qui contient vos sources (tous vos fichiers .java) ;
- un dossier « bin » où vous placerez tous les fichiers compilés (les fichier .class correspondant).
# compile seulement la classe Exemple et place le résultat dans bin javac -d bin src/Exemple.java # compile toutes les sources trouvées dans src et les place dans bin javac -d bin src/**/*.java
Si la compilation échoue parce que votre code utilise des classes que javac ne connait pas (erreur Unable to find symbol), vous devez préciser à javac un classpath comme expliqué plus loin.
Lancer l'application
[modifier | modifier le wikicode]java
permet une dans lancer une application java en ligne de commande, il faut passer en paramètre le nom complet (pleinement qualifié) de la classe.
java org.wikibooks.fr.Exemple argument_1 argument_2
La ligne de commande ci-dessus appel la méthode public static void main(String[] args) de la classe Exemple du package org.wikibooks.fr avec args, un tableau à deux éléments : "argument_1" et argument_2".
javaw permet une application Java sans console (interface graphique seule).
javaw org.wikibooks.fr.Exemple
Une archive java (*.jar) avec l'option -jar
, un petit fichier (appelé « Manifest ») contenu dans le jar indique lui-même le nom de la classe principale à lancer.
java -jar chemin/vers/le/fichier.jar
Préciser le CLASSPATH
[modifier | modifier le wikicode]Dans toutes les commandes ci-dessus, il faut permettre à java de trouver tous les fichiers compilés nécessaire à l'exécution du code (les fichiers *.class
générés avec javac
). Pour cela, il faut préciser à java
les répertoires ou celui-ci pourra trouver les classes et les packages nécessaires à l'application.
Pour cela, il faut définir ce qu'on appelle le « CLASS PATH », c'est une simple chaîne qui définit plusieurs chemins pour trouver des .class séparés par ":" sous Linux ou ";" sous Windows. Les chemins peuvent être des chemins vers des fichiers .jar ou vers des répertoires contenant des fichiers .class
. Il y a deux façons de préciser le CLASSPATH à java, la première est d'utiliser le paramètre -classpath
de ligne de commande.
Sous Linux, les chemins sont séparés par le caractère deux-points :
java -classpath chemin/vers/une/premiere/bibliotheque.jar:chemin/vers_une_autre/bibliotheque.jar:bin org.wikibooks.fr.Exemple
Sous Windows, les chemins sont séparés par le caractère point-virgule :
java -classpath X:\chemin\vers\une\premiere\bibliotheque.jar;Y:\chemin\vers_une_autre\bibliotheque.jar;bin org.wikibooks.fr.Exemple
Ici, java essaiera de trouver le fichier Exemple.class dans les deux jars donnés puis dans le dossier bin. Si le fichier n'est trouvé dans aucun de ces éléments, il y aura une erreur (Class not found exception).
La seconde façon de préciser le CLASSPATH est de définir une variable d'environnement système. Sous Linux :
export CLASSPATH="chemin/vers/une/premiere/bibliotheque.jar:chemin/vers_une_autre/bibliotheque.jar"
Sous Windows :
SET "CLASSPATH=X:\chemin\vers\une\premiere\bibliotheque.jar;Y:\chemin\vers_une_autre\bibliotheque.jar"
Attention à ne pas confondre java -jar fichier.jar
et java -cp fichier.jar
, la première commande permet de lancer le programme qui se trouve dans fichier.jar et fonctionne ; la seconde précise que des classes peuvent être chargée depuis fichier.jar mais oublie de préciser quelle classe il faut lancer : java indiquera qu'un paramètre est manquant. Enfin, sachez que vous pouvez remplacer « -classpath » par « -cp ».
jar
[modifier | modifier le wikicode]L'outil jar
permet de regrouper les classes (fichiers *.class
) et ressources d'une application en une seule archive exécutable.
Cette archive est au format ZIP mais possède l'extension .jar
.
Dans cette archive, un répertoire spécial situé à la racine nommé META-INF
contient des fichiers d'information et de configuration pour la machine virtuelle Java, dont notamment le fichier MANIFEST.MF
(fichier manifest, MF = Meta File).
Une archive JAR peut être exécutable si la classe principale (contenant une méthode statique nommée main
) de l'application est spécifiée dans ce fichier MANIFEST.MF
.
Dans ce cas, l'application peut se lancer avec la commande suivante :
java -jar chemin_de_l_archive.jar ...arguments passés à l'application si besoin...
Ce fichier manifest est au format texte. Exemple :
Manifest-Version: 1.0 Ant-Version: Apache Ant 1.7.1 Created-By: 19.1-b02 (Sun Microsystems Inc.) Main-Class: org.wikibooks.fr.ApplicationExemple Class-Path: .
Il possède différents champs. Chaque champ possède un nom, suivi de deux-points et de sa valeur. Si une valeur est longue, elle peut être répartie sur plusieurs lignes, chaque ligne additionnelle commençant alors par au moins un caractère espace.
Pour plus de détails sur les fichiers manifest voir http://docs.oracle.com/javase/tutorial/deployment/jar/manifestindex.html
Le champ Class-Path
dans une archive JAR diffère du paramètre classpath passé aux outils Java :
- Il ne peut contenir que des chemins relatifs à d'autres archives Java (*.jar) ; les répertoires ne sont pas supportés ;
- Le séparateur est un caractère espace. Les fichiers référencés ne peuvent donc en contenir dans leur nom.
Pour créer une archive JAR à partir d'un répertoire contenant les classes et ressources (les sous-répertoires devant correspondre aux packages), et d'un fichier texte pour le fichier manifest :
jar cfm mon_archive.jar mon_manifest.txt -C répertoire .
Quel que soit le nom et l'extension du fichier source pour le manifest, il sera nommé MANIFEST.MF
dans l'archive.
Les arguments de la commande sont décrits ci-dessous :
- cfm
- Une série de caractères dont le premier spécifie l'action principale :
- c Créer une archive (Create).
- t Afficher le contenu de l'archive (Table of content).
- x Extraire les fichiers de l'archive (eXtract files).
- u Mettre à jour l'archive existante (Update archive).
- Les caractères suivants donne l'ordre des arguments qui suivent :
- f Le nom du fichier archive (archive File).
- m Le nom du fichier manifest à utiliser (Manifest file).
- mon_archive.jar
- Le nom du fichier archive.
- mon_manifest.txt
- Le nom du fichier manifest à utiliser.
- -C répertoire
- Précise le chemin du répertoire pour les chemins relatifs
- .
- Inclus le fichier spécifié (
.
désignant le répertoire courant sous la plupart des systèmes d'exploitation).
Eclipse possède une interface interactive pour générer un fichier JAR à partir des classes d'un projet Java, ou d'une configuration d'exécution.
javadoc
[modifier | modifier le wikicode]L'outil javadoc
est le générateur de documentation, qui génère automatiquement de la documentation à partir des commentaires du code source.
Voir le chapitre sur les commentaires pour plus de détails sur l'outil et son utilisation.
jdb
[modifier | modifier le wikicode]Le JDK fournit un débogueur nommé jdb
(Java DeBugger) utilisable depuis la ligne de commande.
Mode interactif
[modifier | modifier le wikicode]Lancez la ligne de commande jdb
pour accéder au débogueur interactif :
- Entrez
version
pour obtenir les versions de l'outil et de Java, - Entrez
help
pour obtenir la liste des commandes disponibles, - Entrez
exit
pour quitter le débogueur.
Initializing jdb ... > version This is jdb version 1.6 (Java SE version 1.8.0_05) Java Debug Interface (Reference Implementation) version 1.6 Java Debug Wire Protocol (Reference Implementation) version 1.6 JVM Debug Interface version 1.2 JVM version 1.8.0_05 (Java HotSpot(TM) 64-Bit Server VM, mixed mode, sharing) > help ** command list ** connectors -- list available connectors and transports in this VM run [class [args]] -- start execution of application's main class threads [threadgroup] -- list threads thread <thread id> -- set default thread suspend [thread id(s)] -- suspend threads (default: all) resume [thread id(s)] -- resume threads (default: all) where [<thread id> | all] -- dump a thread's stack wherei [<thread id> | all]-- dump a thread's stack, with pc info up [n frames] -- move up a thread's stack down [n frames] -- move down a thread's stack kill <thread id> <expr> -- kill a thread with the given exception object interrupt <thread id> -- interrupt a thread print <expr> -- print value of expression dump <expr> -- print all object information eval <expr> -- evaluate expression (same as print) set <lvalue> = <expr> -- assign new value to field/variable/array element locals -- print all local variables in current stack frame classes -- list currently known classes class <class id> -- show details of named class methods <class id> -- list a class's methods fields <class id> -- list a class's fields threadgroups -- list threadgroups threadgroup <name> -- set current threadgroup stop in <class id>.<method>[(argument_type,...)] -- set a breakpoint in a method stop at <class id>:<line> -- set a breakpoint at a line clear <class id>.<method>[(argument_type,...)] -- clear a breakpoint in a method clear <class id>:<line> -- clear a breakpoint at a line clear -- list breakpoints catch [uncaught|caught|all] <class id>|<class pattern> -- break when specified exception occurs ignore [uncaught|caught|all] <class id>|<class pattern> -- cancel 'catch' for the specified exception watch [access|all] <class id>.<field name> -- watch access/modifications to a field unwatch [access|all] <class id>.<field name> -- discontinue watching access/modifications to a field trace [go] methods [thread] -- trace method entries and exits. -- All threads are suspended unless 'go' is specified trace [go] method exit | exits [thread] -- trace the current method's exit, or all methods' exits -- All threads are suspended unless 'go' is specified untrace [methods] -- stop tracing method entrys and/or exits step -- execute current line step up -- execute until the current method returns to its caller stepi -- execute current instruction next -- step one line (step OVER calls) cont -- continue execution from breakpoint list [line number|method] -- print source code use (or sourcepath) [source file path] -- display or change the source path exclude [<class pattern>, ... | "none"] -- do not report step or method events for specified classes classpath -- print classpath info from target VM monitor <command> -- execute command each time the program stops monitor -- list monitors unmonitor <monitor#> -- delete a monitor read <filename> -- read and execute a command file lock <expr> -- print lock info for an object threadlocks [thread id] -- print lock info for a thread pop -- pop the stack through and including the current frame reenter -- same as pop, but current frame is reentered redefine <class id> <class file name> -- redefine the code for a class disablegc <expr> -- prevent garbage collection of an object enablegc <expr> -- permit garbage collection of an object !! -- repeat last command <n> <command> -- repeat command n times # <command> -- discard (no-op) help (or ?) -- list commands version -- print version information exit (or quit) -- exit debugger <class id>: a full class name with package qualifiers <class pattern>: a class name with a leading or trailing wildcard ('*') <thread id>: thread number as reported in the 'threads' command <expr>: a Java(TM) Programming Language expression. Most common syntax is supported. Startup commands can be placed in either "jdb.ini" or ".jdbrc" in user.home or user.dir > exit
Ligne de commande
[modifier | modifier le wikicode]Pour les options de la ligne de commande, entrez la commande jdb -help
Usage: jdb <options> <class> <arguments> where options include: -help print out this message and exit -sourcepath <directories separated by ";"> directories in which to look for source files -attach <address> attach to a running VM at the specified address using standard connector -listen <address> wait for a running VM to connect at the specified address using standard connector -listenany wait for a running VM to connect at any available address using standard connector -launch launch VM immediately instead of waiting for 'run' command -listconnectors list the connectors available in this VM -connect <connector-name>:<name1>=<value1>,... connect to target VM using named connector with listed argument values -dbgtrace [flags] print info for debugging jdb -tclient run the application in the HotSpot(TM) Client Compiler -tserver run the application in the HotSpot(TM) Server Compiler options forwarded to debuggee process: -v -verbose[:class|gc|jni] turn on verbose mode -D<name>=<value> set a system property -classpath <directories separated by ";"> list directories in which to look for classes -X<option> non-standard target VM option <class> is the name of the class to begin debugging <arguments> are the arguments passed to the main() method of <class> For command help type 'help' at jdb prompt
Déboguer une classe
[modifier | modifier le wikicode]Cette section montre le débogage d'une classe simple :
package org.wikibooks.fr; /** * Une classe simple à déboguer. * @author fr.wikibooks.org */ public class Addition { public static Integer somme(Integer a, Integer b) { if (a==null) return b; if (b==null) return a; return a + b; } public static void main(String[] args) { // null équivaut à 0 pour la méthode somme System.out.println("Somme 1+2 = "+somme(1,2)); System.out.println("Somme +2 = "+somme(null,2)); } }
Après compilation de la classe, la ligne de commande pour lancer de débogueur est la suivante, en supposant que les fichiers sources dans un sous-répertoire nommé src
et le code compilé dans un sous-répertoire nommé bin
:
jdb -classpath bin -sourcepath src org.wikibooks.fr.Addition
Beaucoup de commandes ne sont pas disponibles tant que la JVM n'est pas lancée :
Initializing jdb ... > print 10+15 Command 'print' is not valid until the VM is started with the 'run' command > trace methods Command 'trace' is not valid until the VM is started with the 'run' command
Lancez la classe spécifiée en ligne de commande :
> run run org.wikibooks.fr.Addition Set uncaught java.lang.Throwable Set deferred uncaught java.lang.Throwable > VM Started: Somme 1+2 = 3 Somme +2 = 2 The application exited
L'application a été exécutée et s'est terminée, et le débogueur a quitté également. Le débogueur n'est utilisable que durant l'exécution de l'application, que son exécution soit active ou suspendue soit par un point d'arrêt soit par une exécution pas à pas.
Point d'arrêt et pas à pas
[modifier | modifier le wikicode]La pose d'un point d'arrêt peut se faire par la commande stop in
qui a besoin du nom de la classe (paquetage inclus) et de la méthode.
jdb -classpath bin -sourcepath src org.wikibooks.fr.Addition
Posez ensuite un point d'arrêt à l'entrée de la méthode main
et lancez l'exécution :
Initializing jdb ... > stop in org.wikibooks.fr.Addition.main Deferring breakpoint org.wikibooks.fr.Addition.main. It will be set after the class is loaded. > run run org.wikibooks.fr.Addition Set uncaught java.lang.Throwable Set deferred uncaught java.lang.Throwable > VM Started: Set deferred breakpoint org.wikibooks.fr.Addition.main Breakpoint hit: "thread=main", org.wikibooks.fr.Addition.main(), line=19 bci=0 19 System.out.println("Somme 1+2 = "+somme(1,2)); main[1]
Les commandes testées auparavant sont désormais disponibles :
main[1] print 10+15 10+15 = 25 main[1] trace methods
Exécutez ensuite l'application pas à pas avec la commande step
pour exécuter la ligne de code suivante en entrant dans les méthodes appelées, ou la commande next
pour ne pas entrer dans les méthodes appelées.
À chaque pas, vous pouvez afficher la valeur des variables locales, évaluer une expression, ...
- La commande
where
affiche la pile des appels du thread courant, - La commande
list
liste le code en cours d'exécution du thread courant.
main[1] step > Method entered: Step completed: "thread=main", org.wikibooks.fr.Addition.somme(), line=11 bci=0 11 if (a==null) return b; main[1] print a a = "1" main[1] print b b = "2" main[1] print a+b com.sun.tools.example.debug.expr.ParseException: Invalid operation '+' on an Object a+b = null main[1] where [1] org.wikibooks.fr.Addition.somme (Addition.java:11) [2] org.wikibooks.fr.Addition.main (Addition.java:19) main[1] list 7 public class Addition 8 { 9 public static Integer somme(Integer a, Integer b) 10 { 11 => if (a==null) return b; 12 if (b==null) return a; 13 return a + b; 14 } 15 16 public static void main(String[] args) main[1] step > Step completed: "thread=main", org.wikibooks.fr.Addition.somme(), line=12 bci=6 12 if (b==null) return a; main[1] step > Step completed: "thread=main", org.wikibooks.fr.Addition.somme(), line=13 bci=12 13 return a + b; main[1] step > Method exited: return value = instance of java.lang.Integer(id=394), "thread=main", org.wikibooks.fr.Addition.somme(), line=13 bci=24 13 return a + b; main[1] step > Step completed: "thread=main", org.wikibooks.fr.Addition.main(), line=19 bci=23 19 System.out.println("Somme 1+2 = "+somme(1,2)); main[1] step > Somme 1+2 = 3 Step completed: "thread=main", org.wikibooks.fr.Addition.main(), line=20 bci=32 20 System.out.println("Somme +2 = "+somme(null,2)); main[1] step > Method entered: Step completed: "thread=main", org.wikibooks.fr.Addition.somme(), line=11 bci=0 11 if (a==null) return b; main[1] step > Method exited: return value = instance of java.lang.Integer(id=395), "thread=main", org.wikibooks.fr.Addition.somme(), line=11 bci=5 11 if (a==null) return b; main[1] step > Step completed: "thread=main", org.wikibooks.fr.Addition.main(), line=20 bci=52 20 System.out.println("Somme +2 = "+somme(null,2)); main[1] step > Somme +2 = 2Step completed: "thread=main", org.wikibooks.fr.Addition.main(), line=21 bci=61 21 } main[1] step > Method exited: return value = <void value>, "thread=main", org.wikibooks.fr.Addition.main(), line=21 bci=61 21 } main[1] step > The application exited
Après un point d'arrêt ou un pas d'exécution, pour continuer l'exécution jusqu'à la fin ou le prochain point d'arrêt, utiliser la commande cont
(continue).
javah
[modifier | modifier le wikicode]javah permet de générer un fichier d'en-tête C (*.h) contenant la déclaration des fonctions correspondantes aux méthodes natives de la classe compilée spécifiée.
javah -classpath directory-list classname
- Pour plus de détails voir : Développer en Java/Faire appel à du code natif.
javap
[modifier | modifier le wikicode]javap permet de lister les membres et désassembler la classe compilée spécifiée.
javap -c -classpath directory-list classname
Options
[modifier | modifier le wikicode]-help
--help
-?
- Afficher les options possibles.
-version
- Information de version.
-v
-verbose
- Afficher toutes les informations supplémentaires.
-l
- Afficher les numéros de ligne et les tables de variables locales.
-public
- N'afficher que les classes et membres publics.
-protected
- Afficher les classes et membres publics ou protégés.
-package
- Afficher les classes et membres publics, protégés ou paquetage (par défaut).
-p
-private
- Afficher toutes les classes et tous les membres.
-c
- Désassembler le code.
-s
- Afficher les signatures des types internes.
-sysinfo
- Afficher les informations systèmes (chemin, taille, date, MD5) des classes traitées.
-constants
- Afficher les constantes finales et statiques.
-classpath chemin
-cp chemin
- Spécifier le chemin où trouver les classes.
-bootclasspath chemin
- Redéfinir le chemin des classes bootstrap.
Exemple avec désassemblage du code
[modifier | modifier le wikicode]La classe désassemblée dans cet exemple est produite à partir de ce fichier source :
package org.wikibooks.fr;
/**
* Une classe simple à désassembler.
* @author fr.wikibooks.org
*/
public class Addition
{
public static Integer somme(Integer a, Integer b)
{
if (a==null) return b;
if (b==null) return a;
return a + b;
}
public static void main(String[] args)
{
// null équivaut à 0 pour la méthode somme
System.out.println("Somme 1+2 = "+somme(1,2)); // -> 3
System.out.println("Somme +2 = "+somme(null,2)); // -> 2
}
}
L'option verbose permet d'afficher toutes les informations sur le fichier .class :
> javap -cp W:\Programme\TestJava\bin -v org.wikibooks.fr.Addition
Classfile /W:/Programme/TestJava/bin/org/wikibooks/fr/Addition.class Last modified 31 mai 2022; size 1117 bytes MD5 checksum 0a72b766cde42eff71b4b8f7f4a67266 Compiled from "Addition.java" public class org.wikibooks.fr.Addition SourceFile: "Addition.java" minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Class #2 // org/wikibooks/fr/Addition #2 = Utf8 org/wikibooks/fr/Addition #3 = Class #4 // java/lang/Object #4 = Utf8 java/lang/Object #5 = Utf8 <init> #6 = Utf8 ()V #7 = Utf8 Code #8 = Methodref #3.#9 // java/lang/Object."<init>":()V #9 = NameAndType #5:#6 // "<init>":()V #10 = Utf8 LineNumberTable #11 = Utf8 LocalVariableTable #12 = Utf8 this #13 = Utf8 Lorg/wikibooks/fr/Addition; #14 = Utf8 somme #15 = Utf8 (Ljava/lang/Integer;Ljava/lang/Integer;)Ljava/lang/Integer; #16 = Methodref #17.#19 // java/lang/Integer.intValue:()I #17 = Class #18 // java/lang/Integer #18 = Utf8 java/lang/Integer #19 = NameAndType #20:#21 // intValue:()I #20 = Utf8 intValue #21 = Utf8 ()I #22 = Methodref #17.#23 // java/lang/Integer.valueOf:(I)Ljava/lang/Integer; #23 = NameAndType #24:#25 // valueOf:(I)Ljava/lang/Integer; #24 = Utf8 valueOf #25 = Utf8 (I)Ljava/lang/Integer; #26 = Utf8 a #27 = Utf8 Ljava/lang/Integer; #28 = Utf8 b #29 = Utf8 StackMapTable #30 = Utf8 main #31 = Utf8 ([Ljava/lang/String;)V #32 = Fieldref #33.#35 // java/lang/System.out:Ljava/io/PrintStream; #33 = Class #34 // java/lang/System #34 = Utf8 java/lang/System #35 = NameAndType #36:#37 // out:Ljava/io/PrintStream; #36 = Utf8 out #37 = Utf8 Ljava/io/PrintStream; #38 = Class #39 // java/lang/StringBuilder #39 = Utf8 java/lang/StringBuilder #40 = String #41 // Somme 1+2 = #41 = Utf8 Somme 1+2 = #42 = Methodref #38.#43 // java/lang/StringBuilder."<init>":(Ljava/lang/String;)V #43 = NameAndType #5:#44 // "<init>":(Ljava/lang/String;)V #44 = Utf8 (Ljava/lang/String;)V #45 = Methodref #1.#46 // org/wikibooks/fr/Addition.somme:(Ljava/lang/Integer;Ljava/lang/Integer;)Ljava/lang/Integer; #46 = NameAndType #14:#15 // somme:(Ljava/lang/Integer;Ljava/lang/Integer;)Ljava/lang/Integer; #47 = Methodref #38.#48 // java/lang/StringBuilder.append:(Ljava/lang/Object;)Ljava/lang/StringBuilder; #48 = NameAndType #49:#50 // append:(Ljava/lang/Object;)Ljava/lang/StringBuilder; #49 = Utf8 append #50 = Utf8 (Ljava/lang/Object;)Ljava/lang/StringBuilder; #51 = Methodref #38.#52 // java/lang/StringBuilder.toString:()Ljava/lang/String; #52 = NameAndType #53:#54 // toString:()Ljava/lang/String; #53 = Utf8 toString #54 = Utf8 ()Ljava/lang/String; #55 = Methodref #56.#58 // java/io/PrintStream.println:(Ljava/lang/String;)V #56 = Class #57 // java/io/PrintStream #57 = Utf8 java/io/PrintStream #58 = NameAndType #59:#44 // println:(Ljava/lang/String;)V #59 = Utf8 println #60 = String #61 // Somme +2 = #61 = Utf8 Somme +2 = #62 = Utf8 args #63 = Utf8 [Ljava/lang/String; #64 = Utf8 SourceFile #65 = Utf8 Addition.java { public org.wikibooks.fr.Addition(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #8 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 7: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lorg/wikibooks/fr/Addition; public static java.lang.Integer somme(java.lang.Integer, java.lang.Integer); descriptor: (Ljava/lang/Integer;Ljava/lang/Integer;)Ljava/lang/Integer; flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=2, args_size=2 0: aload_0 1: ifnonnull 6 4: aload_1 5: areturn 6: aload_1 7: ifnonnull 12 10: aload_0 11: areturn 12: aload_0 13: invokevirtual #16 // Method java/lang/Integer.intValue:()I 16: aload_1 17: invokevirtual #16 // Method java/lang/Integer.intValue:()I 20: iadd 21: invokestatic #22 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 24: areturn LineNumberTable: line 11: 0 line 12: 6 line 13: 12 LocalVariableTable: Start Length Slot Name Signature 0 25 0 a Ljava/lang/Integer; 0 25 1 b Ljava/lang/Integer; StackMapTable: number_of_entries = 2 frame_type = 6 /* same */ frame_type = 5 /* same */ public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=4, locals=1, args_size=1 0: getstatic #32 // Field java/lang/System.out:Ljava/io/PrintStream; 3: new #38 // class java/lang/StringBuilder 6: dup 7: ldc #40 // String Somme 1+2 = 9: invokespecial #42 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V 12: iconst_1 13: invokestatic #22 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 16: iconst_2 17: invokestatic #22 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 20: invokestatic #45 // Method somme:(Ljava/lang/Integer;Ljava/lang/Integer;)Ljava/lang/Integer; 23: invokevirtual #47 // Method java/lang/StringBuilder.append:(Ljava/lang/Object;)Ljava/lang/StringBuilder; 26: invokevirtual #51 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 29: invokevirtual #55 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 32: getstatic #32 // Field java/lang/System.out:Ljava/io/PrintStream; 35: new #38 // class java/lang/StringBuilder 38: dup 39: ldc #60 // String Somme +2 = 41: invokespecial #42 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V 44: aconst_null 45: iconst_2 46: invokestatic #22 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 49: invokestatic #45 // Method somme:(Ljava/lang/Integer;Ljava/lang/Integer;)Ljava/lang/Integer; 52: invokevirtual #47 // Method java/lang/StringBuilder.append:(Ljava/lang/Object;)Ljava/lang/StringBuilder; 55: invokevirtual #51 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 58: invokevirtual #55 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 61: return LineNumberTable: line 19: 0 line 20: 32 line 21: 61 LocalVariableTable: Start Length Slot Name Signature 0 62 0 args [Ljava/lang/String; }
Informations générales
[modifier | modifier le wikicode]public class org.wikibooks.fr.Addition SourceFile: "Addition.java" minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPER
La première partie indique la version de java utilisée pour compiler la classe (52.0 supportée par Java 8). La classe est publique (ACC_PUBLIC) et a une classe de base (ACC_SUPER) ; seule la classe java.lang.Object n'en a pas.
Pool de constantes
[modifier | modifier le wikicode]Les constantes litérales sont regroupées en un seul endroit du fichier de classe et numérotées.
Elles proviennent du code source mais aussi des constantes implicites utilisées en interne, comme par exemple le nom de la classe de base java/lang/Object
quand aucune n'est spécifiée explicitement.
Constant pool: #1 = Class #2 // org/wikibooks/fr/Addition #2 = Utf8 org/wikibooks/fr/Addition #3 = Class #4 // java/lang/Object #4 = Utf8 java/lang/Object #5 = Utf8 <init> #6 = Utf8 ()V #7 = Utf8 Code #8 = Methodref #3.#9 // java/lang/Object."<init>":()V #9 = NameAndType #5:#6 // "<init>":()V
Chaque constante a un type (Class, Utf8, Methodref) et une ou deux valeurs associés. La valeur peut faire référence à d'autres constantes définies ailleurs, auquel cas la référence est suivi d'un commentaire donnant la valeur référencée.
Type | Description |
---|---|
Utf8
|
Chaîne de caractères encodées en UTF-8. |
String
|
Référence à une chaîne de caractères (Utf8 ) initialisant un objet de type java.lang.String .
|
Class
|
Référence à une chaîne de caractères (Utf8 ) donnant un nom de classe.
|
NameAndType
|
Références à une chaîne de caractères (Utf8 ) donnant un nom de membre et à une autre chaîne de caractères donnant la signature de son type.
|
Methodref
|
Références à une classe (Class ) et un de ses membres (NameAndType ) méthode de la classe.
|
Fieldref
|
Références à une classe (Class ) et un de ses membres (NameAndType ) attribut de la classe.
|
Code des méthodes
[modifier | modifier le wikicode]Le code de chaque méthode (bytecode) de la classe est affiché, comme celui du constructeur de la classe pour l'exemple ci-dessous :
public org.wikibooks.fr.Addition(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #8 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 7: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lorg/wikibooks/fr/Addition;
Aucun constructeur n'est pourtant déclaré dans le code source de l'exemple plus haut, mais le compilateur en a généré un par défaut.
Le nom du constructeur en interne est en fait <init>
.
L'indicateur d'accès ACC_PUBLIC
signifie que ce constructeur est public.
Le descripteur ()V
indique que le constructeur n'a pas d'argument (parenthèses vides) et ne retourne rien (V = void).
Code: stack=1, locals=1, args_size=1
Le code a besoin d'un élément de pile, d'une variable locale, et utilise un argument implicite (this) référençant l'objet créé à initialiser.
0: aload_0 1: invokespecial #8 // Method java/lang/Object."<init>":()V 4: return
Le constructeur effectue dans l'ordre :
- (adresse 0, instruction codée sur 1 octet) Charge l'argument 0 (this) dans la pile (push)
- (adresse 1, instruction codée sur 3 octets) Appeler la méthode
<init>
(le constructeur) de la classe de base (java.lang.Object
par défaut) sur l'objet référencé par l'élément au sommet de la pile. - (adresse 4, instruction codée sur 1 octet) Retour de méthode (aucune valeur de retour).
LineNumberTable: line 7: 0
L'instruction à l'adresse 0 correspond à la ligne 7 dans le code source, mais comme ce constructeur a été généré implicitement, la ligne source correspond à la déclaration de la classe.
Une option du compilateur permet de ne pas générer la table des lignes source LineNumberTable
.
LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lorg/wikibooks/fr/Addition;
Le constructeur utilise une variable locale (slot 0) nommée "this", dont le scope va de l'adresse 0 à l'adresse 5, de type Addition
.
Pour la méthode somme
:
public static java.lang.Integer somme(java.lang.Integer, java.lang.Integer); descriptor: (Ljava/lang/Integer;Ljava/lang/Integer;)Ljava/lang/Integer; flags: ACC_PUBLIC, ACC_STATIC
La méthode est publique et statique (pas de référence this en argument implicite), prend deux arguments de type java.lang.Integer
et sa valeur de retour est du même type, comme indiqué par la signature de méthode (Ljava/lang/Integer;Ljava/lang/Integer;)Ljava/lang/Integer;
.
Code: stack=2, locals=2, args_size=2
La méthode utilise deux emplacements dans la pile, deux variables locales, et a deux arguments.
0: aload_0 1: ifnonnull 6 4: aload_1 5: areturn 6: aload_1 7: ifnonnull 12 10: aload_0 11: areturn 12: aload_0 13: invokevirtual #16 // Method java/lang/Integer.intValue:()I 16: aload_1 17: invokevirtual #16 // Method java/lang/Integer.intValue:()I 20: iadd 21: invokestatic #22 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 24: areturn LineNumberTable: line 11: 0 line 12: 6 line 13: 12
En regroupant par ligne de code source :
Lignes sources | Code source | Assembleur bytecode | Description |
---|---|---|---|
Ligne 11 (adresses 0 à 5) |
if (a==null) return b; |
0: aload_0 1: ifnonnull 6 4: aload_1 5: areturn |
Remarque : les instructions sont préfixées par le type manipulé (a pour les références, i pour les entiers, ...). |
Ligne 12 (adresses 6 à 11) |
if (b==null) return a; |
6: aload_1 7: ifnonnull 12 10: aload_0 11: areturn |
Remarque : instructions similaires à la ligne source précédente. |
Ligne 13 (adresses 12 à 25) |
return a + b; |
12: aload_0 13: invokevirtual #16 // Method java/lang/Integer.intValue:()I 16: aload_1 17: invokevirtual #16 // Method java/lang/Integer.intValue:()I 20: iadd 21: invokestatic #22 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 24: areturn |
|
LocalVariableTable: Start Length Slot Name Signature 0 25 0 a Ljava/lang/Integer; 0 25 1 b Ljava/lang/Integer;
La méthode utilise deux variables locales nommées a et b dont la portée va des adresses 0 à 25 (tout le code de la méthode).
StackMapTable: number_of_entries = 2 frame_type = 6 /* same */ frame_type = 5 /* same */
L'attribut de méthode StackMapTable
facilite la vérification des types des valeurs dans les tables de variables locales et la pile lorsque le code contient des sauts d'instructions comme dans cet exemple.
Sans cette information, la vérification de code, ne pouvant pas se fier aux instructions qui précédent seulement pour vérifier la cohérence, doit parcourir tous les chemins d'exécution possible, ce qui prendrait du temps.
Créer une archive Java
Une archive Java rassemble des classes Java dans un fichier d'archive compressé. Son format est identique à celui des fichiers ZIP.
L'extension de nom de fichier standard est .jar
; elle peut être différente pour certaines utilisations spécifiques (exemple : .war
, ...).
Type d'archives et rôles
[modifier | modifier le wikicode]Il existe différents types d'archives variant selon le contenu et ayant des rôles différents :
- Une archive de fichiers sources (*.java)
- Elle permet de fournir le code source d'une application ou bibliothèque. Alternativement, il peut s'agir du code source incomplet, n'incluant que la partie publique d'une bibliothèque ; cette archive peut alors être utilisée par les IDE pour exposer le code source de certaines classes.
- Une archive de fichiers compilés (*.class)
- Ce type d'archive inclut également les ressources internes. Une telle archive peut être exécutable si elle contient une classe principale, utilisable dans une ligne de commande simplifiée :
java -jar app.jar
oujavaw -jar app.jar
L'archive peut être signée pour authentifier son contenu.
Contenu
[modifier | modifier le wikicode]L'archive contient la hiérarchie des répertoires des paquetages de l'application.
Exemple : pour une application dont les classes sont dans un paquetage nommé org.wikibooks.fr.exemple
:
org/wikibooks/fr/exemple/DemarrageApplication.class
org/wikibooks/fr/exemple/ui/FenetrePrincipale.class
org/wikibooks/fr/exemple/ui/icone_application.png
org/wikibooks/fr/exemple/format/LecteurJson.class
org/wikibooks/fr/exemple/format/LecteurJson$1.class
- ...
Elle contient également un répertoire spécial nommé META-INF
contenant des fichiers d'information pour Java dont notamment :
- le fichier manifest décrit dans la section suivante,
- les fichiers de signature des classes (*.RSA, *.DSA, SIG-*, ...).
Fichier manifest
[modifier | modifier le wikicode]Le fichier manifest META-INF/MANIFEST.MF
est un fichier texte qui définit des propriétés globales à l'archive, dont notamment la classe principale et un chemin de classes (Class-Path
).
Il peut contenir également une section par fichier pour les propriétés de signature.
Exemple :
Manifest-Version: 1.0 Main-Class: org.wikibooks.fr.exemple.ClassePrincipale Class-Path: .
Le fichier est composé de lignes au format nom: valeur
regroupées en sections.
Une section est défini comme un groupe de lignes consécutives sans ligne vide.
Les lignes vides servent à séparer les sections.
Section globale
[modifier | modifier le wikicode]La première section concerne l'archive globalement. La première ligne du fichier indique la version du format (1.0).
Manifest-Version: 1.0
Les autres attributs sont optionnels :
Main-Class
- Nom de la classe principale à lancer. L'archive n'est pas exécutable si cet attribut est absent.
Class-Path
- Liste de chemin relatifs vers d'autres archives Java (*.jar) contenant des classes utilisées par cette archive. Contrairement au chemin de classe passé à la ligne de commande de l'interpréteur ou du compilateur Java, les fichiers ne peuvent être que des archives Java. Les chemins sont séparés par un espace.
- Il s'agit en réalité d'une liste d'URL, mais l'utilisation d'URL absolue n'est pas recommandée pour laisser la liberté à l'utilisateur d'installer les archives où il veut. Pour les noms d'archives contenant un espace, cela signifie qu'il peut être encodé en
%20
comme dans les URLs. Created-By
- Créateur de l'archive. Il s'agit en général de l'outil utilisé pour créer l'archive.
Sealed
- Verrouillage des paquetages de l'archive :
true
(oui) oufalse
(non). Un paquetage verrouillé signifie qu'aucune autre archive ne peut ajouter des classes dans les mêmes paquetages que ceux définis dans cette archive.
D'autres attributs existent[1], notamment concernant la signature. Les attributs non reconnus sont ignorés ; il est donc possible d'ajouter ses propres attributs pour ajouter des informations et d'en récupérer la valeur à l'utilisation.
Section par fichier
[modifier | modifier le wikicode]Les sections suivantes concernent un fichier particulier nommé dans la première ligne du fichier. Un chemin de fichier se terminant pas un slash désigne un répertoire (paquetage).
Exemple :
Name: common/class2.class SHA1-Digest: (...base64 representation of SHA1 digest...) SHA-256-Digest: (...base64 representation of SHA-256 digest...)
Il peut s'agir de modification par rapport aux attributs globaux :
Sealed
- Verrouillage du paquetage :
true
(oui) oufalse
(non). Un paquetage verrouillé signifie qu'aucune autre archive ne peut ajouter des classes dans le même paquetage.
Exemple : Tous les paquetages verrouillés sauf org.wikibooks.fr
:
Manifest-Version: 1.0 Sealed: true Name: org/wikibooks/fr/ Sealed: false
Création
[modifier | modifier le wikicode]La création d'une archive peut se faire avec différents outils :
- l'outil
jar
en ligne de commande, fourni avec le JDK (voir la section « jar » du chapitre « JDK »), - depuis l'IDE Eclipse (voir la section « Créer une archive java (JAR) » du chapitre « Eclipse »),
- l'outil Ant,
- d'autres outils qui ne seront pas décrits ici mais qui peuvent être basés sur les outils cités ci-dessus.
En utilisant un fichier jardesc
[modifier | modifier le wikicode]Un fichier d'extension .jardesc
(JAR description) est un fichier XML décrivant comment construire une archive Java.
Ce type de fichier est généré et utilisé par Eclipse (File > Export... > Java / JAR file
).
Exemple de fichier généré par Eclipse :
<?xml version="1.0" encoding="WINDOWS-1252" standalone="no"?> <jardesc> <jar path="D:/Dev/workspacePerso/Exemple/Export/wikilivres.jar"/> <options buildIfNeeded="true" compress="true" descriptionLocation="/Exemple/wikilivres.jardesc" exportErrors="true" exportWarnings="true" includeDirectoryEntries="false" overwrite="false" saveDescription="true" storeRefactorings="false" useSourceFolders="false"/> <storedRefactorings deprecationInfo="true" structuralOnly="false"/> <selectedProjects/> <manifest generateManifest="false" manifestLocation="/Exemple/wikilivres.mf" manifestVersion="1.0" reuseManifest="false" saveManifest="false" usesManifest="true"> <sealing sealJar="false"> <packagesToSeal/> <packagesToUnSeal/> </sealing> </manifest> <selectedElements exportClassFiles="true" exportJavaFiles="false" exportOutputFolder="false"> <javaElement handleIdentifier="=Exemple/src"/> </selectedElements> </jardesc>
Pour lancer la création du fichier d'archive depuis Eclipse :
- Cliquez avec le bouton droit sur le fichier jardesc,
- Sélectionnez
Create JAR
dans le menu contextuel.
Pour modifier la configuration d'un fichier jardesc existant, vous pouvez relancez le générateur depuis Eclipse :
- Cliquez avec le bouton droit sur le fichier jardesc,
- Sélectionnez
Open With > JAR Export Wizard
dans le menu contextuel.
En utilisant un script Ant
[modifier | modifier le wikicode]Un script Ant est un fichier XML (extension .xml
) permettant la construction d'une archive Java.
Ce type de fichier est généré et utilisé par Eclipse (File > Export... > Java / Runnable JAR file
).
Exemple : Un fichier généré par Eclipse :
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <project default="create_run_jar" name="Create Runnable Jar for Project Exemple"> <!--this file was created by Eclipse Runnable JAR Export Wizard--> <!--ANT 1.7 is required --> <target name="create_run_jar"> <jar destfile="D:/Dev/workspacePerso/Exemple/export/wikilivres.jar" filesetmanifest="mergewithoutmain"> <manifest> <attribute name="Main-Class" value="org.wikibooks.fr.app.Demarrer"/> <attribute name="Class-Path" value="livres.jar vitrine.jar"/> </manifest> <fileset dir="D:/Dev/workspacePerso/Exemple/bin"/> <fileset dir="D:/Dev/workspacePerso/Images/bin"/> <fileset dir="D:/Dev/workspacePerso/AutreLib/bin"/> <zipfileset excludes="META-INF/*.SF" src="D:/Dev/workspacePerso/Exemple/forms-1.3.0.jar"/> </jar> </target> </project>
<project default="create_run_jar" name="Create Runnable Jar for Project Exemple">
- L'action par défaut est nommée « create_run_jar » définie par la suite.
<target name="create_run_jar">
- Action nommée « create_run_jar »
<jar destfile="D:/Dev/workspacePerso/Exemple/export/wikilivres.jar" filesetmanifest="mergewithoutmain">
- Création d'une archive Java dont le chemin est spécifié par l'attribut
destfile
. Cet élément contient la définition des attributs du manifest et une série d'ensembles de fichiers (fileset en anglais). <manifest>
- Définition des attributs du fichier manifest.
<fileset dir="D:/Dev/workspacePerso/Exemple/bin"/>
- Inclure les fichiers du répertoire désigné par l'attribut
dir
(ici, les classes compilées situées dans le sous-répertoirebin
du projet). <zipfileset excludes="META-INF/*.SF" src="D:/Dev/workspacePerso/Exemple/forms-1.3.0.jar"/>
- Inclure les fichiers de l'archive compressée désignée par l'attribut
src
(pour cet exemple, en excluant les fichiers de signatureMETA-INF/*.SF
).
Pour lancer la création du fichier d'archive depuis Eclipse :
- Cliquez avec le bouton droit sur le fichier ant,
- Sélectionnez
Run As > Ant Build
dans le menu contextuel.
Utilisation
[modifier | modifier le wikicode]Bibliothèque distribuable
[modifier | modifier le wikicode]Une bibliothèque de classes peut être distribuée sous la forme d'une archive Java.
Dans Eclipse, vous pouvez ajouter une archive Java dans le chemin de compilation :
- Cliquez avec le bouton droit sur un projet ;
- Sélectionnez « Build Path > Configure Build Path... » dans le menu contextuel.
Ajouter l'archive Java des classes compilées. Vous pouvez ensuite lui associer une seconde archive Java pour le code source de la partie publique de la bibliothèque.
Chargement dynamique depuis une application
[modifier | modifier le wikicode]Pour étendre les possibilités d'une application, des extensions peuvent être définies sous forme d'archives Java chargées dynamiquement par l'application plutôt que lié statiquement par un chemin de classe (classpath
ou Class-Path
).
La lecture de fichiers contenus dans une archive Java ainsi que le chargement dynamique de classes sont décrits en détails dans la section « Exemple concret : un gestionnaire d'extensions » du chapitre « Réflexion ».
La récupération d'information depuis le fichier manifest d'une archive Java peut se faire ainsi :
import java.util.jar.*; // ... File f = new File("ext.jar"); // ou passé en argument d'une méthode JarFile jf = new JarFile(f); try { Manifest mf = jf.getManifest(); Attributes attrs = mf.getMainAttributes(); // Attributs de la section principale String info = (String)attrs.get("Wikilivres-Info"); } finally { jf.close(); }
Pour en savoir plus
[modifier | modifier le wikicode]Références
[modifier | modifier le wikicode]
Paramétrer la JVM
La machine virtuelle Java désigne l'environnement d'exécution d'un programme Java. Il est possible d'adapter cet environnement suivant les besoins : utilisation de la mémoire, configuration du réseau, etc.
Il existe deux possibilités de paramétrage de cet environnement :
- au moment du lancement du programme Java (et de la JVM) ;
- au cours de l'exécution d'un programme Java.
L'action de paramétrer la machine virtuelle représente le changement d'état d'un paramètre système. Dans un premier temps, les paramètres les plus utilisés sont listés. Ensuite, les deux possibilités de paramétrage sont exposées.
Liste des paramètres de la machine virtuelle Java (JVM)
[modifier | modifier le wikicode]Beaucoup de paramètres et options sont liés au fonctionnement interne de la JVM : réseau, mémoire (ramasse-miette), ... En cas de doute, utilisez les valeurs par défaut.
Paramètres de lancement d'application
[modifier | modifier le wikicode]Image de démarrage (Splash screen)
[modifier | modifier le wikicode]Durant le lancement d'une application Java, il est possible de spécifier à la JVM d'afficher une image d'attente (comme lors du lancement d'Eclipse par exemple) en utilisant l'option -splash
.
-splash:<chemin_de_l_image>
L'image doit se situer dans le système de fichiers (elle ne peut pas être dans un JAR), et utiliser l'un des formats supportés : PNG, GIF, JPEG, BMP.
Pour une application dans une archive JAR, il est possible d'afficher une image de splash screen en utilisant le paramètre SplashScreen-Image
dans le fichier manifest.
L'application peut récupérer la fenêtre affichant l'image (pour y ajouter une barre de progression ou une animation par exemple) en utilisant la méthode getSplashScreen()
de la classe java.awt.SplashScreen
[1] retournant l'instance de cette classe créée par la JVM.
La fenêtre de splash screen est automatiquement fermée quand l'application affiche sa première fenêtre d'interface graphique.
Paramètres du proxy
[modifier | modifier le wikicode]Nom du paramètre[2] | Description |
---|---|
http.proxyHost
|
Nom du serveur proxy |
http.proxyPort
|
Port du proxy (valeur par défaut:80) |
http.nonProxyHosts
|
Liste des hôtes qui doivent être accédés directement, sans passer par le proxy. Il s'agît d'une liste d'expressions régulières séparées par '|'. Tous les hôtes correspondant à l'une de ces expressions seront accédés par une connexion directe sans passer par le proxy |
http.proxyUser
|
Nom d'utilisateur utilisé pour le proxy |
http.proxyPassword
|
Mot de passe utilisateur utilisé pour le proxy |
Paramètres de la mémoire
[modifier | modifier le wikicode]La mémoire utilisée pour l'allocation dynamique des objets est appelée le tas (heap en anglais), paramétrable avec les options commençant par -Xm
.
Le paramétrage de la mémoire se fait au lancement d'un programme Java grâce aux options du tableau ci-dessous.
Nom du paramètre | Description |
---|---|
-Xms
|
Taille initiale du tas (Memory Start) |
-Xmx
|
Taille maximale du tas (Memory maX) |
-Xmn
|
Taille du tas (Memory Nursery) pour les objets ayant un cycle de vie court.
Le tas de jeune génération (Young generation) est celui où les objets sont créés. Le ramasse-miette (garbage collector) passe plus souvent dans ce tas pour libérer les objets qui ne sont plus utilisés. Tout objet s'y trouvant durant 2 ou 3 cycles du ramasse-miette est déplacé vers le tas des objets plus anciens (Old generation). |
-Xss
|
Taille de la pile (Stack Size) pour chaque processus léger (thread).
La pile est la zone mémoire où les arguments et variables locales sont empilées durant l'appel à une méthode.
Une taille insuffisante peut aboutir à une exception de classe |
Exemple pour lancer un programme Java en allouant 256 Mo :
java -Xms256M -Xmx256M monProgrammeJava
Modification d'un paramètre de la JVM
[modifier | modifier le wikicode]Au lancement d'un programme Java, il est possible de positionner un ou plusieurs paramètres système de la manière suivante :
$ java -D<nom du paramètre1>=<valeur du paramètre1> -D<nom du paramètre2>=<valeur du paramètre2> monProgrammeJava
Exemple pour positionner le proxy à utiliser :
$ java -Dhttp.proxyHost=monproxy -Dhttp.proxyPort=3128 monProgrammeJava
- dans cet exemple, le proxy utilisé sera l'hôte "monproxy" sur le port 3128
Au sein d'un programme Java, il est possible de changer l'état d'un paramètre système de la manière suivante :
System.getProperties().put("http.proxyHost", "monproxy");
System.getProperties().put("http.proxyPort", "3128");
System.getProperties().put("http.proxyUser", "toto");
System.getProperties().put("http.proxyPassword", "totoisback");
- dans cet exemple, le proxy utilisé sera l'hôte "monproxy" sur le port 3128 en utilisant comme utilisateur "toto"
Références
[modifier | modifier le wikicode]- ↑ (anglais) https://docs.oracle.com/javase/8/docs/api/java/awt/SplashScreen.html
- ↑ (anglais) https://docs.oracle.com/javase/6/docs/api/java/net/Proxy.html
Liste des mots réservés
Voici la liste des mots réservés à la programmation en Java :
Mot réservé | Type | Description |
---|---|---|
abstract |
Mot-clé | Déclaration d'une méthode ou d'une classe abstraite. |
assert |
Mot-clé | Assertion |
boolean |
Type de données | Valeur booléenne (vrai ou faux). |
break |
Mot-clé | Interrompre une boucle ou un choix multiple. |
byte |
Type de données | Entier signé de -128 à +127. |
case |
Mot-clé | Cas dans un choix multiple. |
catch |
Mot-clé | Capture d'un type d'exception. |
char |
Type de données | Caractères Unicode (UTF-16, donc sur 16 bits). |
class |
Mot-clé | Déclaration d'une classe. |
const |
Réservé[1] | Inutilisé actuellement. |
continue |
Mot-clé | Continuer une boucle en allant à l'itération suivante. |
default |
Mot-clé | Cas par défaut dans un choix multiple. |
do |
Mot-clé | Boucle itérative. |
double |
Type de données | Nombre à virgule flottante, double précision. |
else |
Mot-clé | Exécution conditionnelle. |
enum |
Mot-clé | Déclaration d'une énumération. |
extends |
Mot-clé | Héritage : déclaration de la classe mère, ou pour une interface de toutes les interfaces mères. |
false |
Valeur littérale | Valeur booléenne fausse. |
final |
Mot-clé | Déclarer un membre comme final. |
finally |
Mot-clé | Code exécuté quoi qu'il se passe dans un bloc de capture d'exception. |
float |
Type de données | Nombre à virgule flottante, simple précision. |
for |
Mot-clé | Boucle itérative. |
goto |
Réservé[1] | Inutilisé actuellement. |
if |
Mot-clé | Exécution conditionnelle. |
implements |
Mot-clé | Déclaration des interfaces implémentées par une classe. |
import |
Mot-clé | Déclaration des packages utilisés par une classe. |
instanceof |
Mot-clé | Tester si un objet est de la classe indiquée (voir Transtypage). |
int |
Type de données | Entier signé de −2 147 483 648 à 2 147 483 647. |
interface |
Mot-clé | Déclaration d'une interface. |
long |
Type de données | Entier signé de −9 223 372 036 854 776 000 à 9 223 372 036 854 776 000. |
native |
Mot-clé | Déclaration d'une méthode native. |
new |
Mot-clé | Allocation d'une instance de classe. |
null |
Valeur littérale | Référence nulle. |
package |
Mot-clé | Déclaration du package de la classe. |
private |
Mot-clé | Déclaration d'un membre privé de la classe. |
protected |
Mot-clé | Déclaration d'un membre protégé de la classe. |
public |
Mot-clé | Déclaration d'un membre public de la classe. |
return |
Mot-clé | Retourner une valeur depuis une méthode. |
short |
Type de données | Entier signé de −32 768 à 32 767. |
static |
Mot-clé | Déclaration d'un membre statique de la classe. |
strictfp |
Mot-clé | Déclaration d'une méthode ou classe où les opérations en virgule flottante doivent être évalué strictement de gauche à droite selon la spécification Java. |
super |
Mot-clé | Référence à l'instance de la classe mère. |
switch |
Mot-clé | Début d'un choix multiple. |
synchronized |
Mot-clé | Voir Processus légers et synchronisation. |
this |
Mot-clé | Référence à l'instance de la classe englobante. |
throw |
Mot-clé | Lever une exception |
throws |
Mot-clé | Déclaration des exception levées par une méthode. |
transient |
Mot-clé | Déclaration d'un attribut à exclure de la sérialisation. |
true |
Valeur littérale | Valeur booléenne vraie. |
try |
Mot-clé | Capture d'un type d'exception. |
void |
Mot-clé | Déclaration d'une méthode ne retournant aucune valeur. |
volatile |
Mot-clé | Déclaration d'un attribut volatile, c'est à dire dont la valeur ne doit pas être mise en cache car elle est accédée par différents threads. |
while |
Mot-clé | Boucle itérative. |
Suffixes
[modifier | modifier le wikicode]- L
- L est un suffixe pour déclarer une valeur littérale de type
long
au lieu deint
(voir syntaxe des valeurs de type long). - F
- Idem pour le suffixe F pour déclarer une valeur littérale de type
float
au lieu dedouble
(voir syntaxe des valeurs de type float). - D
- Idem pour le suffixe D pour déclarer une valeur littérale de type
double
. Cependant, le type par défaut des nombres à virgules étantdouble
, ce suffixe n'est pas obligatoire (voir syntaxe des valeurs de type double).
Préfixes
[modifier | modifier le wikicode]- 0
- 0 est un préfixe utilisable pour une valeur entière exprimée en octal (base 8, chiffres de 0 à 7).
- 0x
- 0x est un préfixe utilisable pour une valeur entière exprimée en hexadécimal (base 16, chiffres de 0 à 9 et les lettres de A à F).
Notes et références
[modifier | modifier le wikicode]
Débogage
Outils de débogage
[modifier | modifier le wikicode]Pour déboguer sans TDD (test driven development), on peut ajouter le code suivant où l'on veut :
System.out.println("Valeur à afficher");System.exit(0);
Cependant ce n'est pas la meilleure façon de faire. Il est préférable d'utiliser un IDE supportant le débogage :
- Poser des points d'arrêt pour suspendre l'exécution et inspecter les valeurs des attributs, des arguments, des variables locales.
- Reprendre ensuite en mode pas à pas pour voir l'évolution des valeurs et voir quelle partie du code s'exécute.
Par exemple, Eclipse possède deux manières de lancer une application :
- Le bouton de lancement normal ignore tous les points d'arrêts.
- Le bouton de débogage représenté par une icône d'insecte pour le mot anglais "bug" (cafard), permet de prendre en compte les points d'arrêt pour suspendre l'exécution de l'application. Une perspective spécifique au débogage est alors affichée pour lister les expressions à pister et voir le code source.
VisualVM
[modifier | modifier le wikicode]VisualVM est un outil permettant d'explorer les informations sur les machines virtuelles Java qui tournent sur la machine (une JVM par application).
Cet outil était disponible avec la plateforme Java, édition standard (Java SE) dans les versions du JDK de la version 6 update 7 jusuq'à la version 8 incluse (voir https://docs.oracle.com/javase/8/docs/technotes/guides/visualvm/). Il est désormais disponible séparément sur le site https://visualvm.github.io/ .
jdb
[modifier | modifier le wikicode]jdb est un débogueur en ligne de commande fourni avec le JDK.
- Pour plus de détails voir : Programmation Java/JDK#jdb.
Erreurs à la compilation
[modifier | modifier le wikicode]Fenêtre de warning ...java uses unchecked or unsafe operations
[modifier | modifier le wikicode]Une déclaration ne tient pas compte des surcharges possibles, une conversion de variable pose problème. Il faut donc en prévoir les exceptions. Par exemple pour manipuler des entiers :
try {
i = i/2;
} catch (NumberFormatException e) {
return 0;
}
<identifier> expected
[modifier | modifier le wikicode]L'ordre des déclarations n'est pas chronologique.
annotation type not applicable to this kind of declaration
[modifier | modifier le wikicode]Voir @Override.
cannot be dereferenced
[modifier | modifier le wikicode]- Il suffit de retirer la conversion, le .toString() après la variable de type int sélectionnée.
- Ou en ajouter une comme : (float), Float.valueOf(), ou .floatValue().
cannot find symbol - variable
[modifier | modifier le wikicode]Soit :
- La variable mentionnée n'a pas été déclarée (ou elle a été mal écrite) ;
- Sa déclaration trouve dans une condition (
if
outry
) fermée avant la ligne en erreur ; - Elle se trouve dans un package non importé ;
- Si elle se trouve dans un autre fichier, les compiler depuis leur répertoire parent commun (les IDE le font automatiquement) :
javac Projet1/Classe1.java
javac Projet1/Classe2.java
cannot instantiate from arguments because actual and formal argument lists differ in length
[modifier | modifier le wikicode]Cela peut survenir suite à un Collections.sort(liste), alors même que le Collections.shuffle(liste) fonctionne. Il faut donc utiliser import java.util.Comparator;
dans le trie :
import java.util.Collections;
import java.util.Comparator;
...
private List<T> list;
private Comparator<T> comparator = null;
if(this.comparator!= null) {
Collections.sort(list, comparator);
}
constructor variable in class classe cannot be applied to given types
[modifier | modifier le wikicode]exception IOException is never thrown in body of corresponding try statement
[modifier | modifier le wikicode]Un try
inutile a été détecté.
Cela peut être causé par la mise en commentaires de lignes devenues inutiles (instructions de débogage par exemple).
impossible de trouver la classe principale
[modifier | modifier le wikicode]Lancer la commande "java" vers un .class (et non un .java).
incompatible types: possible lossy conversion from float to int ou double to float
[modifier | modifier le wikicode]Il faut passer une conversion de format :
java.text.DecimalFormat df = new java.text.DecimalFormat("#");
monFloat = Float.valueOf(df.format(monDouble));
java.lang.NumberFormatException
[modifier | modifier le wikicode]Le contenu d'une chaîne de caractère ne peut être interprétée comme un nombre :
- Soit à cause d'un symbole qui n'est pas un chiffre (ex : €)
- ou bien une virgule à la place d'un point pour un nombre réel.
local variable referenced from a lambda expression must be final or effectively final
[modifier | modifier le wikicode]local variable ma_variable is accessed from within inner class; needs to be declared final
[modifier | modifier le wikicode]no suitable method found for - méthode
[modifier | modifier le wikicode]La méthode mentionnée n'a pas été déclarée, ou du moins pas avec ce type de paramètre (ex : remplacer .addAll() par .add()).
no suitable constructor found for Integer(double)
[modifier | modifier le wikicode]Convertir plus explicitement :
MonEntier = ((Number)MonDouble).intValue();
no suitable constructor for classe is abstract cannot be instanciated
[modifier | modifier le wikicode]S'il est possible de retirer le mot abstract
de la déclaration de la classe, le faire.
non-static method/variable ... cannot be referenced from a static context
[modifier | modifier le wikicode]Cela peut se produire en invoquant une méthode ou une variable dans sa classe, ou une autre classe.
- Si elle est bien statique :
- Au sein de la même classe, retirer le
this.
en préfixe. - Depuis une autre classe, ajouter le nom de la classe en préfixe.
- Sinon, il faut rendre la variable appelée dans la méthode statique, statique aussi.
- Au sein de la même classe, retirer le
- Si elle n'est pas statique :
- On peut rendre la méthode statique non statique.
- Sinon, ajouter ou remplacer le "this." par le nom de la variable désignant la classe (ex : "c.") :
MaClasse c = new MaClasse();
c.MaMethode();
not a statement
[modifier | modifier le wikicode]Il manque sûrement les parenthèses après le nom d'une méthode.
reference to assertequals is ambiguous
[modifier | modifier le wikicode]Il convient d'assurer un typage plus fort des paramètres de assertequals()
, par exemple avec .intValue()
.
unclosed character literal
[modifier | modifier le wikicode]Il manque le délimiteur apostrophe final, ou bien l'apostrophe comme caractère doit être préfixée d'un anti-slash.
Ou bien utiliser une chaîne de caractères pour zéro ou plus d'un caractère.
unclosed string literal
[modifier | modifier le wikicode]Il manque le délimiteur guillemet final, ou bien le guillemet comme caractère dans la chaîne doit être préfixé d'un anti-slash.
unreachable statement
[modifier | modifier le wikicode]Une partie de code ne sera jamais exécutée, parce qu'elle est juste après un break
ou un return
.
unreported exception ...; must be caught or declared to be thrown
[modifier | modifier le wikicode]Une exception est lancée explicitement (throw new NomDeTException("Message");
) ou par une méthode déclarant pouvoir lancer cette exception sans que la méthode appelante ait déclarée avec throws NomDeLException
.
- Soit il faut ajouter une capture de l'exception dans la méthode si elle peut la traiter complétement à son niveau (journalisation, message à l'utilisateur, arrêter une procédure en cours...),
- Soit plus probablement il faut que la méthode déclare lancer ce type d'exception avec le mot-clé
throws
à la fin de la déclaration de la méthode.
Il se peut que la méthode puisse traiter partiellement l'exception (ex : journaliser l'erreur) et qu'elle doive la relancer pour qu'elle soit traitée complétement ailleurs (remonter jusqu'au code de l'interface graphique pour l'affichage d'un message d'erreur). Dans ce cas, un mélange des deux solutions est à faire :
- Capturer l'exception pour journaliser l'erreur et la relancer avec
throw
- Déclarer ce type d'exception avec le mot-clé
throws
à la fin de la déclaration de la méthode.
variable is already defined
[modifier | modifier le wikicode]Si la variable est redéfinie dans une condition différente, il faut les séparer avec des { }, ex :
switch (args[0].toString()) {
case "1": {
a = 1;
break;
}
case "2": {
a = 2;
break;
}
}
variable might not have been initialized
[modifier | modifier le wikicode]Il faut assigner une valeur par défaut à l'objet. Ex :
String[] a = {"", ""};
BufferedWriter bw = null;
Erreurs à l'exécution
[modifier | modifier le wikicode]Si deux variables de type String identiques ne peuvent pas être comparées
[modifier | modifier le wikicode]L'opérateur ==
vérifie les références au lieu du contenu.
Il faut donc utiliser la méthode .equals()
.
String s = "test";
StringBuffer sb = new StringBuffer("test");
System.out.println(s == new String(sb)); // false !
System.out.println(s.equals(new String(sb))); // true :)
Si une division fait toujours 0.0
[modifier | modifier le wikicode]Ajouter (float)
avant.
NullPointerException
[modifier | modifier le wikicode]Soulevée si l'on applique une méthode sur un objet null. On peut donc changer l'appel ou lever l'exception :
try {
...
} catch (NullPointerException npe) {
npe.printStackTrace();
}
aucun attribut manifest principal dans ...jar
[modifier | modifier le wikicode]Il n'y a pas de méthode main() dans le .jar, ce fichier ne peut donc pas être exécuté directement, mais peut être utilisé par une application ayant une méthode main().
Class names, classe, are only accepted if annotation processing is explicitly requested
[modifier | modifier le wikicode]Les classes définies à la compilation (par exemple avec javac -classpath
) sont introuvables.
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 1
[modifier | modifier le wikicode]Un des tableau de la ligne indiquée est appelé avec un indice négatif ou supérieur à sa taille.
java.lang.ArithmeticException: / by zero
[modifier | modifier le wikicode]Une division par zéro s'évite généralement par un if
sur le dénominateur, ou un throw
d'une exception.
Stream closed
[modifier | modifier le wikicode]Retirer le .close()
de la boucle du .read()
ou du .write()
, même s'il est situé après. Si cela ne suffit pas, le retirer de la méthode.
Unable to load native library: Can't load IA 32-bit .dll on a AMD 64-bit platform
[modifier | modifier le wikicode]Une application utilisant du code natif a tenté de chargé une bibliothèque de code dynamique (.dll sous windows, .so sous Linux/Mac) 32 bit sur une machine virtuelle Java 64 bits. L'inverse provoque également une variante de ce message d'erreur.
Solutions possibles :
- Installer la version 32 ou 64 bits du JRE requise.
- Ou chercher une version de l'application ayant une bibliothèque de code dynamique adaptée à la JVM 32 ou 64 bits.
Une application Java utilisant du code natif bien conçue ne devrait jamais rencontrer ce problème : il est possible de détecter la version de Java (32 ou 64 bits) avant de charger la bibliothèque dynamique appropriée, ce qui signifie avoir une version 32 bits et une version 64 bits de cette bibliothèque dynamique (ex : mylib-32.dll et mylib-64.dll).
Autres langages
Cette section sera utile aux personnes qui apprennent Java mais connaissant un ou plusieurs autres langages. Les confusions courantes sont évoquées et les équivalences listées.
- En Java, on utilise
final
et nonconst
. Le motconst
étant tout de même réservé pour un usage ultérieur. - Java ne définit pas d'espace de nom (namespace), mais des paquetages (
package
). - On ne libère pas la mémoire. Cela est géré automatiquement par le ramasse-miette. À la place, il est recommandé de libérer les références aux objets qui ne sont plus utilisés en assignant
null
à la référence, dès que possible ou juste avant une potentielle allocation d'une grande quantité d'objets ou de grands tableaux. - Il n'y a pas de pointeurs mais des références :
- Les opérateurs
->
et*
(déréférencement) n'existent pas. Il faut utiliser le point (.
) ; - Les références (contrairement au C++) peuvent être modifiées pour référencer un autre objet en utilisant l'opérateur d'affectation (
=
).
- Les opérateurs
- Un
char
fait un octet en C++ mais 2 octets en Java car l'encodage Unicode utilisé est l'UTF-16. unsigned
n'existe pas en Java : tous les types entiers sont signés.- L'héritage multiple possible en C++ est interdit en Java, il faut utiliser des interfaces.
- L'opérateur de résolution de portée «
::
» n'existe pas en Java :- Pour utiliser un membre d'un package ou un membre statique d'une classe, utiliser le point (
.
) ; - Pour appeler une méthode telle qu'elle est définie dans la classe parente, utiliser la référence
super
.
- Pour utiliser un membre d'un package ou un membre statique d'une classe, utiliser le point (
- Les classes ou méthodes ne peuvent être déclarées virtual car elles le sont toutes : toutes les liaisons sont dynamiques.
- Les opérateurs ne sont pas redéfinissables en Java. Les opérateurs n'agissent pas sur les objets, excepté pour l'opérateur
+
permettant de concaténer deux chaînes de caractères.
- L'équivalent de PDO (PHP Data Objects) est JDBC (Java DataBase Connectivity).
- Import est remplacé par require_once.
- Le framework JUnit par PHPUnit.
Le langage C sharp ressemble beaucoup à Java dans la syntaxe, cependant :
- Le nom des méthodes de l'API est similaire, mais commence par une minuscule en Java :
C sharp : ToString, Length, Main Java : toString, length, main
- Les accesseurs de propriétés n'existe pas en Java. Cependant, Java utilise la convention Bean (JavaBean), définissant des noms standards pour les méthodes d'accès aux propriétés (
getProperty(), setProperty(value), addPropertyCollectionItem(item), removePropertyCollectionItem(item)
...). Cette convention est supportée par les outils de développement en Java (NetBeans par exemple), notamment les outils de création d'interface graphique pour les bibliothèques de composants (AWT, Swing, JavaFX...) qui respectent la convention.
Voir aussi
[modifier | modifier le wikicode]GFDL | Vous avez la permission de copier, distribuer et/ou modifier ce document selon les termes de la licence de documentation libre GNU, version 1.2 ou plus récente publiée par la Free Software Foundation ; sans sections inaltérables, sans texte de première page de couverture et sans texte de dernière page de couverture. |