JAVA avancé: les API avancées Java

Le JDK fourni un certains nombres d’API intégrés au JDK pour des fonctionnalités avancées.

Plusieurs chapitres dans cette partie détaillent les API pour utiliser les fonctionnalités suivantes :

  • les collections : propose une revue des classes fournies par le JDK pour gérer des ensembles d’objets
  • les flux : explore les classes utiles à la mise en oeuvre d’un des mécanismes de base pour échanger des données
  • la sérialisation : présente un mécanisme qui permet de rendre persistant un objet
  • l’intéraction avec le réseau :
  • l’accès aux bases de données : indique comment utiliser JDBC pour accéder
  • l’appel de méthodes distantes : étudie la mise en oeuvre de la technologie RMI pour permettre l’appel de méthodes distantes.
  • l’internationalisation : traite d’une façon pratique de la possibilité d’internationnaliser une application
  • les composants java beans : examine comment développer et utiliser des composants réutilisables
  • logging : indique comment mettre en oeuvre deux API pour la gestion des logs : Log4J du projet open source jakarta et l’API logging du JDK 1.4

1. Les collections

Les collections sont des objets qui permettent de gérer des ensembles d’objets. Ces ensembles de données peuvent être définis avec plusieurs caractéristiques : la possibilité de gérer des doublons, de gérer un ordre de tri, etc. …

Chaque objet contenu dans une collection est appelé un élément.

1.1. Présentation du framework collection

Dans la version 1 du J.D.K., il n’existe qu’un nombre restreint de classes pour gérer des ensembles de données :

  • Vector
  • Stack
  • Hashtable
  • Bitset

L’interface Enumeration permet de parcourir le contenu de ces objets.

Pour combler le manque d’objets adaptés, la version 2 du J.D.K. apporte un framework complet pour gérer les collections. Cette bibliothèque contient un ensemble de classes et interfaces. Elle fourni également un certain nombre de classes abstraites qui implémentent partiellement certaines interfaces.

Les interfaces à utiliser par des objets qui gèrent des collections sont :

  • Collection : interface qui est implementée par la plupart des objets qui gèrent des collections
  • Map : interface qui définie des méthodes pour des objets qui gèrent des collections sous la forme clé/valeur
  • Set : interface pour des objets qui n’autorisent pas la gestion des doublons dans l’ensemble
  • List : interface pour des objets qui autorisent la gestion des doublons et un accès direct à un élément
  • SortedSet : interface qui étend l’interface Set et permet d’ordonner l’ensemble
  • SortedMap : interface qui étend l’interface Map et permet d’ordonner l’ensemble

Certaines méthodes définies dans ces interfaces sont dites optionnelles : leur définition est donc obligatoire mais si l’opération n’est pas supportées alors la méthode doit lever une exception particulière. Ceci permet de réduire le nombre d’interfaces et de répondre au maximum de cas.

Le framework propose plusieurs objets qui implémentent ces interfaces et qui peuvent être directement utilisés :

  • HashSet : HashTable qui implémente l’interface Set
  • TreeSet : arbre qui implémente l’interface SortedSet
  • ArrayList : tableau dynamique qui implémente l’interface List
  • LinkedList : liste doublement chaînée (parcours de la liste dans les deux sens) qui implémente l’interface List
  • HashMap : HashTable qui implémente l’interface Map
  • TreeMap : arbre qui implémente l’interface SortedMap

Le framework définit aussi des interfaces pour faciliter le parcours des collections et leur tri :

  • Iterator : interface pour le parcours des collections
  • ListIterator : interface pour le parcours des listes dans les deux sens et modifier les éléments lors de ce parcours
  • Comparable : interface pour définir un ordre de tri naturel pour un objet
  • Comparator : interface pour définir un ordre de tri quelconque

Deux classes existantes dans les précédentes versions du JDK ont été modifiées pour implémenter certaines interfaces du framework :

  • Vector : tableau à taille variable qui implémente maintenant l’interface List
  • HashTable : table de hashage qui implémente maintenant l’interface Map

Le framework propose la classe Collections qui contient de nombreuses méthodes statiques pour réaliser certaines opérations sur une collection. Plusieurs méthodes unmodifiableXXX() (ou XXX représente une interface d’une collection) permettent de rendre une collection non modifiable. Plusieurs méthodes synchronizedXXX() permettent d’obtenir une version synchronisée d’une collection pouvant ainsi être manipulée de façon sûre par plusieurs threads. Enfin plusieurs méthodes permettent de réaliser des traitements sur la collection : tri et duplication d’une liste, recherche du plus petit et du plus grand élément, etc. …

Le framework fourni plusieurs classes abstraites qui proposent une implémentation partielle d’une interface pour faciliter la création d’un collection personnalisée : AbstractCollection, AbstractList, AbstractMap, AbstractSequentialList et AbstractSet.

Les objets du framework stockent toujours des références sur les objets contenus dans la collection et non les objets eux mêmes. Ce sont obligatoirement des objets qui doivent être ajoutés dans une collection. Il n’est pas possible de stocker directement des types primitifs : il faut obligatoirement encapsuler ces données dans des wrappers.

Toutes les classes de gestion de collection du framework ne sont pas synchronisées : elles ne prennent pas en charge les traitements multi−threads. Le framework propose des méthodes pour obtenir des objets de gestion de collections qui prennent en charge cette fonctionnalité. Les classes Vector et Hashtable était synchronisée mais l’utilisation d’une collection ne se fait généralement pas de ce contexte. Pour réduire les temps de traitement dans la plupart des cas, elles ne sont pas synchronisées par défaut.

Lors de l’utilisation de ces classes, il est préférable de stocker la référence de ces objets sous la forme d’une interface qu’ils implémentent plutôt que sous leur forme objet. Ceci rend le code plus facile à modifier si le type de l’objet qui gèrent la collection doit être changé.

1.2. Les interfaces des collections

