
FAQ JavaConsultez toutes les FAQ
Nombre d'auteurs : 53, nombre de questions : 231, dernière mise à jour : 7 juin 2009
Sommaire→Généralités→Structures de données -Tableaux et collections- Comment bien utiliser les collections ?
- Quel différence entre HashSet, TreeSet et LinkedHashSet ?
- Quelles différences entre ArrayList, LinkedList et Vector ?
- Quels sont les différents types de Map ?
- Comment créer une pile (LIFO) ?
- Comment augmenter la taille d'un tableau ?
- Comment fonctionne un ArrayList ?
- Comment trier une List (ArrayList, Vector, ...) ou un tableau?
- Comment déterminer si un objet est un tableau ?
- Comment déterminer le nombre de dimensions d'un tableau ?
- Comment instancier un tableau?
- Comment agrandir un tableau ?
- Comment parcourir tous les éléments d'un tableau ?
- Comment caster un type non-paramétré vers son équivalent paramétré ?
- Pourquoi ne peut-on pas instancier de tableau paramétré ?
- Comment trier un Map selon les valeurs et non pas les clés ?
- Comment parcourir une Map ?
- Pourquoi ne faut-il pas employer la classe Vector ?
Les collections font partie de Java (J2SE) depuis la version 1.2. Les collections proposent une série de classes, d'interfaces et d'implémentations pour gérer efficacement les données.
Pour chaque type de structure de données (liste, ensemble, association) existe une interface et plusieurs implémentations. Chaque implémentation utilise une stratégie avec des avantages et des inconvénients. Il est important de bien comprendre les différentes stratégies pour choisir l'implémentation la plus performante en fonction de ses besoins.
// La bonne solution :
List<Object> list = new ArrayList<Object>();
Set<Object> set = new HashSet<Object>();
Map<Object> map = new TreeMap<Object>();
// La mauvaise solution :
ArrayList<Object> list = new ArrayList<Object>();
HashSet<Object> set = new HashSet<Object>();
TreeMap<Object> map = new TreeMap<Object>();
Il peut néanmoins arriver qu'on soit contraint d'utiliser la 2ème solution pour avoir accès aux méthodes spécifiques à l'implémentation. En effet les classes concrètes sont souvent plus riches que les interfaces, ce qui peut se révéler nécessaire pour tirer le meilleur parti possible d'une implémentation.
Les java.util.List (listes) sont une suite d'éléments ordonnés accessibles par leur indice (leur place dans la liste). Les listes ne garantissent pas l'unicité des éléments.
Les java.util.Map (associations) mémorisent une collection de couples clé-valeur. Si vous avez une clé, l'association retrouvera la valeur associée à cette clé. Les clés sont uniques, mais la même valeur peut-être associée à plusieurs clés.
Lien : Quel différence entre HashSet, TreeSet et LinkedHashSet ?
Lien : Quelles différences entre ArrayList, LinkedList et Vector ?
Lien : Quels sont les différents types de Map ?
L'API propose trois implémentations concrètes pour les ensembles (java.util.Set). Un ensemble est un groupe d'élément uniques.
Note : L'ordre d'itération des éléments est aléatoire.
Complexité : Les opérations add, remove, contains and size sont exécutées en un temps constant.
Complexité : Les opérations add, remove, contains and size sont exécutées en un temps log(n).
Note : L'ordre d'itération des éléments correspond à l'ordre d'insertion, l'ordre reste inchangé si l'on ajoute un élément déjà présent.
Complexité : Les opérations add, remove, contains and size sont exécutées en un temps constant (mais supérieur au temps du HashSet car il faut également gérer la liste chainée).
L'API propose deux implémentations concrètes pour les listes (java.util.List). Une liste est une suite ordonnée d'éléments (les éléments peuvent être ajoutés à plusieurs endroits de la liste).
Complexité : Les opérations size, isEmpty, get, set, iterator sont exécutées en temps constant.
Les opérations d'ajout/suppression sont exécutées en temps constant amorti (les ajouts/suppressions en fin de liste sont plus rapides).
Complexité : Les opérations size, isEmpty, add, remove, set, get sont exécutées en temps constant. Toutes les méthodes qui font référence à un indice sont exécutées en temps O(n).
Note : Cette classe est "thread-safe", c'est-à-dire que plusieurs processus peuvent l'utiliser en même temps sans risque.
Complexité : idem que pour ArrayList, plus le temps de synchronisation des méthodes.
L'API propose cinq implémentations concrètes pour les 'Map' (java.util.Map). Une map permet de créer un ensemble de couples clé/valeur (On parle aussi de tableaux associatifs), la clé permettant de retrouver rapidement la valeur qui lui a été associée. Les clés sont des objets uniques pouvant être NULL; Les valeurs peuvent être multiples et NULL.
Map<Object> map = new HashMap<Object>();
// Toutes les operations pour ajouter les éléments...
map.put(....);
// ... puis on construit la TreeMap.
map = new TreeMap<Object>(map);
Lien : Comment bien utiliser les collections ?
Téléchargement : Stack.java
Téléchargement : LinkedStack.java
La taille d'un tableau est fixée lors de sa construction et ne peut plus être modifiée. La seule solution consiste à créer un nouveau tableau plus grand et à copier les anciennes valeurs dans le nouveau tableau. Pour effectuer la copie de tableau, on utilise la méthode arraycopy(java.lang.Object, int, java.lang.Object, int, int) de la classe java.lang.System bien plus performante qu'une itération.
/** la méthode simple */
int[] nouveau = new int[longueur];
System.arraycopy(vieuxTableau,0,nouveau,0,vieuxTableau.length);
/** Note : On place ici les anciennes valeurs au début du nouveau tableau. */
Lien : Quelles différences entre ArrayList, LinkedList et Vector ?
Bien utiliser une structure de données implique d'en comprendre le fonctionnement. Les classes java.util.Vector et java.util.ArrayList fonctionnent de la même manière (Quel différence ?).
Lien : Comment bien utiliser les collections ?
Lien : Quelles différences entre ArrayList, LinkedList et Vector ?
La classe java.util.Collections apporte la solution :
List<Object> ma_liste = new ArrayList<Object>();
Collections.sort(ma_liste);
ma_liste est ainsi triée par ordre croissant. Pour trier dans l'ordre décroissant, il suffit de faire ceci :
Collections.sort(ma_liste, Collections.reverseOrder());
Attention cependant, cet exemple fonctionne que si les éléments dans la liste sont de type Comparable, comme int, String, Date, ... (voir l'interface java.lang.Comparable). Si ce n'est pas le cas (les éléments sont des objets que vous avez défini), il faut que votre classe implémente l'interface java.lang.Comparable, donc en fait il faut définir la méthode int compareTo(Object o). Prenons un exemple simple :
class Voiture {
String marque = "";
int nbCheveau = 0;
public Voiture(String s, int i) {
marque = s;
nbCheveau = i;
}
public int getNbCheveau() { return nbCheveau; }
public String toString() { return marque + "\t" + nbCheveau; }
}
On veut pouvoir trier une liste de Voiture suivant leur nombre de chevaux. Voilà alors les modifications à apporter à la classe :
class Voiture implements java.lang.Comparable {
// méthode à implémenter
/**
* @param other other doit être de type Voiture !
*/
public int compareTo(Object other) {
int nombre1 = ((Voiture) other).getNbCheveau();
int nombre2 = this.getNbCheveau();
if (nombre1 > nombre2) return -1;
else if(nombre1 == nombre2) return 0;
else return 1;
}
}
Maintenant, vous pouvez trier facilement une liste de voitures :
Collections.sort(liste_voitures);
Le code est identique pour un tableau. Il faut seulement utiliser la classe Arrays.
Arrays.sort(unTableauDeVoitures);
Vous avez aussi la possibilité de facilement mélanger une liste grâce à la méthode shuffle de Collections.
Lien : Comment bien utiliser les collections ?
Lien : Quelles différences entre ArrayList, LinkedList et Vector ?
Tout simplement en faisant :
boolean estUnTableau = monObjet.getClass().isArray();
Cette propriété n'est pas directement accessible, mais une petite fonction permet de la calculer rapidement. On se base sur le fait qu'un tableau à plusieurs dimensions est en fait un tableau de tableaux.
public static int getNbDimension( Object monTableau ) {
int dim=0;
Class cls = monTableau.getClass();
while( cls.isArray() ) {
cls = cls.getComponentType();
dim++;
}
return( dim );
}
Pour instancier un tableau, il faut :
- Réserver la place en mémoire pour les N références vers les objets du tableau,
- Réserver la place pour chaque objet, car la création d'un tableau d'objets, va créer un tableau rempli de null
Par exemple :
Point[] p = new Point[10]; // réserver la place en mémoire pour 10 références vers des objets Point
for(int i=0 ; i<p.length ; i++) {
p[i] = new Point(i, i+10); // réserve en mémoire la place pour un objet point (10 fois)
}
Si l'on essaye d'instancier des tableaux d'objets sans réserver la place de chaque objets en mémoire, on reçoit une exception «NullPointerException » lors de l'accès à l'objet. Comme par exemple en créant un tableau de Point comme on créerais un tableau d'int.
Alors qu'avec un tableau de types primitifs, chaque "case" du tableau est directement rempli avec la valeur par défaut de celui-ci, car un type primitif ne peut pas être null.
En Java, les tableaux ne sont pas extensibles. On doit donc procéder en 2 étapes, créer un nouveau tableau plus grand, puis copier toutes les valeurs de l'ancien tableau dans le nouveau.
Object nouveauTableau = Array.newInstance( ancienTableau.getClass().getComponentType(),
nouvelleTaille );
System.arraycopy( ancienTableau, 0, nouveauTableau, Array.getLength(ancienTableau);
Boucle "for"
A l'ancienne, avec une boucle for traditionnelle
int tab[50];
...
for (int index = 0; index < tab.length; index++)
System.out.println(tab[index]);
Boucle "for each"
Le JDK 5.0 apporte une construction de boucle performante qui permet de parcourir tous les éléments d'un tableau, ou d'un objet implémentant l'interface Iterable (ArrayList, ...), sans se préoccuper de l'indice.
int tab[50];
...
for (int n : tab)
System.out.println(n);
Les Generics introduits dans Java 5.0 permettent une compatibilité avec les anciennes librairies qui utiliserait leurs équivalent non paramétrés. Ainsi s'il est possible d'affecter une référence non-paramétré dans son homologue paramétré, par exemple :
Map<String,String[]> parameters = request.getParameterMap(); // retourne une Map
Malheureusement, ce code génère un warning car il n'est pas sécurisé :
warning: [unchecked] unchecked conversion
En effet il est impossible pour le compilateur de vérifier que l'objet non-paramétré respectent les conditions de la déclaration en Generics. Ainsi dans notre exemple la Map retourné par getParameterMap() pourrait très bien contenir des objets de type différents sans que le compilateur ne puisse le détecter, ce qui provoquerait des exceptions inattendus lors de l'utilisation de la variable parameters.
Toutefois, ce type d'affectation peut s'avérer utile afin d'utiliser conjointement des API Generics avec d'autres qui ne le sont pas, surtout que dans cet exemple la documentation de la méthode précise bien que la Map renvoyé respecte bien la déclaration Map<String,String[]>.
On peut ainsi utiliser l'annotation @SuppressWarning pour indiquer au compilateur que l'on prend en charge la sécurité de ce code :
@SuppressWarnings("unchecked")
Map<String,String[]> parameters = request.getParameterMap();
L'annotation @SuppressWarning ne pouvant pas s'appliquer à une seule instruction en dehors des déclarations de variable, on peut utiliser la méthode suivante afin de limiter son scope d'utilisation :
/**
* Cast non sécurisé.<br/>
* Cette méthode permet de caster des types non-paramétré en
* type paramétré.<br/>
* Exemple :
* {@code List<String> list = uncheckedCast( maListeNonParamétré ); }
* <br/>
* <b>Attention :</b> cela peut avoir des effets de bords
* indésirables si la référence en paramètre ne respectent
* pas le type paramétré !
*
* @param <P> Type du paramètre de la méthode.
* @param <R> Type de la valeur de retour.
* @param p Paramètre à "caster".
* @return La référence 'p', castée dans le type de R.
* @throws ClassCastException en cas de type incompatible.
*/
@SuppressWarnings("unchecked")
public static <P,R extends P> R uncheckedCast(P p) throws ClassCastException {
return (R) p;
}
Map<String,String[]> parameters = null;
parameters = uncheckedCast(request.getParameterMap());
Cette méthode est très proche d'un cast standard et permet les mêmes vérifications.
Quelques exemples :
Object reference = new ArrayList();
Map<String,String> map = uncheckedCast(reference); // ClassCastException
Ce code compilera sans erreur (car une référence de type Object pourrais contenir une Map), mais génèrera une exception à l'exécution (puisqu'en réalité il contient un type incompatible avec Map).
ArrayList reference = new ArrayList();
Map<String,String> map = uncheckedCast(reference); // Erreur
A l'inverse, ce code provoquera une erreur car le compilateur détectera que la type de la référence est incompatible avec le type attendu.
Attention : La sécurité du code et de la correspondance entre le type standard et la déclaration utilisant les Generics est entièrement à la charge du développeur. Une mauvaise utilisation pourrait provoquer des erreurs d'exécutions plus loin dans le code.
Lien : [Java 5.0] Comment supprimer un warning en particulier ?
Le compilateur interdit toute création de tableau de type paramétré, ainsi la ligne suivante provoquera irrémédiablement une erreur :
List<String> stringListArray[] = new List<String>[100];
Ce problème vient du fait que les tableaux et les Generics ont une approche totalement opposée de la vérifications des types.
Avec les tableaux, la quasi-totalité des vérifications est effectué pendant l'exécution, et la plupart des opérations sur les tableaux peuvent générer des ClassCastException selon le type réel du tableau et le type réel de l'élément qu'on lui affecte.
A l'inverse, les Generics sont sécurisé à la compilation. C'est à dire que c'est le compilateur qui se charge de vérifier la cohérence de l'ensemble pendant la compilation, et qui garantit l'absence d'exception pendant l'exécution.
Du fait de leurs fortes oppositions et de l'héritage particulier des tableaux, l'utilisation de tableaux Generics peut aboutir à des situations complètement fausses et générer des exceptions inattendus.
Ainsi, si la création de tableaux Generics étaient possible, il serait relativement simple de passer outre la protection des Generics sans warnings ni erreurs :
List<String>[] stringListArray = new List<String>[100]; // ERREUR
Object[] simpleArray = stringListArray;
simpleArray[0] = new ArrayList<Number>(); // OK ?!?!?
Le pire c'est que ce code ne génèrerait même pas d'exception lors de son exécution. Au contraire l'exécution serait reporté lors de l'utilisation des données du tableaux alors qu'il devrait s'agir d'un code sécurisé...
Bref, l'utilisation de tableaux Generics est loin d'assurer la sécurité de code promise par les Generics, et le compilateur interdit donc leurs créations.
Il existe pourtant deux alternatives.
La meilleur solution serait d'utiliser l'API de Collection, et donc une List à la place du tableau. En effet dans ce cas le cas de figure décris ci-dessus, l'utilisation des Generics permettrait d'être avertit du problème via un warning :
List<List<String>> stringListList = new ArrayList<List<String>>();
List simpleList = stringListList;
simpleList.add( new ArrayList<Number>() ); // Warning unchecked
La seconde solution, à n'utiliser que si l'utilisation du tableau est vraiment une nécessité, consiste à créer un tableau non-Generics et à ignorer le warning reçu :
@SuppressWarnings("unchecked")
List<String>[] stringListArray = new List[100];
Mais bien entendu cela ne corrige en rien le problème des tableaux Generics...
Lien : Comment caster un type non-paramétré vers son équivalent paramétré ?
Dans un Map, il n'y pas de notion d'ordre vu que l'on n'accède généralement pas à ses éléments par indice comme dans une Liste mais plutôt par clés.
Trier un Map suivant les valeurs revient en fait à :
- Soit copier la liste des valeurs du Map dans une Liste et trier cette dernière : consultez "Comment trier une List (ArrayList, Vector, ...) ou un tableau?" dans la FAQ. L'inconvénient est que l'on ne travaille plus sur le Map et donc on n'a plus un accès direct aux éléments par clés.
- Ou encore récupérer l'ensemble des clés dans une Liste, la trier suivant les valeurs et accéder aux éléments du Map en parcourant séquentiellement les éléments de cette Liste. L'avantage est qu'on accède toujours aux éléments directement par clé dans le Map. C'est à cette méthode que l'on s'intéressera via un exemple.
Exemple:
Considérons la classe suivante :
public Class Personne {
private Long id;//Identifiant d'une personne
private int age;
private String nom;
//implémenter les getters et les setters
}
et supposons que l'on stocke un ensemble de personnes dans un Map avec l'identifiant comme clé.
Map<Long, Personne> personnes;
Pour trier ce Map en fonction de l'age des personnes par exemple, on procède comme suit :
1. Créer un Comparator qui compare deux personnes suivant l'age. Le problème est que l'on va trier l'ensemble des clés, donc le Comparator reçoit deux identifiants (Long). Il suffit alors de passer le Map au Comparator dans le constructeur pour que l'on puisse retrouver une personne via son identifiant.
public class PersonneComparator implements Comparator<Long>{
private Map<Long, Personne> personnes;//pour garder une copie du Map que l'on souhaite traiter
public PersonneComparator(Map<Long, Personne> personnes){
this.personnes = personnes; //stocker la copie pour qu'elle soit accessible dans compare()
}
public int compare(Long id1, Long id2){
//récupérer les personnes du Map par leur identifiant
Personne p1 = personnes.get(id1);
Personne p2 = personnes.get(id2);
//comparer les deux clés en fonction de l'age des personnes qu'ils indexent.
return p1.getAge() - p2.getAge();
}
}
2. Trier les clés du Map en utilisant ce Comparator.
List<Long> cles = new ArrayList<Long>(personnes.keySet());
Collections.sort(cles, new PersonneComparator(personnes));
3. Accéder aux éléments du Map en utilisant la liste triée.
for(Long id : cles){
Personne p = personnes.get(id);
...
}
On peut parcourir une collection de type Map au moyen d'Iterator. Pour cela, il suffit de récupérer soit les clefs avec la méthode keySet() soit les valeurs avec la méthode values() et d'appeller ensuite la méthode iterator() sur la collection :
Map<TypeClefs,TypeValeurs> map = new ClasseImplementantMap<TypeClefs,TypeValeurs>
for (Iterator<TypeClefs> i = map.keySet().iterator() ; i.hasNext() ; ){
System.out.println(i.next());
}
Map<TypeClefs,TypeValeurs> map = new ClasseImplementantMap<TypeClefs,TypeValeurs>
for (Iterator<TypeValeurs> i = map.values().iterator() ; i.hasNext() ;){
System.out.println(i.next());
}
Pour les personnes n'ayant pas Java 5.0, il vous suffira de supprimmer les generics.
On peut aussi récupérer les clefs et les valeurs avec une seule boucler. Il suffit pour cela de boucler sur les clefs et de récupérer ensuite la valeur à partir de la Map :
Map map = new ClasseImplementantMap();
for (Iterator i = map.keySet().iterator() ; i.hasNext() ; ){
TypeClefs key = i.next();
System.out.println("key = " + key + " value = " + map.get(key));
}
Depuis Java 5.0, on peut aussi utiliser la boucle for étendu pour parcourir une Map. Il suffit d'utiliser la méthode entrySet() qui renvoie une collection de Map.Entry qui représentent un couple clé-valeur. Voici un exemple :
//Pour récupérer les clefs et les valeurs
for (Map.Entry<TypeClefs, TypeValeurs> e : map.entrySet()){
System.out.println(e.getKey() + " : " + e.getValue());
}
Il est déconseillé d'utiliser la classe Vector. En effet, celle-ci fait partie des vieilles du framework de collections de Java. Ces méthodes ne respectent donc pas les standards de nommage des collections. De plus, cette classe est synchronisée, c'est à dire qu'elle peut s'utiliser dans un environement multi-threadé mais cela résulte en une perte de performances.
Il vaut donc mieux utiliser la classe ArrayList qui s'utilise presque de la même manière que Vector, mais qui n'est pas synchronisé et fait partie des nouvelles classes du framework.
Si vous avez tout de même besoin d'une collection synchronisée du type de Vector, il vous suffit d'utiliser la méthode synchronizedList de la classe Collections :
List<Object> list = Collections.synchronizedList(new ArrayList<Object>(...));
Vous aurez ainsi une ArrayList tout à fait normale mais synchronisée.


















