Salut,
Ca doit surement exister dans ces APIs de collection, mais je ne les connais pas suffisamment...
En effet le get(0) n'est pas top : cela pourrait engendrer des erreurs dans le cas où la liste comporte des types différents.
Exemple :
1 2 3 4 5
| List<Number> list = new ArrayList<Number>();
list.add( 10 ); // Integer
list.add( 10.0 ); // Double
Map<Number, Number> map = asMap(list, "value"); |
Les types Integer et Double possèdent bien un champ "value", mais ce n'est pas le même ce qui engendre une erreur...
Il serait préférable de passer la classe de base en paramètre, afin d'être sûr de taper dans quelque chose qui existe bel et bien :
public static <K, V> Map<K, V> asMap(final List<? extends V> items, Class<V> clazz, final String keyProperty)
De même plutôt que de passer par les Fields, il serait plus propre de passer par un accesseur via les Methods (ce qui permet au passage d'éviter également le setAccessible()).
Attention également à ce bout de code anodin :
1 2 3
| } catch (final Exception e) {
result = null;
} |
En cas d'erreur tu retournes null... sans rien comprendre. Le problème c'est que du coup tu peux passer complètement à travers... 
Il serait préférable de remonter une RuntimeException, afin d'avoir toutes les infos utiles si jamais l'erreur se produit (ca fera planter le programme, mais avec un joli stacktrace et une exception claire et net) :
1 2 3
| } catch (final Exception e) {
throw new RuntimeException(e);
} |
Enfin dernier problème signalé par le warning sur cette ligne :
final K key = (K) fieldKey.get(item);
Le compilateur ne peut pas vérifier la cohérence des types. Donc en cas d'erreur il est possible de créer une Map incorrect, par exemple :
Map<Long, Client> clientById = asMap(clients, "date");
L'attribut "date" est une Date, mais on le déclare en Long.
Cela fonctionne sans erreur mais on reçoit une Map<Date,Client> déclarée en Map<Long, Client> ce qui va provoquer des erreurs inattendus lorsqu'on l'utilisera... et un bon casse-tête pour en comprendre l'origine 
La solution la plus sécurisée consiste à passer via une interface définie comme ceci :
1 2 3
| public interface GetProperty<T, A> {
public A get(T object);
} |
Cette interface permettra d'obtenir une valeur à partir d'un objet.
Il suffit ensuite d'utiliser cette interface pour accéder aux champs souhaités, ce qui nous donne une méthode asMap() plutôt simplifiée :
1 2 3 4 5 6 7 8 9 10 11
| public static <K, V> Map<K, V> asMap(final List<? extends V> items, GetProperty<V, K> getKey) {
Map<K, V> result = null;
if (items != null && !items.isEmpty()) {
result = new HashMap<K, V>();
for (final V item : items) {
result.put(getKey.get(item), item);
}
}
return result;
} |
Après c'est sûr que l'appel est un peu plus "long" à écrire (vivement Java 8 bordel) :
1 2 3 4 5 6 7
| Map<Long, Client> clientById = asMap(clients,
new GetProperty<Client, Long>() {
@Override
public Long get(Client c) {
return c.getId();
}
}); |
Mais c'est nettement plus propre (le compilateur détectera les erreurs de type) et plus rapide que la reflection qui s'avère assez "lourde" (environ 3x plus rapide chez moi).
a++
Partager