Le framework de java 2 définit 6 interfaces en relation directe avec les collections qui sont regroupées dans deux arborescences :

Le JDK ne fourni pas de classes qui implémentent directement l’interface Collection.

Le tableau ci dessous présente les différentes classes qui implémentent les interfaces de bases Set, List et Map :

Set List Map

Hashtable

HashSet

HashMap

Tableau redimensionnable

 

ArrayList, Vector

 
    

Arbre

TreeSet

 

TreeMap

    

Liste chaînée

 

LinkedList

 
    

Classes du JDK 1.1

 

Stack

Hashtable

    

Pour gérer toutes les situations de façon simple, certaines méthodes peuvent être définies dans une interface comme «optionnelles ». Pour celles ci, les classes qui implémentent une telle interface, ne sont pas obligées d’implémenter du code qui réalise un traitement mais simplement lève une exception si cette fonctionnalité n’est pas supportée.

Le nombre d’interfaces est ainsi grandement réduit.

Cette exception est du type UnsupportedOperationException. Pour éviter de protéger tous les appels de méthodes d’un objet gérant les collections dans un bloc try−catch, cette exception hérite de la classe RuntimeException.

Toutes les classes fournies par le J.D.K. qui implémentent une des interfaces héritant de Collection implémentent toutes les opérations optionnelles.

1.2.1. L’interface Collection

Cette interface définit des méthodes pour des objets qui gèrent des éléments d’une façon assez générale. Elle est la super interface de plusieurs interfaces du framework.

Plusieurs classes qui gèrent une collection implémentent une interface qui hérite de l’interface Collection. Cette interface est une des deux racines de l’arborescence des collections.

Cette interface définit plusieurs méthodes :

Méthode

Rôle

 
   

boolean add(Object)

ajoute l’élément fourni en paramètre à la collection. La valeur de retour indique si

 

la collection a été mise à jour

 
  
   

boolean addAll(Collection)

ajoute à la collection tous les éléments de la collection fournie en paramètre

 
   

void clear()

supprime tous les éléments de la collection

 
   

boolean contains(Object)

indique si la collection contient au moins un élément identique à celui fourni en

 

paramètre

 
  
   

boolean containsAll(Collection)

indique si tous les éléments de la collection fournie en paramètre sont contenus

 

dans la collection

 
  
   

boolean isEmpty()

indique si la collection est vide

 
   

Iterator iterator()

renvoie un objet qui permet de parcourir l’ensemble des éléments de la collection

 
   

boolean remove(Object)

supprime l’élément fourni en paramètre de la collection. La valeur de retour

 

indique si la collection a été mise à jour

 
  
   

boolean removeAll(Collection)

supprime tous les éléments de la collection qui sont contenus dans la collection

 

fournie en paramètre

 
  
   

int size()

renvoie le nombre d’éléments contenu dans la collection

 
   

Object[] toArray()

renvoie d’un tableau d’objets qui contient tous les éléments de la collection

 
   

Cette interface représente un minimum commun pour les objets qui gèrent des collections : ajout d’éléments, suppression d’éléments, vérifier la présence d’un objet dans la collection, parcours de la collection et quelques opérations diverses sur la totalité de la collection.

Ce tronc commun permet entre autre de définir pour chaque objet gérant une collection, un constructeur pour cette objet demandant un objet de type Collection en paramètre. La collection est ainsi initialisée avec les éléments contenus dans la collection fournie en paramètre.

Attention : il ne faut pas ajouter dans une collection une référence à la collection elle−même.

1.2.2. L’interface Iterator

Cette interface définie des méthodes pour des objets capables de parcourir les données d’une collection.

La définition de cette nouvelle interface par rapport à l’interface Enumeration a été justifiée par l’ajout de la fonctionnalité de suppression et la réduction des noms de méthodes.

Méthode

Rôle

  

boolean hasNext()

indique si il reste au moins à parcourir dans la collection

  

Object next()

renvoie la prochain élément dans la collection

  

void remove()

supprime le dernier élément parcouru

  

La méthode hasNext() est équivalente à la méthode hasMoreElements() de l’interface Enumeration.

La méthode next() est équivalente à la méthode nextElement() de l’interface Enumeration.

La méthode next() lève une exception de type NoSuchElementException si elle est appelée alors que la fin du parcours des éléments est atteinte. Pour éviter la lever de cette exception, il suffit d’appeler la méthode hasNext() et de conditionner avec le résultat l’appel à la méthode next().

Exemple ( code java 1.2 ) :

Iterator iterator = collection.Iterator();

while (iterator.hasNext()) {

System.out.println(“objet = ”+iterator.next());

}

La méthode remove() permet de supprimer l’élément renvoyé par le dernier appel à la méthode next(). Il est ainsi impossible d’appeler la méthode remove() sans un appel correspondant à next() : on ne peut pas appeler deux fois de suite la méthode remove().

Exemple ( code java 1.2 ) : suppression du premier élément

Iterator iterator = collection.Iterator();

if (iterator.hasNext()) {

iterator.next();

itérator.remove();

}

Si aucun appel à la méthode next() ne correspond à celui de la méthode remove(), une exception de type IllegalStateException est levée

1.3. Les listes

Une liste est une collection ordonnée d’éléments qui autorise d’avoir des doublons. Etant ordonnée, un élément d’une liste peut être accédé à partir de son index.

1.3.1. L’interface List

Cette interface étend l’interface Collection.

Les collections qui implémentent cette interface autorisent les doublons dans les éléments de la liste. Ils autorisent aussi l’insertion d’éléments null.

L’interface List propose plusieurs méthodes pour un accès à partir d’un index aux éléments de la liste. La gestion de cet index commence à zéro.

Pour les listes, une interface particulière est définie pour assurer le parcours dans les deux sens de la liste et assurer des mises à jour : l’interface ListIterator

Méthode

Rôle

 
   

ListIterator listIterator()

