9. Classes utilitaires Collections et Arrays

9.1. Introduction

Ces deux classes font partie de l'API Java Collection depuis sa création. Elles comportent chacune une cinquantaine de méthodes utilitaires, toutes statiques. Ces méthodes résolvent à peu près tous les problèmes que l'on peut rencontrer lorsque l'on manipule des tableaux ou des ensembles. Bien connaître le contenu de ces classes est indispensable à tout développeur Java.

9.2. Classe Arrays

Cette classe expose des méthodes utilitaires qui traitent des tableaux. On peut trier ces méthodes dans les catégories suivantes. D'une façon générale, il existe une version de chacune de ces méthodes pour tous les types primitifs Java, ainsi que pour la classe Object. La première méthode exposée n'entre dans aucune catégorie. Elle permet de convertir un tableau en List : asList(Object[] tab). Attention toutefois, la liste retournée ne supporte pas l'ajout d'objet !

Exemple 15. Utilisation de Arrays.asList(...)

// jusqu'ici tout va bien...
List<String> list = Arrays.asList("Un",  "Deux",  "Trois") ;

 // génération d'une UnsupportedOperationException !!!
list.add("Quatre") ;

  • Recherche d'un élément dans un tableau : méthodes binarySearch(int[] tab, int key). Il existe une version supplémentaire qui prend en paramètre deux index entre lesquels la recherche doit se limiter. Ces méthodes retournent l'index de l'élément recherché.
  • Copie d'un tableau dans un autre : méthodes copyOf(int[] original, int newLength). Ces méthodes retournent un nouveau tableau, dans lequel les cases vides sont initialisées à null, ou à la valeur par défaut de chaque type primitif.
  • Copie d'une partie de tableau dans un nouveau tableau : méthodes copyOfRange(int[] original, int from, int to). Le nouveau tableau de la bonne taille est retourné.
  • Comparaison de deux tableaux, élément par élément : equals(int[] tab1, int[] tab2). Retourne true si les éléments de ces tableaux sont égaux deux à deux.
  • Initialisation d'un tableau : fill(int[] tab, int val) et fill(int[] tab, int fromIndex, int toIndex, int val). Ces méthodes ne retournent rien, c'est le tableau passé en paramètre qui est modifié.
  • Calcul du code de hachage d'un tableau : hashCode(int[] tab) : retourne le code de hachage du tableau passé en paramètre.
  • Tri d'un tableau : sort(int[] tab) et sort(int[] tab, int fromIndex, int toIndex) : tri le tableau passé en paramètre en fonction du type du tableau. Si le tableau est un tableau d'objets, alors les objets doivent être Comparable.
  • Méthode toString() pour les tableaux : toString(int[] tab).
Voyons un exemple d'utilisation de toString().

Exemple 16. Utilisation de Arrays.toString()

// création d'un tableau
String [] tab = {"Un",  "Deux",  "Trois"} ;

 // utilisation de la méthode
System.out.println(Arrays.toString(tab)) ;

L'exécution de ce code donne le résultat suivant.
[Un, Deux, Trois]
Les méthodes equals(), hashCode() et toString() sont construites sur les mêmes méthodes de la classe Object, mais ne traitent pas le fait que certains des éléments passés en paramètre peuvent être eux-mêmes des tableaux. La classe Arrays prend ce point en compte, et expose trois méthodes supplémentaires : deepToString(), deepHashCode() et deepEquals(). Voyons l'utilisation de la méthode deepToString() sur un exemple.

Exemple 17. Utilisation de Arrays.deepToString()

Object [] tabObject = {"Un",  1, tab} ;

 // utilisation de Arrays.toString() classique
System.out.println(Arrays.toString(tabObject)) ;

 // utilisation de Arrays.deepToString()
System.out.println(Arrays.deepToString(tabObject)) ;

