Java: tribulations d'un développeur C++
Sommaire
Motivation
Bien que développeur C/C++, il m'a fallu apprendre du Java pour terminer une partie de projet non-prévue.
Ces notes éparses ne sont pas un vrai tutoriel: elles condensent ce qui a été nécessaire à un développeur déjà expérimenté pour passer à une certaine efficacité dans un langage voisin.
Pour (re)débuter sur Java
Lancer ou examiner un programme existant
- java <fichier>: lancer le programme (sans l'extension .class);
- jar -xf <fichier.jar> : extrait les fichiers .class composant l'objet;
- javap <fichier.class>: désassemble la classe correspondante;
Principaux atouts du langage
- La caractéristique la plus connue du langage est la présence d'un «Garbage collector», dont l'implémentation est d'ailleurs facilitée par le fait que tous les objets dérivent d'une unique classe.
- Classes abstract (pas d'implémentation), et interface (implémentation obligatoire par les successeurs);
- Polymorphisme des objets (sans besoin de mot-clef comme en C++);
- Vérification de downcast dans le langage;
- Gestion (et obligation) des exceptions;
- Multithreading (et gestion des ressources par synchronized);
- Distribution, et programmation réseau;
Vocabulaire
Java brouille (tout comme C++) les frontières entre fonctions et méthodes. En principe:
- une fonction: produit un résultat, mais n'a pas d'effet de bord (si elle est «pure»);
- une méthode peut changer des choses;
Concepts et syntaxe
Visibilité
- import ...; importe un symbole ou un package particulier
- import static <package>.\*; : utilise tous les symboles sans préfixe.
Querelles d'héritage
Le mot-clef final (voir a plusieurs sens):
- final <classe> : pas de classe fille;
- final <methode> : pas de redéfinition dans les classes filles;
- final <variable> : est affectée une seule fois, à la déclaration OU dans le constructeur; cela ne la rend pas forcément constante, car ses méthodes peuvent la modifier;
Le mot-clef static aussi:
- pour un membre de classe: sera commun à toutes les instances;
- pour un bloc hors-méthode: initialisation exécutée au chargement de classe, et même avant main();
Idem pour extends
Quant à this et super, ils désignent respectivement la classe courante et la classe mère;
Déclarations de fonctions
- Une flèche -> définit une fonction anonyme, alias «lambda» : (args) -> { expression };
- Fonctions variadiques: écrire f(Object ... ao) passe un tableau d’objets ao à la fonction;
Annotations
Ces compléments du langage, d’abord utilisés seulement par Javadoc, on été standardisés par Java5
- Annotation @Override: s'assure que la fonction redéfinit bien un membre de la classe mère: @Override public int calcul() { ... };
- Annotations en général sur le cours de J-M
Doudoux
et la doc
Oracle;
3 rôles principaux:
- fournir des informations au compilateur;
- générer du code ou des fichiers de déploiement;
- influer (parfois) sur l'exécution;
Curiosités et logique
- final : une variable locale citée dans un lambda ou une classe anonyme (telle un listener) doit être final (car on ignore quand les fonctions concernées seront exécutées?), ou «effectively final» en Java 8.
Templates
- Voir Generic Types : class nom<T1,T2> { ... }
- Voir Generic Methods : <T1,T2> T1 f(T2,...) {...}
Les évolutions de Java
Les spécifications
Les spécifications de la machine virtuelle Java (JVM) montrent bien que, même non-liée au langage Java, elle est pensée pour lui.
- Les specs du langage peuvent éclairer les zones d'ombre. Auteurs: James Gosling, Bill Joy, Guy Steele ... du beau monde!
- La JVM ne sachant rien du langage, d'autres, tels Scala, peuvent fort bien tourner dessus.
- Par rapport à une spécification hardware normale, la JVM définit:
- des class avec des methods dont on a les reference;
- un bytecode, des instructions pour tableaux avec vérification d'index,...
- des exécutions avec frame, synchronisation avec des sections monitor, et instructions JVM pour les catch
Apports de Java 8
- Changements détaillés dans What's New in JDK8
- Streams facilitant les conversions. Par exemple de Integer[]vers int[]: intList.stream().mapToInt(Integer::intValue).toArray();
- Notion de variable "effectively final", i.e non-modifiée après son initialisation, même sans déclaration final;
- Rapprochement avec langage fonctionnel : lambdas;
- Annotations de types, et meilleure inférence de type;
- Référence de méthode : classe::methode;
- Méthodes par défaut dans les interfaces;
Cours, Pratiques et Jargon Java
Les cours
- les concepts de Java;
- les cours Sun/Oracle;
- Références Oracle: la référence de l'API ou toutes les classes;
- JMDoudoux.fr: copieuse présentation de Java;
Présentations rapides par thèmes
- présentation par notion et idem chez beginnersbooks.com
- les ressources pour débutant
- un petit cours sur JavaFX: remplaçant de Swing qui remplaçait AWT;
- Autre collection de titres, dont «Penser en Java v2»;
Présentations plus avancées
- Des Considérations techniques
- La Reflection: (capacité du code à s'inspecter et se modifier lui-même). Natif en Python, implémenté via le package reflection de Java, et utilisé en particulier pour les annotations.
- Les lambdas : voir notamment Jenkov.com et le Tutorial Oracle;
- Java Technology Network: le Technet d'Oracle, apparemment;
Bonnes pratiques
- JavaPractices.com: déjà ancien, mais dense et concis;
- Writing good Java code;
- Docs Oracle ... pas forcément utilisées par Oracle d'ailleurs!
Jargon
- POJO : Plain Old Java Objects (des structures simples, quoi);
Pièges du Java
Quelques points qui embêtent notoirement les programmeurs C++.
Dans les comparaisons
Entre objets, l'opérateur == teste juste l’égalité des références. Ce n'est pas équivalent à l'égalité de contenus (sauf entre singletons).
- En fait == ne vaut qu'entre des objets singletons comme enum, ou entre des non-objets comme int, double, boolean;
- L’équivalence entre objets se teste avec .equals(), donc on
compare:
- deux Double avec .equals();
- deux double avec ==;
Dans les manipulations d'arguments
Tous les arguments sont passés par copie d’adresse, donc pour changer un argument:
- ne pas utiliser =, qui écrasera juste le registre contenant l’adresse de l'argument, non-relu par l'appelant;
- modifier les champs via une méthode de l’argument s'il en existe;
- sinon créer une classe dont les membres sont les arguments à changer;
Dans les manipulations de fichiers
- BufferedWriter : perd les octets en attente si détruit avant appel à close();
Erreurs classiques à l'exécution
- java.lang.UnsatisfiedLinkError: no <truc> in java.library.path: problème dans la recherche d'une bibliothèque native (DLL par ex.); explication détaillée dans les forums
Tâches usuelles
Conversions
- Copier des tableaux : clone() réalise une copie superficiielle;
Lecture/Ecriture
Lecture/Ecriture vers fichiers
- Voir chez Oracle : Java:Basic IO et la classe IO: classes ReadAllBytes, BufferedWriter ...
- Java 7 a introduit `java.nio.file.Path <https://docs.oracle.com/javase/tutorial/essential/io/pathClass.html>`__ et de nouvelles façons de faire avec Files;
Analyser des fichiers XML
- Analyser du XML : sur le Site du Zéro, sur TutorialPoints.
Utiliser un logger
Construction/Analyse de chaînes de caractères
Lire et découper des chaînes de caractères
Principales méthodes pour obtenir ou découper des chaînes:
- <BufferedWriter>.readLine(): null, ou chaîne sans saut ou fin de ligne.
- <String>.split(<regex>,<N>): découpe selon <regex>, en au plus <N> morceaux dont le dernier contient toute la fin de chaîne;
- StringTokenizer : obsolète, un peu plus rapide que split(), car sans regexp;
La classe Scanner est plus souple, quoique beaucoup plus lente:
- voir un article en français
- attention, les tokens sont séparés AVANT détection de patterns par next(), prendre autre chose pour isoler des tokens avec espaces (cf classe matcher)
Extraire des nombres
Une méthode simple et un peu paresseuse est la classe Scanner:
- voir un article en français;
- séparateurs décimaux changeables par la méthode .useLocale();
Choix des séparateurs décimaux en général:
- Locale.ROOT (indépendant de la langue, mais proche anglo-saxon);
- Locale.UK (moins neutre, mais équivaut en principe au précédent);
S'il faut reconnaître des nombre sans se préoccuper de la langue:
- présentation dans le tutoriel Oracle;
- les classes dérivées de Number (comme Double,Integer, ...) ignorent les locales dans leurs méthodes .parseXXX() ou toString(): cf doc de leur membre valueOf().
Construire de longues chaînes
Un objet String étant unmutable, en construire un par étapes demande un intermédiaire dynamique comme StringBuilder:
- un objet StringBuilder est réutilisable sans réservation avec la méthode .setLength(0) (cf débat sur performances);
- Attention aux conversions d'accents : cf codage des String : pour contrôler l'encodage, passer par OutputStreamWriter;
Java pour programmeur C++
Démarrage et arguments du programme
Dans main(String[] args]), args[0] est le premier argument, pas le nom du programme;
Il est facile de se tromper dans la classe à lancer. Elle doit:
- avoir une méthode public static void main(String []);
- être donnée avec son nom de package complet;
Conventions du langage
- Types élémentaires en minuscules (int, double), comparables par ==;
- Types classe en majuscule, à comparer par .equals();
- Les types élémentaires ont des jumeaux objets (tels Integer ou Double) en majuscules;
- Ces jumeaux servent aux lectures: int Integer.parseInt(chaine), double Double.parseDouble(chaine) ...
- et aux conversions en chaîne: I.toString(..) si I est Integer (plus sur les conversions);
- Exceptions possibles: NumberFormatException, NullPointerException;
Changements d'habitudes
Contrairement au C++, Java ne permet pas de redéfinir les opérateurs;
En particulier, == ne teste que l'égalité des références de deux objets. En général, une méthode .equals() teste l'égalité des contenus.
Par ailleurs, les arguments sont passés par copie de référence:
- Pour des objets dont on peut modifier le contenu, c'est presque équivalent au passage par référence du C++;
- Mais pour des objets «unmutable» comme «String», il est impossible de modifier l'objet d'une référence
Opérations mathématiques classiques
- import java.lang.Math;
Structures de contrôle
Les exceptions
Les exceptions dérivent de Exception voir explication détaillée:
-
catch(Exception e) correspond au catch(...) du C++, avec particularités:
-
catch(ExA|ExB|ExC e): catch multiple (Java >7)
-
f(...) throws e1, ..., eN { ... }: une fonction doit lister les checked exceptions qu'elle peut produire;
-
checked exception: celles que l’on peut attraper par catch;
-
unchecked exception: celles qui terminent le programme de toute manière (ex: NullPointerException);
-
Syntaxe try ... finally ... : pour fermer les objets importants en dépit des exceptions.
-
Syntaxe try-with-resource : n'oublie pas de fermer un objet.
try (FileWriter fw=...) { ... } catch (IOException e) { ... }
Boucles et itérations
- for(Objet x : Tableau) { ... }
Structures de données
Types de base
- Arrays : à la base, tous les éléments d’un Array sont de même type;
- int, double, boolean: ne sont pas des objets (contrairement à leurs acolytes en majuscules), et peuvent donc être comparés par ==;
Enumérations
- Les énumérations: forcent à utiliser des noms plutôt que des valeurs en dur; Ce sont aussi des singletons, comparables pour une fois avec ==;
- Set et EnumSet: utilisation pour sous-ensemble de type énuméré
Collections
-
Java Collections, et en particulier ce résumé; cf aussi un aide-mémoire assez complet;
-
Contrairement aux tableaux de base [], les collections ne peuvent contenir que des objets, pas des types élémentaires comme int, boolean, .... ;
-
Interfaces les plus courantes pour les Collections :
- Set, List, Queue, Deque (double-ended queue), List, Map;
- SortedSet et SortedMap
-
Implémentations les plus courantes pour les Collections :
- ArrayList, ArrayDeque : interfaces reposant sur tableau dynamique;
- Vector` : comme ArrayList, mais synchronisé entre threads;
- HashMap, LinkedHashMap, avec plusieurs façons d’itérer dessus, et des méthodes containsKey(), put(), putIfAbsent(), get(), getOrDefault();
- Convertisseurs: souvent toArray() ou subList();
-
Astuces et conversions entre collections et tableaux:
-
ArrayList<T> AL vers <T>[] A:
T [] A = new T[AL.size()] A = AL.toArray(A) # Verbeux, hein ? A = AL.toArray(new T[0]) # Plus court
-
Ensemble à partir d'un tableau:
Set<T> e = new HashSet<>(Arrays.asList(A))
-
Initialisations et tris rapides sur ArrayList:
al = new ArrayList<Integer>(Arrays.asList(1,2,3,)); al.subList(k,al.size()).clear() : Efface de k à la fin Collections.sort(al);
-
Quelques bibliothèques
JavaFX
Les bases
- Présentation de référence chez Oracle;
- JavaFX by Example: une petite application d'exemple;
- JavaFXTutorials;
- Refcard JavaFX sur DZonec.com: résumé des notions importantes;
- Sur Fedora 25, il faut compiler; Voir le site OpenJFX: quelques différences avec Oracle, expliquées par 'JewelSea'
- Projet 7GUIs : objectif comparer les langages pour des IHM de plus en plus complexes.
Quelques classes de base
- GridPane: éléments à mettre, avec éventuellement un index de ligne/colonne;
- ComboBox: .getValue() renvoie le texte sélectionné, mais pour l'index il faut tester .getSelectionModel().isSelected(j) sur chacun des combo.getItems();
Bibliothèque JFreeChart
Pour la plupart des graphiques en Java.
- Pour commencer:
- Site officiel: jfree.org, et sources sur github
- Tutoriels : une liste, une intro détaillée;
- Exemples avancés:
- avec des seuils: mettre des seuils sur une ligne
- avec des lignes différentes : choisir le style de ligne
- bricolage sur les «renderers» pour heatmaps avex xyblockrenderer
- Intégration / comparaison avec JavaFX
- graphiques en tâche de fond
- ChartCanvas : pour afficher dans JavaFX