renvoie un objet capable de parcourir la liste

 
   

Object set (int, Object)

remplace l’élément contenu à la position précisée par l’objet fourni en paramètre

 
   

void add(int, Object)

ajouter l’élément fourni en paramètre à la position précisée

 
   

Object get(int)

renvoie l’élément à la position précisée

 
   

int indexOf(Object)

renvoie l’index du premier élément fourni en paramètre dans la liste ou –1 si

 

l’élément n’est pas dans la liste

 
  
   

ListIterator listIterator()

renvoie un objet pour parcourir la liste et la mettre à jour

 
   
 

renvoie un extrait de la liste contenant les éléments entre les deux index fournis (le

 

List subList(int,int)

premier index est inclus et le second est exclus). Les éléments contenus dans la

 

liste de retour sont des références sur la liste originale. Des mises à jour de ces

 
  
 

éléments impactent la liste originale.

 
   

int lastIndexOf(Object)

renvoie l’index du dernier élément fourni en paramètre dans la liste ou –1 si

 

l’élément n’est pas dans la liste

 
  
   

Object set(int, Object)

remplace l’élément à la position indiquée avec l’objet fourni

 
   

Le framework propose de classes qui implémentent l’interface List : LinkedList et ArrayList.

1.3.2. Les listes chaînées : la classe LinkedList

Cette classe hérite de la classe AbstractSequentialList et implémente donc l’interface List.

Elle représente une liste doublement chaînée.

Cette classe possède un constructeur sans paramètre et un qui demande une collection. Dans ce dernier cas, la liste sera initialisée avec les éléments de la collection fournie en paramètre.

Exemple ( code java 1.2 ) :

LinkedList listeChainee = new LinkedList();

Iterator iterator = listeChainee.Iterator();

listeChainee.add(“element 1”);

listeChainee.add(“element 2”);

listeChainee.add(“element 3”);

while (iterator.hasNext()) { System.out.println(“objet = ”+iterator.next());

}

Une liste chaînée gère une collection de façon ordonnée : l’ajout d’un élément peut se faire à la fin de la collection ou après n’importe quel élément. Dans ce cas, l’ajout est lié à la position courante lors d’un parcours.

Pour répondre à ce besoin, l’interface qui permet le parcours de la collection est une sous classe de l’interface Iterator :

l’interface ListIterator.

Comme les iterator sont utilisés pour faire des mises à jour dans la liste, une exception de type CurrentModificationException levé si un iterator parcours la liste alors qu’un autre fait des mises à jour (ajout ou suppression d’un élément dans la liste).

Pour gérer facilement cette situation, il est préférable si l’on sait qu’il y ait des mises à jour à faire de n’avoir qu’un seul iterator qui soit utilisé.

Plusieurs méthodes pour ajouter, supprimer ou obtenir le premier ou le dernier élément de la liste permettent d’utiliser cette classe pour gérer une pile :

Méthode

Rôle

  

void addFirst(Object)

insère l’objet en début de la liste

  

void addLast(Object)

insère l’objet en fin de la liste

  

Object getFirst()

renvoie le premier élément de la liste

  

Object getLast()

renvoie le dernier élément de la liste

  

Object removeFirst()

supprime le premier élément de la liste et renvoie le premier élément

  

Object removeLast()

supprime le dernier élément de la liste et renvoie le premier élément

  

De par les caractéristiques d’une liste chaînée, il n’existe pas de moyen d’obtenir un élément de la liste directement. Pourtant, la méthode contains() permet de savoir si un élément est contenu dans la liste et la méthode get() permet d’obtenir l’élément à la position fournie en paramètre. Il ne faut toutefois pas oublier que ces méthodes parcours la liste jusqu’à obtention du résultat, ce qui peut être particulièrement gourmand en terme de temps de réponse surtout si la méthode get() est appelée dans une boucle.

Pour cette raison, il ne faut surtout pas utiliser la méthode get() pour parcourir la liste.

La méthode toString() renvoie une chaîne qui contient tous les éléments de la liste.

1.3.3. L’interface ListIterator

Cette interface définie des méthodes pour parcourir la liste dans les deux sens et effectuer des mises à jour qui agissent par rapport à l’élément courant dans le parcours.

En plus des méthodes définies dans l’interface Iterator dont elle hérite, elle définie les méthodes suivantes :

Méthode

Roles

 
   

void add(Object)

ajoute un élément dans la liste en tenant de la position dans le parcours

 
   

boolean hasPrevious()

indique si il reste au moins un élément à parcourir dans la liste dans son sens

 

inverse

 
  
   

Object previous()

renvoi l’élément précédent dans la liste

 
   

void set(Object)

remplace l’élément courante par celui fourni en paramètre

 
   

La méthode add() de cette interface ne retourne pas un booléen indiquant que l’ajout à réussi.

Pour ajouter un élément en début de liste, il suffit d’appeler la méthode add() sans avoir appeler une seule fois la méthode next(). Pour ajouter un élément en fin de la liste, il suffit d’appeler la méthode next() autant de fois que nécessaire pour atteindre la fin de la liste et appeler la méthode add(). Plusieurs appels à la méthode add() successifs, ajoute les éléments à la position courante dans l’ordre d’appel de la méthode add().

1.3.4. Les tableaux redimensionnables : la classe ArrayList

Cette classe représente un tableau d’objets dont la taille est dynamique.

Elle hérite de la classe AbstractList donc elle implémente l’interface List.

Le fonctionnement de cette classe est identique à celui de la classe Vector.

La différence avec la classe Vector est que cette dernière est multi thread (toutes ces méthodes sont synchronisées). Pour une utilisation dans un thread unique, la synchronisation des méthodes est inutile et coûteuse. Il est alors préférable d’utiliser un objet de la classe ArrayList.

Elle définit plusieurs méthodes dont les principales sont :

Méthode

Rôle

 
   

boolean add(Object)

ajoute un élément à la fin du tableau

 
   