L'exécution de ce code donne le résultat suivant. Sur la première ligne, on voit que Arrays.toString() appelle la méthode toString() de chacun des éléments du tableau. Sur la deuxième ligne, on constate que deepToString() a bien vu que le troisième élément était un tableau, et a donc exploré ses éléments un par un.
[Un, 1, [Ljava.lang.String;@addbf1]
[Un, 1, [Un, Deux, Trois]]

9.3. Classe Collections

Cette classe compte une cinquantaine de méthodes de toutes sortes, qu'il n'est pas vraiment possible de regrouper en catégories simples comme pour Arrays. Mettons l'accent sur certaines méthodes, qui permettent de construire des listes ou des tables particulières (vides, ne comportant qu'un unique élément), synchronisés ou immutables . Certaines méthodes permettent de mélanger les éléments d'une collection, soit de façon prévisible ( rotate), soit de façon aléatoire ( shuffle). On trouve encore des méthodes permettant de faire du tri, de rechercher le nombre de fois qu'un élément particulier apparaît dans une collection, ou encore de rechercher le plus petit ou le plus grand élément d'une collection.
Java API avancées
Retour au blog Java le soir
Cours & Tutoriaux
Table des matières
API Collection
1. Introduction
2. Interface Collection
2.1. Notion de Collection
2.2. Détail des méthodes disponibles
2.3. Interface Iterator
2.4. Implémentation, exemples d'utilisation
3. Interface List
3.1. Notion de List
3.2. Détail des méthodes disponibles
3.3. Interface ListIterator
3.4. Implémentations, exemples d'utilisation
4. Interface Set
4.1. Notion de Set
4.2. Implémentations HashSet et LinkedHashSet
4.3. Exemples d'utilisation
5. Interface SortedSet
5.1. Notion de SortedSet
5.2. Détails des méthodes disponibles
5.3. Exemples d'utilisation
6. Interface NavigableSet
6.1. Notion de NavigableSet
6.2. Détails des méthodes disponibles
6.3. Exemple d'utilisation
7. Interfaces Queue et Deque
7.1. Notion de file d'attente
7.2. Détail des méthodes disponibles
7.3. Utilisation des interfaces Queue et Deque
8. Tables de hachage
8.1. Notion de table de hachage
8.2. Interface Map
8.3. Interface Map.Entry
8.4. Interface SortedMap
8.5. Interface NavigableMap
8.6. Implémentations
8.7. Exemples d'utilisation
9. Classes utilitaires Collections et Arrays
9.1. Introduction
9.2. Classe Arrays
9.3. Classe Collections
Génériques
1. Introduction
2. Un premier exemple
2.1. Une première classe générique
2.2. Une première méthode générique
3. Contraindre un type générique
3.1. Problème posé
3.2. Contraindre un type générique
4. Implémentation des génériques
4.1. Type erasure
4.2. Types génériques et casts
4.3. Type générique et exception
4.4. Construction d'une instance générique
4.5. Génériques et membres statiques
4.6. Collisions de méthodes génériques
4.7. Implémentation de plusieurs types identiques
5. Type <?>
5.1. Introduction
5.2. Type ? extension d'un type
5.3. Type ? super-type d'un type
Expressions régulières
1. Introduction
2. Mise en œuvre des expressions régulières
2.1. Fonctionnement d'une regexp
2.2. Fonctionnement de l'API en Java
2.3. Un premier exemple
2.4. Classe Pattern
2.5. Classe Matcher
2.6. Utilisation des méthode find() et group()
2.7. Méthodes de remplacement
2.8. Sélection de régions
3. Syntaxe des expressions régulières
3.1. Notion de classe
3.2. Étude d'un cas réel
3.3. Recherche d'un mot précis
3.4. Recherche de deux mots précis
3.5. Recherche d'un mot commençant par une lettre donnée
3.6. Cas de mots comportant des caractères accentués
3.7. Recherche sur les lignes
Introspection
1. Introduction
2. La classe Class
2.1. Utilisation de Class
2.2. Méthodes disponibles
2.3. Remarque sur la propriété Accessible
2.4. Type d'une classe
2.5. Création d'une instance à partir d'un objet Class
2.6. Cas des énumérations
3. Les classes Method et Constructor
3.1. Utilisation de Method
3.2. Utilisation de Constructor
3.3. Méthodes disponibles
3.4. Invocation d'une méthode par introspection
4. La classe Field
4.1. Utilisation de Field
4.2. Méthodes disponibles
4.3. Accès à un champ par introspection
5. La classe Modifier
Programmation concurrente
1. Introduction
2. Lançons nos premiers threads
2.1. Introduction
2.2. Un premier thread, extension de Thread
2.3. Un deuxième thread, implémentation de Runnable
2.4. Remarque sur la méthode Thread.sleep(long)
2.5. Arrêter un thread
3. Concurrence d'accès
3.1. Notion d'état
3.2. Exemple de concurrence d'accès sur un état
3.3. Analyse de la concurrence d'accès
3.4. Solution au problème
3.5. Champs volatile
4. Synchronisation
4.1. Définition d'un bloc synchronisé
4.2. Fonctionnement d'un bloc synchronisé
4.3. Notion de deadlock
4.4. Bonnes pratiques pour la synchronisation de threads
5. Opérations atomiques
5.1. Atomicité d'une opération
5.2. Solutions disponibles
5.3. Variables atomiques
6. Collections synchronisées et concurrentes
6.1. Introduction
6.2. Position du problème
6.3. Solutions proposées
7. Files d'attente
7.1. Introduction, pattern producteur / consommateur
7.2. Interface BlockingQueue<E>
7.3. Implémentations de BlockingQueue
7.4. Exemple de producteur / consommateur
7.5. Arrêter un producteur / consommateur : pilule empoisonnée
8. Classes utilitaires de l'API Concurrent
8.1. Introduction
8.2. Énumération TimeUnit
8.3. Interface Callable<V>
8.4. Interfaces Future<V> et RunnableFuture<V>
8.5. Interface ScheduledFuture<V> et RunnableScheduledFuture<V>
9. Pattern executor
9.1. Notion de réserve de threads
9.2. Interface Executor
9.3. Interface ExecutorService
9.4. Interface ScheduledExecutorService
9.5. Classe Executors
9.6. Pattern de lancement de tâches
10. Classes de contrôle d'accès
10.1. Introduction
10.2. Interfaces Lock et ReadWriteLock
10.3. Notion de verrou réentrant
10.4. Classe RentrantLock
10.5. Classe ReadWriteRentrantLock
11. Sémaphores, barrières et latches
11.1. Introduction
11.2. Notion de sémaphore, classe Semaphore
11.3. Notion de latch, classe CountDownLatch
11.4. Notion de barrière, classe CyclicBarrier