Thierry :
* 35 ans
* 12 ans d’expérience
* Consultant freelance (ICAUDA)
- Assurance
- Grande distribution
* Professeur de Génie Logiciel à l'ESIEA
* Rédacteur pour developpez.com
Etienne :
* 25 ans
* Sfeirien
* Ingénieur généraliste, consultant Java depuis 2008
- Comptabilité
- Édition
- E-commerce
* Utilise Google Collections / Guava depuis 3 ans
THIERRY
Dans cette présentation, on ne peut pas parler de tout ce que contient Guava. On a fait le choix de ne présenter que des aspects simples mais qui nous semblent importants. Ces points sont ceux qui donnent envie d'en savoir plus.
Open Source - Licence Apache 2.0
Premier contact avec guava, on commence par un truc bien pratique, même si ce n'est pas révolutionnaire .
La plupart d'entre nous n'aurons pas accès à Java 7 avant 2 ou 3 ans sur les projets de leur entreprise. Je travaille moi-même encore sur des projets en java 1.4 et même en 1.2
Les static factories sont recommandées dans effective Java. D’ailleurs Guava est très inspiré par le contenu de ce livre.
Avec les imports statiques de :
* Lists.newArrayList
* Sets.newTreeSet
* Maps.newHashMap
Le Dog est un peu le fil conducteur de cette présentation.
Varargs
THIERRY
Si on utilise souvent ce Predicate, on peut le définir avec une variable et pas forcément en param
In, and, or, etc. sont dans Predicates
Ici j'ai besoin d'introduire un objet Chien pour la suite.
C'est assez courant de devoir convertir des Objets sur un projet. Disons par exemple que le projet en cours utilise encore des objet en francais et ne s'est pas encore mis à la nouvelle norme de la société.
Il faut bien comprendre qu'on travaille avec des vues. La transformation est faite de façon lazy.
L'appel à size ou à isempty ne déclenche pas le transform.
Par contre elle sera faite à chaque fois qu'on va itérer sur la liste.
Du coup, si on pense qu'on utilisera plus d'une fois la liste, il vaut mieux directement convertir la vue en une vraie liste.
Ca se marie relativement bien avec les Converters de Spring
Guava n'aime pas les null et renvoie une exception si aucun élément n'est trouvé
THIERRY
Il ne faut pas se demander si une collection doit etre immutable mais plutôt si elle a besoin d’être mutable. Et en général, la réponse est non : elle n'a pas besoin d’être mutable, donc autant utiliser une immutable.
Les Immutable ont notamment un interet du point de vue de la concurrence, ce que va expliquer Etienne plus tard
Etienne : Je ne vais pas vraiment l'expliquer. Je vais juste dire en deux phrases que Guava est particulièrement adapté à la programmation concurrente, en particulier grâce à ses collections immutables. Mais je ne rentre pas ds les détails.
C'est super complexe comme écriture. On a tous déjà pondu au moins une fois ce genre de chose, voire pire.
Ici les gens oublient souvent le « final »...
Penser à dire que la vue n'est pas modifiable, mais le set d'origine, si.
Ici je ne déclare pas la constante sous forme de Set mais comme un ImmutableSet pour bien faire passer le message.
« constante » → « variable »
ETIENNE
Je vais maintenant vous présenter les classes qui sont à mon avis les plus utiles dans le package Guava Base.
Ce package contient un grand nombre de classes et méthodes utilitaires « basiques ». On peut considérer qu'il étend en quelque sorte « java.lang ».
Commençons par la classe Objects, qui simplifie beaucoup l'écriture des méthodes classiques d'Object.
Pour cela, on va reprendre pour exemple notre chien, un bean avec 3 champs.
Lorsque les champs peuvent être null, la méthode equals() devient vite verbeuse...
Objects.equal() permet de faire un test d'égalité qui gère les objets nulls, ce qui simplifie beaucoup le code.
De même pour la méthode hashCode(), qui peut être implémentée en une ligne grâce à Objects.hashCode.
Objects.toStringHelper() simplifie l'écriture de la méthode toString(), grâce au builder pattern.
Le code est aussi beaucoup plus lisible.
Pour finir, la méthode compareTo() peut être simplifiée grâce à ComparisonChain.
Le builder pattern nous permet d'enchaîner des comparaisons très simplement..
Il est possible de customiser chaque comparaison de ComparisonChain en passant un comparateur.
C'est notamment utile pour comparer des objects nulls, en utilisant un comparateur qui classe ces derniers à la fin plutôt que de jetter une NPE...
ETIENNE
Passons maintenant à la classe Preconditions, l'une de mes classes préférées, qui permet de valider les arguments des méthodes et constructeurs..
Je vais prendre pour exemple une classe Money, associant un montant et une devise. C'est une classe que l'on retrouve dans de nombreux projets.
Une bonne pratique est de rendre immutables les value objects de ce genre. Cela permet d'éviter les copies défensives, de simplifier la programmation concurrente, et de préserver les invariants une fois l'objet construit.
On voudrait valider les arguments de son constructeur, pour s'assurer qu'elle est correctement construite.
On va donc vérifier que le montant et la devise ne sont pas nulls, et vérifier que le montant est positif.
En Java classique, c'est assez verbeux.
La classe Preconditions permet de simplifier ces checks, grâce à checkNotNull, qui lance une NPE si l'argument est null, et checkArgument, qui lance une IllegalArgumentException si une condition est fausse.
Notez que l'on peut paramétrer les messages d'erreur.
Le code est plus court et plus lisible.
Mais on peut encore simplifier.
Les designers de Guava ont été astucieux, car checkNotNull() retourne son argument après validation. Cela permet de valider un argument avec checkNotNull et de l'assigner à un champ, en une seule ligne.
C'est une fonctionnalité que j'utilise tout le temps.
ETIENNE
Je vais maintenant vous présenter la classe CharMatcher, qui est très utile pour manipuler les chaînes de caractères.
Google avait à l'origine une classe StringUtils, contenant de nombreuses méthodes utilitaires pour manipuler les chaînes de caractères.
Ils avaient par exemple différentes version de la méthode remove(). La première, remove(), supprimait des caractères précis d'une chaîne. La seconde, removeDigits(), supprimait tous les nombres. La troisième, removeWhitespace(), enlevait les espaces blancs.
Les méthodes se multipliant, ils ont réalisé qu'elles représentaient en réalité un produit croisé de deux notions :
(a) qu'est-ce qu'un caractère qui « matche »
(b) que faire de ces caractères
Cette approche n'étant pas scalable, ils ont créé CharMatcher.
Une instance de CharMatcher correspond à la première notion, et les opérations que l'on invoque sur ce CharMatcher correspondent à la seconde.
On peut obtenir un CharMatcher de différentes manières.
La première consiste à utiliser l'une des constantes disponibles.
THIERRY
* Multimap
* BiMap
* MultiSet
THIERRY
Une Multimap, c'est grossomodo une « Map de List »
THIERRY
Pour faire une Bimap en java pur, il faut en gros faire deux map symétriques
THIERRY
List : Ordre + Doublon
Set : Sans ordre significatif + Sans Doublon
MultiSet : Sans ordre significatif + Doublon
THIERRY
C'est le genre de fonction qu'on a tous eut à écrire un jour ou l'autre.
Le splitter est grosso modo l'inverse du joiner.
ETIENNE
Je vais maintenant vous présenter deux types de caches disponibles dans Guava :
- la mémoization d'une part, qui permet de cacher le résultat unique d'un appel à une fonction
- les caches « classiques » d'autre part, qui permettent de cacher des couples clé-valeur, comme dans une Map
Prenons pour exemple un service permettant d'obtenir des informations sur la dernière version du JDK disponible.
Pour ce faire, ce service appelle un web-service, ce qui est lent et gourmand en ressources.
On voudrait donc cacher son résultat.
Une première approche consiste à faire de la mémoization « manuelle », en cachant le résultat dans un champ.
Cela nécessite cependant de synchroniser la méthode, ce qui peut poser des problèmes de contention.
On pourrait éviter la synchronisation, en mettant en place un double-checked locking, mais le code est complexe, verbeux, et souvent mal implémenté.
Guava permet de mémoizer une valeur très simplement, grâce à l'interface Supplier.
Un Supplier est une classe qui peut fournir des objets d'un certain type, une Factory en quelque sorte.
Pour mémoizer notre appel, on créé un Supplier, qui va appeller notre web-service lorsque l'on invoque sa méthode « get ».
On combine ce Supplier avec la méthode utilitaire Suppliers.memoize(). Elle renvoie un nouveau Supplier, qui décore le premier, et cache le résultat du premier appel.
L'avantage, c'est que les problématiques de synchronization sont gérées par Guava, avec une variante du double-checked locking.
Problème : nous cachons la version du JDK éternellement... Or, il arrive -certes rarement- qu'une nouvelle version soit disponible.
Pas de problème, Guava propose une seconde méthode, Suppliers.memoizeWithExpiration(), qui permet de mémoizer le résultat pour une durée déterminée.
Dans notre exemple, un an me semble raisonnable ;)
ETIENNE
Nous allons maintenant voir comment mettre en place un cache clé-valeur.
Prenons pour exemple un service permettant de récupérer des informations sur un film, en appelant IMDB, l'Internet Movie DataBase, via un web service.
On voudrait cacher les appels à ce service.
Contrairement à l'exemple précédent, le service peut renvoyer des résultats différents selon l'ID du film passé en paramètre.
Guava 10 a introduit un nouveau package dédié aux caches, com.google.common.cache. Il contient une interface pour représenter un cache, avec des méthodes pour charger et invalider des valeurs, obtenir des statistiques d'utilisation, et obtenir une vue du cache sous la forme d'une Map.
Introduisons le cache Guava dans notre exemple.
Pour le construire, il est recommandé d'utiliser CacheBuilder, qui permet de configurer simplement un cache via une interface fluide.
On passe à notre builder un CacheLoader, l'interface à implémenter pour indiquer à Guava comment charger les nouvelles valeurs.
Il nous suffit maintenant d'appeler filmCache.getUnchecked() dans la méthode getFilm(). Le cache se charge du reste.
Problème : nous voulons expirer les éléments régulièrement, et limiter la taille totale du cache, qui utilise trop de mémoire.
Nous pouvons configurer ces propriétés via l'interface fluide de CacheBuilder:
- taille maximum de 2000 éléments
- expiration des éléments après 30 minutes
CacheBuilder est un excellent exemple d'une API très simplequi cache (;)) des fonctionnalités puissantes et complexes.
ETIENNE
Le package common.io contient des classes et méthodes utilitaires pour travailler avec Java I/O, c'est-à-dire les input streams, output streams, readers, writers, et fichiers.
La gestion des entrées/sorties est très verbeuse en Java. Il faut fermer correctement les ressources, et gérer les IOExceptions pouvant être lancé lors de l'ouverture, l'utilisation, ou la fermeture de ces dernières.
La bonne pratique est de fermer les ressources dans des blocs finally, comme dans cet exemple. Si une exception est lancée lors de l'utilisation de la ressource, il faut veiller à ne pas l'« avaler » en jettant une nouvelle exception dans le bloc finally.
http://www.ibm.com/developerworks/java/library/j-jtp03216/index.html
Java 7 simplifie la gestion des ressources, avec les interfaces AutoCloseable, et le méchanisme de multi-exceptions (todo).
Mais nombre d'entreprises sont timides (?) à l'adoption de Java 7 (?). Et Guava I/O a plus d'un tour dans son sac...
Voici l'équivalent Guava du code précédent.
Guava I/O est basé sur la notion d'IntputSupplier et d'OutputSupplier. Ces deux interfaces représentent des fabriques d'objets I/O, encapsulant la création d'InputStream, OutputStream, Reader, et Writer.
La construction de ces ressources peut souvent lancer des IOExceptions. En utilisant ces suppliers, on laisse Guava se charger du cycle de vie des ressources (ouverture, fermeture, gestion des exceptions).
Cette approche est aussi très modulaire. Elle permet par exemple à Google d'avoir des suppliers basés sur GFS, le file-system interne de Google.
Les méthodes utilitaires de Guava I/O sont réparties dans 3 classes. ByteStreams permet de manipuler des bytes.
CharStreams contient des méthodes équivalentes, mais fonctionnant avec des caractères, càd en terme de Reader, Writer, CharSequence, et String.
Enfin, Files, se base sur ByteStreams et CharStreams pour faciliter la manipulation de fichiers
ETIENNE
Introduire brièvement util.concurrent en disant que Guava est tout particulièrement indiqué pour la programmation concurrente, car privilégiant l'immutabilité (collections immutables, mais aussi nombre autre classes telles que CharMatcher / Joiner / Splitter)
ListenableFuture est un Future avec une méthode supplémentaire, qui permet d'ajouter des listeners. Ils sont appelés lorsque le Future est terminé, qu'il s'agisse d'un succès, d'une exception, ou d'une annulation. Si le Future est déjà terminé lorsque l'on appelle addListener(), le listener est appelé immédiatement.
Cela permet de faire de la programmation asynchrone,
ETIENNE ou THIERRY
Déjà possible avec les Functionnal-Collections
THIERRY
Ici j'utilise des Set à la place des listes
THIERRY
Attention : ne pas oublier equals et hashCode
THIERRY
Petit bonus du genre de fonctionnalité qu'on est tout le temps obligés de faire.