boolean addAll(Collection)

ajoute tous les éléments de la collection fournie en paramètre à la fin du tableau

 
   

boolean addAll(int, Collection)

ajoute tous les éléments de la collection fournie en paramètre dans la collection à

 

partir de la position précisée

 
  
   

void clear()

supprime tous les éléments du tableau

 
   

void ensureCapacity(int)

permet d’augmenter la capacité du tableau pour s’assurer qu’il puisse contenir le

 

nombre d’éléments passé en paramètre

 
  
   

Object get(index)

renvoie l’élément du tableau dont la position est précisée

 
   

int indexOf(Object)

renvoie la position de la première occurrence de l’élément fourni en paramètre

 
   

boolean isEmpty()

indique si le tableau est vide

 
   

int lastIndexOf(Object)

renvoie la position de la dernière occurrence de l’élément fourni en paramètre

 
   

Object remove(int)

supprime dans le tableau l’élément fourni en paramètre

 
   

void removeRange(int,int)

supprime tous les éléments du tableau de la première position fourni incluse

 

jusqu’à la dernière position fournie exclue

 
  
   

 

Object set(int, Object)

remplace l’élément à la position indiquée par celui fourni en paramètre

  

int size()

renvoie le nombre d’élément du tableau

  

void trimToSize()

ajuste la capacité du tableau sur sa taille actuelle

  

Chaque objet de type ArrayList gère une capacité qui est le nombre total d’élément qu’il est possible d’insérer avant d’agrandir le tableau. Cette capacité a donc une relation avec le nombre d’élément contenu dans la collection. Lors d’ajout dans la collection, cette capacité et le nombre d’élément de la collection détermine si le tableau doit être agrandi. Si un nombre important d’élément doit être ajouté, il est possible de forcer l’agrandissement de cette capacité avec la méthode ensureCapacity(). Son usage évite une perte de temps liée au recalcul de la taille de la collection. Un constructeur permet de préciser la capacité initiale.

1.4. Les ensembles

Un ensemble (Set) est une collection qui n’autorise pas l’insertion de doublons.

1.4.1. L’interface Set

Cette classe définit les méthodes d’une collection qui n’accepte pas de doublons dans ces éléments. Elle hérite de l’interface Collection mais elle ne définie pas de nouvelle méthode.

Pour déterminer si un élément est déjà inséré dans la collection, la méthode equals() est utilisée.

Le framework propose deux classes qui implémentent l’interface Set : TreeSet et HashSet

Le choix entre ces deux objets est liés à la nécessité de trié les éléments :

  • les éléments d’un objet HashSet ne sont pas triés : l’insertion d’un nouvel élément est rapide
  • les éléments d’un objet TreeSet sont triés : l’insertion d’un nouvel éléments est plus long

1.4.2. L’interface SortedSet

Cette interface définie une collection de type ensemble triée. Elle hérite de l’interface Set.

Le tri de l’ensemble peut être assuré par deux façons :

  • les éléments contenus dans l’ensemble implémentent l’interface Comparable pour définir leur ordre naturel
  • il faut fournir au constructeur de l’ensemble un objet Comparator qui définit l’ordre de tri à utiliser

Elle définie plusieurs méthodes pour tirer parti de cette ordre :

Méthode

Rôle

 
   

Comparator comparator()

renvoie l’objet qui permet de trier l’ensemble

 
   

Object first()

renvoie le premier élément de l’ensemble

 
   

SortedSet headSet(Object)

renvoie un sous ensemble contenant tous les éléments inférieurs à celui fourni en

 

paramètre

 
  
   

Object last()

renvoie le dernier élément de l’ensemble

 
   

renvoie un sous ensemble contenant les éléments compris entre le premier

SortedSet subSet(Object, Object)

paramètre inclus et le second exclus

renvoie un sous ensemble contenant tous les éléments supérieurs ou égaux à celui

SortedSet tailSet(Object)

fourni en paramètre

1.4.3. La classe HashSet

Cette classe est un ensemble sans ordre de tri particulier.

Les éléments sont stockés dans une table de hashage : cette table possède une capacité.

Exemple ( code java 1.2 ) :

import java.util.*;

public class TestHashSet {

public static void main(String args[]) {

Set set = new HashSet();

set.add(« CCCCC »);

set.add(« BBBBB »);

set.add(« DDDDD »);

set.add(« BBBBB »);

set.add(« AAAAA »);

Iterator iterator = set.iterator();

while (iterator.hasNext()) {System.out.println(iterator.next());}

}

}

Résultat :

AAAAA

DDDDD

BBBBB

CCCCC

1.4.4. La classe TreeSet

Cette classe est un arbre qui représente un ensemble trié d’éléments.

Cette classe permet d’insérer des éléments dans n’importe quel ordre et de restituer ces éléments dans un ordre précis lors de son parcours.

L’implémentation de cette classe insère un nouvel élément dans l’arbre à la position correspondant à celle déterminée par l’ordre de tri. L’insertion d’un nouvel élément dans un objet de la classe TreeSet est donc plus lent mais le tri est directement effectué.

L’ordre utilisé est celui indiqué par les objets insérés si ils implémentent l’interface Comparable pour un ordre de tri naturel ou fournir un objet de type Comparator au constructeur de l’objet TreeSet pour définir l’ordre de tri.

Exemple ( code java 1.2 ) :

import java.util.*;

public class TestTreeSet {

public static void main(String args[]) {

Set set = new TreeSet();

set.add(« CCCCC »);

set.add(« BBBBB »);

set.add(« DDDDD »);

set.add(« BBBBB »);

set.add(« AAAAA »);

Iterator iterator = set.iterator();

while (iterator.hasNext()) {System.out.println(iterator.next());}

}

}

 

Résultat :

AAAAA

BBBBB

CCCCC

DDDDD

1.5. Les collections gérées sous la forme clé/valeur

