sept.
2009
MultiValueMapBuilder
Aujourd'hui, je vous propose une petite classe utilitaire permettant de simplifier l'insertion d'éléments dans des collections (List, Set, Map) elle-mêmes placées dans des Maps.
Ce genre de structure n'est pas rare, dès lors que l'on manipule des données complexes :
Map<City, List<String>> streetNamesByCity; Map<Department, Set<Employee>> employeesByDepartment; Map<Employee, Map<Date,VacationType>> vacationsByEmployee;
Insérer un élément dans une sous-collection est simple mais un peu fastidieux et répétitif : il faut vérifier si la sous-collection existe, la créer et l'insérer au besoin, puis -enfin- y insérer le nouvel élément...
Les méthodes statiques de la classe que je vous propose encapsulent cet algorithme :
public static <K,V> boolean putList(Map<K,List<V>> map, K key, V value); public static <K,V> boolean putSet(Map<K,Set<V>> map, K key, V value); public static <K,MK,MV> void putMap(Map<K,Map<MK,MV>> map, K key, MK mapKey, MV mapValue);
Examinons le code de la méthode putList
:
public static <K,V> boolean putList(Map<K,List<V>> map, K key, V value) { List<V> list = map.get(key); if (list == null) { list = new ArrayList<V>(); map.put(key, list); } return list.add(value); }
Rien de compliqué, mais le fait de l'encapsuler dans une méthode utilitaire vous permet de simplifier votre code appelant :
List<Street> allStreets = streetDao.getAll(); Map<City, List<String>> streetNamesByCity = new HashMap<City, List<String>>(); for(Street street : allStreets) { MultiValueMapBuilder.putList(streetNamesByCity, street.getCity(), street.getName()); }
Un autre exemple, utilisant une Map :
List<Vacation> employeesVacations = vacationDao.getEmployeesVacations(); Map<Employee, Map<Date,VacationType>> employeeVacationsByDate = new HashMap<Person, Map<Date, Vacation>>(); for (Vacation vacation : vacations) { MultiValueMapBuilder.putMap(employeeVacationsByDate, vacation.getEmployee(), vacation.getDate(), vacation.getType()); }
Le code source de la classe est disponible en annexe.
Commentaires
Bonne idée ! Ça m'a fait pensé à une variante, afin d'éviter de coupler tous les endroits dans le code où on souhaite insérer dans la map à une classe statique utilitaire. J'ai fais une variante mais qui utilise les proxy afin de décorer les getters pour créer ces listes automatiquement. Cette variante peut aussi être utilisée pour faire des trucs plus sympa, comme le type de la valeur de la map peut être instancié automatiquement.
Voir http://blog.mycila.com/.
C'est bien mais ça existe déjà dans Google Collections avec la Multimap. Pas la peine de réinventer la roue.
@totolastname : merci pour cette remarque, je n'avais pas vu.
Effectivement, Google Collections propose ce genre de fonctionnalités via les MultiMaps, avec plein d'options. Si votre projet utilise déjà cette librairie, c'est impeccable, sinon, je pense que cette seule fonctionnalité ne justifie pas l'ajout d'un jar de presque 600k dans le projet, auquel cas ma solution "light" me semble préférable.