Une collection en PHP se résume à l'utilisation des arrays. On peut y placer tout type de données possible (mélanger les chaînes de caractères avec les entiers et d'autres tableaux). En Java, le champ de manœuvre est plus grand.
On peut définir une collection comme un objet qui regroupe d'autres objets dans une seule entité. Dans les premières versions du Java étaient accessibles des collections suivantes : - array: il s'agit d'un tableau qui peut être converti en une liste. Cependant, il ne peut pas être agrandi dynamiquement. Il n'accepte qu'un nombre précis d'éléments à son initialisation. L'ajout d'un nouvel élément provoque l'exception ArrayIndexOutOfBoundsException. - Hashtable : il s'agit d'une map ressemblant des paires clé - valeur. Chaque objet pas null peut jouer le rôle de la clé. - Vector : il s'agit d'un tableau qui peut être dynamiquement modifié. Il ne nécessite pas le paramètre de taille à l'initialisation. Contrairement à Hashtable, il n'admet que les clés entiers. La clé d'un élément rajouté dynamiquement doit correspondre à la taille maximale du tableau. Dans le cas contraire, à l'exécution on recevra une ArrayIndexOutOfBoundsException.
Voici un exemple résumant ces trois éléments :
import java.util.Hashtable; import java.util.Enumeration; import java.util.Vector; public class CollectionsOrigine { public static void main(String[] args) { // array case String[] arrayString = {"a", "b", "c"}; for(int i = 0; i < arrayString.length; i++) { System.out.println(i + " = " + arrayString[i]); } // Decomment this line to see ArrayIndexOutOfBoundsException // arrayString[3] = "test"; // Hashtable case HashtablehashString = new Hashtable (); hashString.put("a", "A letter"); hashString.put("b", "B letter"); hashString.put("c", "C letter"); Enumeration emHash = hashString.keys(); while(emHash.hasMoreElements()) { String key = (String)emHash.nextElement(); String value = (String)hashString.get(key); System.out.println(key + " has value " + value); } hashString.put("last", "last test"); System.out.println("Dynamically added element is " + hashString.get("last")); // Vector case Vector vectorString = new Vector (); vectorString.add("a"); vectorString.add("b"); vectorString.add("c"); vectorString.add("d"); Enumeration emVector = vectorString.elements(); while(emVector.hasMoreElements()) { System.out.println("Found Vector element " + emVector.nextElement()); } System.out.println("Initial Vector size " + vectorString.size()); // removes the third element (Vector's count starts from 0) vectorString.remove(2); emVector = vectorString.elements(); while(emVector.hasMoreElements()) { System.out.println("Found Vector element " + emVector.nextElement()); } System.out.println("Vector size after element removing " + vectorString.size()); // decomment this line to see ArrayIndexOutOfBoundsException // vectorString.add(50, "invalid element key"); vectorString.add(vectorString.size(), "valid element key"); emVector = vectorString.elements(); while(emVector.hasMoreElements()) { System.out.println("Found Vector element " + emVector.nextElement()); } } }
Les version suivantes du Java ont introduit des types de collections plus élaborés. Désormais, on distingue les interfaces de collections suivantes : - Collection : la classe principale dans la hiérarchie des collections. Elle englobe les trois interfaces suivantes (Set, List, Queue). - Set : une collection qui ne peut pas contenir des duplicatas. Il est parent pour l'interface SortedSet - SortedSet : il garde l'ordre de clés ascendant. - List : ressemble à Vector car il peut changer sa taille dynamiquement. Il accepte les duplicatas. - Queue : une collection qui permet de manipuler les éléments en ordre de priorités (selon la régle FIFO - first in, first out [les premiers éléments sont traités d'abord]). - Map : ressemble à Hashtable car il contient des paires clé-valeur. Il est parent pour l'interface SortedMap - SortedMap : une map qui garde l'ordre montant des clés.
Regardons maintenant quelles classes implémentent ces interfaces et à quoi elles peuvent servir dans le cas d'une réelle application :
import java.util.Set; import java.util.TreeSet; import java.util.SortedSet; import java.util.List; import java.util.ArrayList; import java.util.Iterator; import java.util.Queue; import java.util.PriorityQueue; import java.util.Map; import java.util.HashMap; import java.util.TreeMap; public class CollectionsInterfaces { public static void main(String[] args) { // Set implementation : diary exemple System.out.println("\nSET IMPLEMENTATION"); SetsetCase = new TreeSet (); setCase.add("2012-06-01"); setCase.add("2012-06-02"); setCase.add("2012-06-04"); // We can't add an existing element : this code will do nothing setCase.add("2012-06-02"); setCase.add("2012-06-03"); Iterator setIterator = setCase.iterator(); while (setIterator.hasNext()) { System.out.println("Found day " + setIterator.next()); } // SortedSet implementation : ascending ordering by the value; like a list of school pupils System.out.println("\nSORTEDSET IMPLEMENTATION"); SortedSet ssSet = new TreeSet (); ssSet.add("Bart"); ssSet.add("Julie"); ssSet.add("Wayne"); ssSet.add("David"); ssSet.add("Sophie"); Iterator ssIt = ssSet.iterator(); while(ssIt.hasNext()) { System.out.println("Pupil found " + ssIt.next()); } // List implementation : shopping list System.out.println("\nLIST IMPLEMENTATION"); List listCase = new ArrayList (); listCase.add("milk"); listCase.add("lemon"); listCase.add("milk"); listCase.add("milk"); listCase.add("milk"); listCase.add("water"); Iterator listIterator = listCase.iterator(); while(listIterator.hasNext()) { System.out.println("I need to buy " + listIterator.next()); } // removes the second element (milk) listCase.remove(2); // removes only the first occurence of milk listCase.remove("milk"); System.out.println("\nThis is my new shopping list"); listIterator = listCase.iterator(); while(listIterator.hasNext()) { System.out.println("I need to buy " + listIterator.next()); } // Queue implementation : hospital patient list System.out.println("\nQUEUE IMPLEMENTATION"); Queue queueCase = new PriorityQueue (); queueCase.add("1. Jon Parker"); queueCase.add("2. Mike Pok"); queueCase.add("3. Tiphany Taylor"); queueCase.add("6. Jude Forester"); queueCase.add("5. Luise Castor"); queueCase.add("4. Maria Deeb"); queueCase.add("7. Caroline Pesterson"); // if you use Iterator, you won't able to see in which order the elements are considered while(queueCase.size() > 0) { System.out.println(queueCase.remove() + " can now enter to Dr Hopkins office"); } // Map implementation : gift list relationship between donors (keys) and receivers (values) System.out.println("\nMAP IMPLEMENTATION"); Map mapCase = new HashMap (); mapCase.put("Mike", "Wendy"); mapCase.put("Jon", "Maria"); mapCase.put("Anthony", "Luise"); mapCase.put("David", "Amberly"); for (Iterator mapIterator = mapCase.keySet().iterator(); mapIterator.hasNext();) { String k = (String)mapIterator.next(); System.out.println(k + " gives a gift to " + mapCase.get(k)); } // SortedMap implementation : map's value decides do treatement position of the element // like an alphabet System.out.println("\nSORTEDMAP IMPLEMENTATION"); Map smCase = new TreeMap (); smCase.put("b", new Integer(2)); smCase.put("c", new Integer(3)); smCase.put("d", new Integer(4)); smCase.put("a", new Integer(1)); Set setKey = smCase.entrySet(); Iterator smIterator = setKey.iterator(); while(smIterator.hasNext()) { Map.Entry entry =(Map.Entry)smIterator.next(); Integer value = (Integer)entry.getValue(); System.out.println("Treatement of " + value + " element"); } } }
Différence entre une Map et un Hashtable
Les Maps ressemblent à des Hashtables. Les deux sont pourtant basés sur la rélation clé-valeur. Cependant, il y a quelques différences subtiles entre elles : - les Maps peuvent être parcourous selon : clé, valeur ou clé-valeur et les Hashtables non - Hashtable est synchronisé; il est donc conseillé d'utiliser les Maps dans les applications monoprocessus (mono-threading) - l'implementation d'une Map, HashMap admet les nulls comme valeurs; Hashtable ne le permet pas - Hashtable permet vérifier si une valeur existe (méthode contains()). Une Map rend possible la même opération sur la clé (containsKey()) ou sur une valeur (containsValue())
Différence entre un Set et une Map
Et sur quoi reposent les différences entre un Set et une Map ? Les voici : - les Maps stockent des clés uniques tandis que les Sets les valeurs uniques - les Maps stocket des paires clé-valeur tandis que les Sets contiennent uniquement des instances des objets - les Sets sont ordonnés selon les valeurs, les Maps selon les clés
Types des Maps en Java
HashMap
L'implémentation la plus standarde de l'interface Map. Il s'agit d'une collection des paires clé-valeur. Cette classe accepte les nulls comme clés et valeurs. Comme déjà mentionné, HashMap n'est pas synchronisée en natif. Elle nécessite une synchronisation supplémentaire, par exemple avec l'enveloppeur Collections.synchronizedMap(new HashMap(...)).
TreeMap
Contrairement à HashMap, les valeurs de TreeMap sont ordonnées selon les clés. Elle doit également être synchronisée en plus car elle ne l'est pas par défaut.
LinkedHashMap
Les éléments de cette map sont traités selon l'ordre d'insertion. Comme dans 2 cas précédents, elle n'est pas synchronisé en natif.
IdentityHashMap
Elle ressemble énormément à HashMap. La différence entre elles repose sur la détection si une clé est déjà présente ou pas. IdentityHashMap se base pour cette opération sur l'équation "==" tandis que d'autres maps utilisent la méthode equals(). Autrement dit, la clé sous forme d'un objet String("cle") ne sera pas la même chose que le type primitif "cle" (les deux seront correctement ajoutés dans la map car il s'agit d'une instance du String et d'un type primitif). Elle nécessite également une synchronisation supplémentaire.
WeakHashMap
Sa spécificité concerne les références. Chaque référence fait partie des clés de cette Map. Si une de ces références n'est plus utilisée par l'application (par exemple en devenant un null), le garbage collector de Java va la supprimer de la WeakHashMap pour libérer de l'espace dans la mémoire. Elle est également non synchronisée par défaut.
Elle se base sur l'utilisation de ses références. Si une d'entre elles n'est plus utilisée dans l'application, le garbage collector de Java va la supprimer de la Map et libérer de l'espace dans la mémoire.
Voici les exemples d'implémentation de ces maps :
import java.util.Map; import java.util.HashMap; import java.util.IdentityHashMap; import java.util.TreeMap; import java.util.LinkedHashMap; import java.util.WeakHashMap; import java.util.Iterator; import java.util.Set; public class Maps { private Mapidentity = new IdentityHashMap () {{ put("a", "A element, primitive type"); put("b", "B element"); put(new String("a"), "A element, instance of String"); }}; private Map hashToIdentity = new HashMap () {{ put("a", "A element, primitive type"); put("b", "B element"); put(new String("a"), "A element, instance of String"); }}; public static void main(String[] args) { Maps mapsInst = new Maps(); System.out.println("HashMap exemple"); mapsInst.normalHashMap(); System.out.println("\n"); System.out.println("TreeMap exemple"); mapsInst.treeMap(); System.out.println("\n"); System.out.println("LinkedHashMap exemple"); mapsInst.linkedMap(); System.out.println("\n"); System.out.println("IdentityHashMap and HashMap difference"); mapsInst.differenceIdentity(); System.out.println("\n"); System.out.println("WeakHashMap exemple"); mapsInst.weakHashMapGarbage(); } private void normalHashMap() { Set setKey = hashToIdentity.entrySet(); Iterator it = setKey.iterator(); while(it.hasNext()) { Map.Entry mapEntry = (Map.Entry)it.next(); String key = (String)mapEntry.getKey(); String value = (String)mapEntry.getValue(); System.out.println("Found \"" + value + "\" for key \"" + key + "\""); } } private void treeMap() { TreeMap tree = new TreeMap (); tree.put("a", "1st"); tree.put("d", "4th"); tree.put("e", "5th"); tree.put("b", "2nd"); tree.put("c", "3rd"); Set setKey = tree.entrySet(); Iterator tmIterator = setKey.iterator(); while(tmIterator.hasNext()) { Map.Entry entry =(Map.Entry)tmIterator.next(); String value = (String)entry.getValue(); System.out.println("Treatement of " + value + " element"); } } private void linkedMap() { LinkedHashMap linked = new LinkedHashMap (); linked.put("a", "1st"); linked.put("d", "4th"); linked.put("e", "5th"); linked.put("b", "2nd"); linked.put("c", "3rd"); Set setKey = linked.entrySet(); Iterator tmIterator = setKey.iterator(); while(tmIterator.hasNext()) { Map.Entry entry =(Map.Entry)tmIterator.next(); String value = (String)entry.getValue(); System.out.println("Treatement of " + value + " element"); } } private void weakHashMapGarbage() { Pupil pupilInst = new Pupil("first name exemple", "last name exemple"); System.out.println("Pupil first name is " + pupilInst.getFirstName()); System.out.println("Pupil last name is " + pupilInst.getLastName()); Map
Plusieurs fois dans l'article on a abordé la problématique de la synchronisation. En effet, pour éviter les problèmes d'accès concurrentiel à une resource, on est obligés de synchroniser les Maps manuellement. Pour ce faire, on peut soit employer la méthode Collections.synchronizedMap(), soit utiliser dans le code ConcurrentHashMap. L'exemple du code ci-dessous illustre en quoi consiste le problème d'accès concurrentiel. Le cas de figure est un bout de code qui gère les équipes participants dans un tournoi. Les équipes sont classées par villes. La condition principale est qu'une ville peut avoir qu'un seule équipe. L'ordre d'inscription est alors important.
import java.util.Map; import java.util.HashMap; public class UnsynchronizedMapProblem { public static void main(String[] args) { ClubsList clubs = new ClubsList(); Thread t1 = new Thread(new Adder(clubs, "Arsenal", "London")); Thread t2 = new Thread(new Adder(clubs, "Chelsea", "London")); t1.start(); t2.start(); } } class ClubsList { private MapsynMap = new HashMap (); public ClubsList() { synMap.put("Leverkusen", "Bayer"); synMap.put("Wolfsburg", "VfL"); synMap.put("Bastia", "SC"); synMap.put("Ajaccio", "AC"); synMap.put("Warsaw", "Legia"); } public void getLondonClub() { System.out.println("\n"); System.out.println("Got " + synMap.get("London")); } public void addNew(String c, String n) { synMap.put(c, n); System.out.println("Got " + synMap.get(c)); } } class Displayer implements Runnable { ClubsList clubs = null; public Displayer(ClubsList c) { clubs = c; } public void run() { clubs.getLondonClub(); } } class Adder implements Runnable { ClubsList clubs = null; String city = ""; String name = ""; public Adder(ClubsList c, String n, String ci) { city = ci; name = n; clubs = c; } public void run() { clubs.addNew(city, name); } }
Dans ce cas non synchronisé, on s'aperçoit que soit Chelsea est inscrit avant Arsenal, soit l'inverse. Afin de régler ce problème, on peut utiliser ConcurrentHashMap et sa méthode putIfAbsent(). Le code de la classe ClubsList va se présenter ainsi :
class ClubsList { private ConcurrentHashMapsynMap = new ConcurrentHashMap (); public ClubsList() { synMap.put("Leverkusen", "Bayer"); synMap.put("Wolfsburg", "VfL"); synMap.put("Bastia", "SC"); synMap.put("Ajaccio", "AC"); synMap.put("Warsaw", "Legia"); } public void getLondonClub() { System.out.println("\n"); System.out.println("Got " + synMap.get("London")); } public void removeLondonClub() { System.out.println("\n"); synMap.remove("London"); } public void addNew(String c, String n) { synMap.putIfAbsent(c, n); System.out.println("Got " + synMap.get(c)); } }
Maintenant, en exécutant le code on s'apercevra qu'uniquement une seule équipe est inscrite dans la liste. Et c'est cela que l'application doit faire.