Ce type de collection gère les éléments avec deux entités : une clé et une valeur associée. La clé doit être unique donc il ne peut y avoir de doublons. En revanche la même valeur peut être associées à plusieurs clés différentes.

Avant l’apparition du framework collections, la classe dédiée à cette gestion était la classe Hashtable.

1.5.1. L’interface Map

Cette interface est une des deux racines de l’arborescence des collections. Les collections qui implémentent cette interface ne peuvent contenir de doublons. Chaque clé est associée à une valeur et une seule.

Elle définie plusieurs méthodes pour agir sur la collection :

Méthode

Rôle

  

void clear()

supprime tous les éléments de la collection

  

boolean containsKey(Object)

indique si la clé est contenue dans la collection

  

boolean containsValue(Object)

indique si la valeur est contenue dans la collection

  

Set entrySet()

renvoie un ensemble contenant les valeurs de la collection

  

Object get(Object)

renvoie la valeur associée à la clé fournie en paramètre

  

boolean isEmpty()

indique si la collection est vide

  

Set keySet()

renvoie un ensemble contenant les clés de la collection

  

Object put(Object, Object)

insère la clé et sa valeur associée fournies en paramètres

  

void putAll(Map)

insère toutes les clés/valeurs de l’objet fourni en paramètre

  

Collection values()

renvoie une collection qui contient toutes les éléments des éléments

  

Object remove(Object)

supprime l’élément dont la clé est fournie en paramètre

  

int size()

renvoie le nombre d’éléments de la collection

  

La méthode entrySet() permet d’obtenir un ensemble contenant toutes les clés.

La méthode values() permet d’obtenir une collection contenant toutes les valeurs. La valeur de retour est une Collection et non un ensemble car il peut y avoir des doublons (plusieurs clés peuvent être associées à la même valeur).

Le J.D.K. 1.2 propose deux nouvelles classes qui implémentent cette interface :

  • HashMap qui stocke les éléments dans une table de hashage
  • TreeMap qui stocke les éléments dans un arbre

La classe HashTable a été mise à jour pour implémenter aussi cette interface.

1.5.2. L’interface SortedMap

Cette interface définie une collection de type Map triée sur la clé. Elle hérite de l’interface Map.

Le tri peut être assuré par deux façons :

  • les clés contenues dans la collection implémentent l’interface Comparable pour définir leur ordre naturel
  • il faut fournir au constructeur de la collection un objet Comparator qui définit l’ordre de tri à utiliser

Elle définit plusieurs méthodes pour tirer parti de cette ordre :

Méthode

Rôle

 
   

Comparator comparator()

renvoie l’objet qui permet de trier la collection

 
   

Object first()

renvoie le premier élément de la collection

 
   

SortedSet headMap(Object)

renvoie une sous collection contenant tous les éléments inférieurs à celui fourni en

 

paramètre

 
  
   

Object last()

renvoie le dernier élément de la collection

 
   

SortedMap subMap(Object,

renvoie une sous collection contenant les éléments compris entre le premier

 

Object)

paramètre inclus et le second exclus

 
   

SortedMap tailMap(Object)

renvoie une sous collection contenant tous les éléments supérieurs ou égaux à celui

 

fourni en paramètre

 
  
   

1.5.3. La classe Hashtable

1.5.4. La classe TreeMap

1.5.5. La classe HashMap

1.6. Le tri des collections

L’ordre de tri est défini grace à deux interfaces :

  • Comparable
  • Comparator

1.6.1. L’interface Comparable

Tous les objets qui doivent définir un ordre naturel utilisé par le tri d’une collection avec cet ordre doivent implémenter cette interface.

Cette interface ne définit qu’une seule méthode : int compareTo(Object).

Cette méthode doit renvoyer :

  • une valeur entière négative si l’objet courant est inférieur à l’objet fourni
  • une valeur entière positive si l’objet courant est supérieur à l’objet fourni
  • une valeur nulle si l’objet courant est égal à l’objet fourni

Les classes wrappers, String et Date implémentent cette interface.

1.6.2. L’interface Comparator

Cette interface représente un ordre de tri quelconque. Elle est utile pour permettre le tri d’objet qui n’implémente pas l’interface Comparable ou pour définir un ordre de tri différent de celui défini avec Comparable ( l’interface Comparable représente un ordre naturel : il ne peut y en avoir qu’un)

Cette interface ne définit qu’une seule méthode : int compare(Object, Object).

Cette méthode compare les deux objets fournis en paramètre et renvoie :

  • une valeur entière négative si le premier objet est inférieur au second
  • une valeur entière positive si le premier objet est supérieur au second
  • une valeur nulle si les deux objets sont égaux

1.7. Les algorithmes

La classe Collections propose plusieurs méthodes statiques qui effectuer des opérations sur des collections. Ces traitements sont polymorphiques car ils demandent en paramètre un objet qui implémente une interface et retourne une collection.

Méthode Rôle

void copy(List, List)

copie tous les éléments de la seconde liste dans la première

 
   

Enumaration

renvoie un objet Enumeration pour parcourir la collection

 

enumeration(Collection)

 
  
   

Object max(Collection)

renvoie le plus grand élément de la collection selon l’ordre naturel des éléments

 
   

Object max(Collection,

renvoie le plus grand élément de la collection selon l’ordre naturel précisé par

 

Comparator)

l’objet Comparator

 
   

Object min(Collection)

renvoie le plus petit élément de la collection selon l’ordre naturel des éléments

 
   

Object min(Collection,

renvoie le plus petit élément de la collection selon l’ordre précisé par l’objet

 

Comparator)

Comparator

 
   

void reverse(List)

inverse l’ordre de la liste fournie en paramètre

 
   

void shuffle(List)

réordonne tous les éléments de la liste de façon aléatoire

 
   

void sort(List)

trie la liste dans un ordre ascendant selon l’ordre naturel des éléments

 
   

void sort(List, Comparator)

