Java: tribulations d'un développeur C++

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

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

Présentations plus avancées

Bonnes pratiques

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

Tâches usuelles

Conversions

  • Copier des tableaux : clone() réalise une copie superficiielle;

Lecture/Ecriture

Lecture/Ecriture vers fichiers

Analyser des fichiers XML

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:

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:

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

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

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.