trie la liste dans un ordre ascendant selon l’ordre précisé par l’objet Comparator

 
   

Si la méthode sort(List) est utilisée, il faut obligatoirement que les éléments inclus dans la liste implémentent tous l’interface Comparable sinon une exception de type ClassCastException est levée.

Cette classe propose aussi plusieurs méthodes pour obtenir une version multi−thread ou non modifiable des principales interfaces des collections : Collection, List, Map, Set, SortedMap, SortedSet

  • XXX synchronizedXXX(XXX) pour obtenir une version multi−thread des objets implémentant l’interface XXX
  • XXX unmodifiableXXX(XXX) pour obtenir une version non modifiable des objets implémentant l’interface

XXX

L’utilisation d’une méthode synchronizedXXX() renvoie une instance de l’objet qui supporte la synchronisation pour les opérations d’ajout et de suppression d’éléments. Pour le parcours de la collection avec un objet Iterator, il est nécessaire de synchroniser le bloc de code utilisé pour le parcours. Il est important d’inclure aussi dans ce bloc l’appel à la méthode pour obtenir l’objet de type Iterator utilisé pour le parcours.

1.8. Les exceptions du framework

L’exception de type UnsupportedOperationException est levée lorsque qu’une opération optionnelle n’est pas supportée par l’objet qui gère la collection.

L’exception ConcurrentModificationException est levée lors du parcours d’une collection avec un objet Iterator et que cette collection subi une modification structurelle.

2. Les flux

Un programme a souvent besoin d’échanger des informations pour recevoir des données d’une source ou pour envoyer des données vers un destinataire.

La source et la destination de ces échanges peuvent être de nature multiple : un fichier, une socket réseau, un autre programme, etc …

De la même façon, la nature des données échangées peut être diverse : du texte, des images, du son, etc …

2.1. Présentation des flux

Les flux (stream en anglais) permettent d’encapsuler ces processus d’envoie et de réception de données. Les flux traitent toujours les données de façon séquentielle.

En java, les flux peuvent être divisés en plusieurs catégories :

  • les flux d’entrée (input stream) et les flux de sortie (output stream)
  • les flux de traitement de caractères et les flux de traitement d’octets

Java définit des flux pour lire ou écrire des données mais aussi des classes qui permettent de faire des traitements sur les données du flux. Ces classes doivent être associées à un flux de lecture ou d’écriture et sont considérées comme des filtres. Par exemple, il existe des filtres qui permettent de mettre les données traitées dans un tampon (buffer) pour les traiter par lots.

Toutes ces classes sont regroupées dans le package java.io.

2.2. Les classes de gestion des flux

Ce qui déroute dans l’utilisation de ces classes, c’est leur nombre et la difficulté de choisir celle qui convient le mieux en fonction des besoins. Pour faciliter ce choix, il faut comprendre la dénomination des classes : cela permet de sélectionner la ou les classes adaptées aux traitements à réaliser.

Le nom des classes se décompose en un préfixe et un suffixe. Il y a quatre suffixes possibles en fonction du type de flux (flux d’octets ou de caractères) et du sens du flux (entrée ou sortie).

 

Flux d’octets

Flux de caractères

   

Flux d’entrée

InputStream

Reader

   

Flux de sortie

OutputStream

Writer

   

Il exite donc quatre hiérarchies de classes qui encapsulent des types de flux particuliers. Ces classes peuvent être séparées en deux séries de deux catégories différentes : les classes de lecture et d’écriture et les classes permettant la lecture de

caractères ou d’octets.

  • les sous classes de Reader sont des types de flux en lecture sur des ensembles de caractères
  • les sous classes de Writer sont des types de flux en écriture sur des ensembles de caractères
  • les sous classes de InputStream sont des types de flux en lecture sur des ensembles d’octets
  • les sous classes de OutputStream sont des types de flux en écriture sur des ensembles d’octets

Pour le préfixe, il faut distinguer les flux et les filtres. Pour les flux, le préfixe contient la source ou la destination selon le sens du flux.

Préfixe du flux

source ou destination du flux

  

ByteArray

tableau d’octets en mémoire

  

CharArray

tableau de caractères en mémoire

  

File

fichier

  

Object

objet

  

Pipe

pipeline entre deux threads

  

String

chaîne de caractères

  

Pour les filtres, le préfixe contient le type de traitement qu’il effectue. Les filtres n’existent pas obligatoirement pour des flux en entrée et en sortie.

Type de traitement

Préfixe de la classe

En entrée

En sortie

 
     

Mise en tampon

Buffered

Oui

Oui

 
     

Concaténation de flux

Sequence

Oui pour flux

Non

 

d’octets

 
    
     

Conversion de données

Data

Oui pour flux

Oui pour flux

 

d’octets

d’octets

 
   
     

Numérotation des lignes

LineNumber

Oui pour les flux de

Non

 

caractères

 
    
     

Lecture avec remise dans le flux des données

PushBack

Oui

Non

 
     

Impression

Print

Non

Oui

 
     

Sérialisation

Object

Oui pour flux

Oui pour flux

 

d’octets

d’octets

 
   
     

Conversion octets/caractères

InputStream /

Oui pour flux

Oui pour flux

 

OutputStream

d’octets

d’octets

 
  
     
  • Buffered : ce type de filtre permet de mettre les données du flux dans un tampon. Il peut être utilisé en entrée et en sortie
  • Sequence : ce filtre permet de fusionner plusieurs flux.
  • Data : ce type de flux permet de traiter les octets sous forme de type de données
  • LineNumber : ce filtre permet de numéroter les lignes contenues dans le flux
  • PushBack : ce filtre permet de remettre des données lues dans le flux
  • Print : ce filtre permet de réaliser des impressions formatées
  • Object : ce filtre est utilisé par la sérialisation
  • InputStream / OuputStream : ce filtre permet de convertir des octets en caractères

La package java.io définit ainsi plusieurs classes :

 

Flux en lecture

Flux en sortie

 
    
 

BufferedReader

BufferedWriter

 
 

CharArrayReader

CharArrayWriter

 
 

FileReader

FileWriter

 
 

InputStreamReader

OutputStreamWriter

 

Flux de caractères

   
 

LineNumberReader

  
 

PipedReader

PipedWriter

 
 

PushbackReader

  
 

StringReader

StringWriter

 
    
 

BufferedInputStream

BufferedOutputStream

 
   
 

ByteArrayInputStream

ByteArrayOutputStream

 
   
 

DataInputStream

DataOuputStream

 
   
 

FileInputStream

FileOutputStream

 
   

Flux d’octets

ObjectInputStream

ObjetOutputStream

 
  
 

PipedInputStream

PipedOutputStream

 
   
  

PrintStream

 
 

PushbackInputStream

  
 

SequenceInputStream

  
    

2.3. Les flux de caractères

Ils transportent des données sous forme de caractères : java les gèrent avec le format Unicode qui code les caractères sur 2 octets.

Ce type de flux a été ajouté à partir du JDK 1.1.

Les classes qui gèrent les flux de caractères héritent d’une des deux classes abstraites Reader ou Writer. Il existe de nombreuses sous classes pour traiter les flux de caractères.

2.3.1. La classe Reader

C’est une classe abstaite qui est la classe mère de toutes les classes qui gèrent des flux de caractères en lecture.

Cette classe définit plusieurs méthodes :

Méthodes

Rôles

  

boolean markSupported()

indique si le flux supporte la possibilité de marquer des positions

  

boolean ready()

indique si le flux est prêt à être lu

  

close()

ferme le flux et libère les ressources qui lui étaient associées

  

int read()

renvoie le caractère lu ou −1 si la fin du flux est atteinte.

  

int read(char[])

lire plusieurs caractères et les mettre dans un tableau de caractères

  

int read(char[], int, int)

lire plusieurs caractères. Elle attend en paramètre : un tableau de caractères

 

qui contiendra les caractères lus, l’indice du premier éléments du tableau qui

  

 

 

recevra le premier caractère et le nombre de caractères à lire. Elle renvoie le

 
 

nombre de caractères lus ou −1 si aucun caractère n’a été lu. La tableau de

 
 

caractères contient les caractères lus.

 
   

long skip(long)

saute autant de caractères dans le flux que la valeur fournie en paramètre.

 

Elle renvoie le nombre de caractères sautés.

 
  
   

mark()

permet de marquer une position dans le flux

 
   

reset()

retourne dans le flux à la dernière position marquée

 
   

2.3.2. La classe Writer

C’est une classe abstaite qui est la classe mère de toutes les classes qui gèrent des flux de caractères en écriture.

Cette classe définit plusieurs méthodes :

Méthodes

Rôles

  

close()

ferme le flux et libère les ressources qui lui étaient associées

  

write(int)

écrire le caractère en paramètre dans le flux.

  

write(char[])

écrire le tableau de caractères en paramètre dans le flux.

  
 

écrire plusieurs caractères. Elle attend en paramètres : un tableau de

write(char[], int, int)

caractères, l’indice du premier caractère dans le tableau à écrire et le

 

nombre de caractères à écrire.

  

write(String)

écrire la chaîne de caractères en paramètre dans le flux

  
 

écrire une portion d’une chaîne de caractères. Elle attend en paramètre :

write(String, int, int)

une chaîne de caractères, l’indice du premier caractère dans la chaîne à

 

écrire et le nombre de caractères à écrire.

  

2.3.3. Les flux de caractères avec un fichier

Les classes FileReader et FileWriter permettent de gérer des flux de caractères avec des fichiers.

2.3.3.1. Les flux de caractères en lecture sur un fichier

Il faut instancier un objet de la classe FileReader. Cette classe hérite de la classe InputStreamReader et possède plusieurs constructeurs qui peuvent tous lever une exception de type FileNotFoundException:

Constructeur

Rôle

  

FileInputReader(String)

Créer un flux en lecture vers le fichier dont le nom est précisé en paramètre.

  

FileInputReader(File)

Idem mais le fichier est précisé avec un objet de type File

  

Il existe plusieurs méthodes de la classe FileReader qui permettent de lire un ou plusieurs caractères dans le flux. Toutes ces méthodes sont héritées de la classe Reader et peuvent toutes lever l’exception IOException.

Une fois les traitements sur le flux terminés, il faut libérer les ressources qui lui sont allouées en utilisant la méthode close().

2.3.3.2. Les flux de caractères en écriture sur un fichier

Il faut instancier un objet de la classe FileWriter qui hérite de la classe OuputStreamWriter. Cette classe possède plusieurs constructeurs :

Constructeur Rôle

Si le nom du fichier précisé n’existe pas alors le fichier sera créé. Si il existe

FileWriter(String)

et qu’il contient des données celles ci seront écrasées.

FileWriter(File)

Idem mais le fichier est précisé avec un objet de la classe File.

Le booléen permet de préciser si les données seront ajoutées au fichier

FileWriter(String, boolean)

(valeur true) ou écraseront les données existantes (valeur false)

Il existe plusieurs méthodes de la classe FileWriter héritées de la classe Writer qui permettent d’écrire un ou plusieurs caractères dans le flux.

Une fois les traitements sur le flux terminés, il faut libérer les ressources qui lui sont allouées en utilisant la méthode close().

2.3.4. Les flux de caractères tamponnés avec un fichier.

Pour améliorer les performances des flux sur un fichier, la mise en tampon des données lues ou écrites permet de traiter un ensemble de caractères représentant une ligne plutôt que de traiter les données caractères par caractères. Le nombre d’opérations est ainsi réduit.

Les classes BufferedReader et BufferedWriter permettent de gérer des flux de caractères tamponnés avec des fichiers.

2.3.4.1. Les flux de caractères tamponnés en lecture avec un fichier

Il faut instancier un objet de la classe BufferedReader. Cette classe possède plusieurs constructeurs qui peuvent tous lever une exception de type FileNotFoundException:

Constructeur Rôle

BufferedReader(Reader)

le paramètre fourni doit correspondre au flux à lire.

l’entier en paramètre permet de préciser la taille du buffer. Il doit être positif

BufferedReader(Reader, int)

sinon une exception de type IllegalArgumentException est levée.

Il existe plusieurs méthodes de la classe BufferedReader héritées de la classe Reader qui permettent de lire un ou plusieurs caractères dans le flux. Toutes ces méthodes peuvent lever une exception de type IOException. Elle définit une méthode supplémentaire pour la lecture :

Méthode

Rôle

lire une ligne de caractères dans le flux. Une ligne est une suite de

String readLine() caractères qui se termine par un retour chariot ‘\r’ ou un saut de ligne ‘\n’ ou les deux.

La classe BufferedReader possède plusieurs méthodes pour gérer le flux héritées de la classe Reader.

Une fois les traitements sur le flux terminés, il faut libérer les ressources qui lui sont allouées en utilisant la méthode close().

2.3.4.2. Les flux de caractères tamponnés en écriture avec un fichier

Il faut instancier un objet de la classe BufferedWriter. Cette classe possède plusieurs constructeurs :

Constructeur Rôle

le paramètre fourni doit correspondre au flux dans lequel les données sont

BufferedWriter(Writer)

écrites.

l’entier en paramètre permet de préciser la taille du buffer. Il doit être positif

BufferedWriter(Writer, int)

sinon une exception IllegalArgumentException est levée.

Il existe plusieurs méthodes de la classe BufferedWriter héritées de la classe Writer qui permettent de lire un ou plusieurs caractères dans le flux.

La classe BufferedWriter possède plusieurs méthodes pour gérer le flux :

Méthode

Rôle

  

flush()

vide le tampon en écrivant les données dans le flux.

  

newLine()

écrire un séparateur de ligne dans le flux

  

Une fois les traitements sur le flux terminés, il faut libérer les ressources qui lui sont allouées en utilisant la méthode close().

2.3.4.3. La classe PrintWriter

Cette classe permet d’écrire dans un flux des données formatées.

Cette classe possède plusieurs constructeurs :

Constructeur

Rôle

  

PrintWriter(Writer)

Le paramètre fourni précise le flux. Le tampon est automatiquement vidé.

  

PrintWriter(Writer, boolean)

Le booléen permet de préciser si le tampon doit être automatiquement vidé

  

PrintWriter(OutputStream)

Le paramètre fourni précise le flux. Le tampon est automatiquement vidé.

  

PrintWriter(OutputStream, boolean)

Le booléen permet de préciser si le tampon doit être automatiquement vidé

  

Il existe de nombreuses méthodes de la classe PrintWriter qui permettent d’écrire un ou plusieurs caractères dans le flux en les formatant. Les méthodes write() sont héritées de la classe Writer. Elle définit plusieurs méthodes pour envoyer des données formatées dans le flux :

  • print( … )

Plusieurs méthodes print acceptent des données de différents types pour les convertir en caractères et les écrire dans le flux

  • println()

Cette méthode permet de terminer la ligne courante dans le flux en y écrivant un saut de ligne.

  • println ( … )

Plusieurs méthodes println acceptent des données de différents types pour les convertir en caractères et les écrire dans le flux avec une fin de ligne.

La classe PrintWriter possède plusieurs méthodes pour gérer le flux :

Méthode

Rôle

  

flush()

Vide le tampon en écrivant les données dans le flux.

  

2.4. Les flux d’octets

Ils transportent des données sous forme d’octets. Les flux de ce type sont capables de traiter toutes les données.

Les classes qui gèrent les flux d’octets héritent d’une des deux classes abstraites InputStream ou OutputStream. Il existe de nombreuses sous classes pour traiter les flux d’octets.

2.4.1. Les flux d’octets avec un fichier.

Les classes FileInputStream et FileOutputStream permettent de gérer des flux d’octets avec des fichiers.

2.4.1.1. Les flux d’octets en lecture sur un fichier

Il faut instancier un objet de la classe FileInputStream. Cette classe possède plusieurs constructeurs qui peuvent tous lever l’exception FileNotFoundException:

Constructeur

Rôle

  

FileInputStream(String)

Ouvre un flux en lecture sur le fichier dont le nom est donné en paramètre

  

FileInputStream(File)

Idem mais le fichier est précisé avec un objet de type File

  
  
  

Il existe plusieurs méthodes de la classe FileInputStream qui permettent de lire un ou plusieurs octets dans le flux. Toutes ces méthodes peuvent lever l’exception IOException.

  • int read()

Cette méthode envoie la valeur de l’octet lu ou −1 si la fin du flux est atteinte.

  • int read(byte[], int, int)

Cette méthode lit plusieurs octets. Elle attend en paramètre : un tableau d’octets qui contiendra les octets lus, l’indice du premier éléments du tableau qui recevra le premier octet et le nombre d’octets à lire.

Elle renvoie le nombre d’octets lus ou −1 si aucun octet n’a été lus. La tableau d’octets contient les octets lus.

La classe FileInputStream possède plusieurs méthodes pour gérer le flux :

Méthode

Rôle

 
   

long skip(long)

saute autant d’octets dans le flux que la valeur fournie en paramètre. Elle

 

renvoie le nombre d’octets sautés.

 
  
   

close()

ferme le flux et libère les ressources qui lui étaient associées

 
   

int available()

retourne le nombre d’octets qu’il est encore possible de lire dans le flux

 
   

Une fois les traitements sur le flux terminés, il faut libérer les ressources qui lui sont allouées en utilisant la méthode close().

…………………………………………………………………………………………………………………………………………………………………Telecharger le document pour lire le cours complet

Telecharger PDF