Tri d'une liste grace à une propriété de l'objet
Written by Fneuch on 12.12.06Vous avez une liste d'objet et vous voulez la trier sans passer par la Base de donnée. Vous ne voulez pas non plus implémenter l'interface comparable car vous ne voulez pas utiliser toujours le même tri...
Faites vous un objet Comparator!
En voici un qui tri sur une propriété de type Integer. C'est un draft pour répondre à mes besoins, mais c'est pour le principe.
package com.fneuch.collections.functor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Comparator;
public class IntegerPropertyComparator implements Comparator {
private String nomPropriete;
/**
* Constructeur par défaut.
* Ne pas oubliez d'assigner la nom de la propriété à comparer avant d'utiliser
* le comparator.
*/
public IntegerPropertyComparator() {
}
/**
* Constructeur permettant de fournir immédiatement le nom de la propriété à
* comparer.
*
* @param nomPropriete le nom de la propriété à comparer.
*/
public IntegerPropertyComparator(String nomPropriete) {
this.setNomPropriete(nomPropriete);
}
/**
* Méthode privée permettant de regrouper en un seul endroit la récupération
* de la valeur entière recherchée.
*
* @throws java.lang.reflect.InvocationTargetException Lorsque l'invocation de la méthode est infructueuse
* @throws java.lang.IllegalAccessException Si la méthode n'est pas accessible (avec un statut private par exemple)
* @throws java.lang.NoSuchMethodException Si la méthode n'existe pas
* @return la valeur entière (int) retournée par la méthode demandée
* @param obj Sur quel objet fesons nous l'invocation de la méthode.
* @param methodName Le nom de la méthode à invoquer.
*/
private int getMethodValue(String methodName, Object obj)
throws NoSuchMethodException,
IllegalAccessException,
InvocationTargetException {
// Plus claire de travailler sur l'objet classe que sur obj.getClass()
Class classe = obj.getClass();
// Récupération de la méthode à invoquer
Method method1 = classe.getMethod(methodName, new Class[] {});
// Ce comparateur ne fonctionne que sur les Integer ou les types int
// retourne en type primitif int pour des raisons de simplicité.
return ((Integer)method1.invoke(obj, new Object[] {} )).intValue();
}
/**
* Effectue la comparaison sur la méthode fournis.
*
* @return <0 si l'objet1 est plus petit, >0 si l'objet1 est plus grand et 0 si les 2 objets sont égaux
* @param o1 Le premier objet à comparer
* @param o2 Le deuxième objet à comparer
*/
public int compare(Object o1, Object o2) {
// Le nom de la propriété ne peut pas être nul ou vide
if(this.nomPropriete == null && this.nomPropriete.trim().length() == 0) {
throw new IllegalStateException("Vous devez préciser un nom de propriété à comparer");
}
/*
* Étant donné qu'on ne connait pas l'objet et qu'on fonctionne par une
* propriété pour l'instant je me limite à vérifier l'assignation.
*
* Pourrait être améliorer pour vérifier la présence de la propriété afin de
* pouvoir comparer 2 objets totalement différents, tant qu'ils ont la même
* propriété.
*/
if(!(o1.getClass().isAssignableFrom(o2.getClass()) && o2.getClass().isAssignableFrom(o1.getClass()))) {
throw new ClassCastException("Impossible de comparer " + o1.getClass().getName()
+ " avec la classe " + o2.getClass().getName());
}
// OK, on cherche une méthode du style getNomPropriete
StringBuffer sb = new StringBuffer("get");
sb.append(this.nomPropriete.substring(0,1).toUpperCase());
sb.append(this.nomPropriete.substring(1));
String methodProperty = sb.toString();
// Pour la comparaison par la suite
int valeur1 ;
int valeur2;
// Les 2 try consécutifs servent uniquement à avoir un message d'exception
// plus précis
try {
// On récupère la valeur de l'objet 1
try {
valeur1 = getMethodValue(methodProperty, o1);
} catch (NoSuchMethodException nsfe) {
throw new IllegalStateException("La propriété " + this.nomPropriete
+ " n'existe pas dans l'objet "
+ o1.getClass().getName());
}
// On récupère la valeur de l'objet 2
try {
valeur2 = getMethodValue(methodProperty, o2);
} catch (NoSuchMethodException nsfe) {
throw new IllegalStateException("La propriété " + this.nomPropriete
+ " n'existe pas dans l'objet "
+ o2.getClass().getName());
}
} catch (IllegalAccessException iae) {
throw new ClassCastException("Impossible de comparer " + o1.getClass().getName()
+ " avec la classe " + o2.getClass().getName());
} catch (InvocationTargetException ite) {
throw new ClassCastException("Impossible de comparer " + o1.getClass().getName()
+ " avec la classe " + o2.getClass().getName());
}
// On fait la comparaison et retourne le résultat
return (valeur1 - valeur2);
}
/**
* Retourne le nom de la propriété qui sera comparé.
*
* @return le nom de la propriété
*/
public String getNomPropriete() {
return nomPropriete;
}
/**
* Assigne le nom de la propriété à comparer.
*
* @param nomPropriete le nom de la propriété
*/
public void setNomPropriete(String nomPropriete) {
this.nomPropriete = nomPropriete;
}
}
Et vous l'utilisez de la façon suivante:
Collections.sort(liste,new IntegerPropertyComparator("ordreAffichage"));
Et voilà, votre objet liste sera trié.
Tout ce que vous avez à faire c'est d'avoir une liste d'objets semblables qui contiennent la méthode getOrdreAffichage().
5 commentaires: Responses to “ Tri d'une liste grace à une propriété de l'objet ”
By Anonyme on 20:18
J'imagine que je ne saisi pas exactement le but, mais me semble que c'est complexe ton affaire pour faire un trie.
En C++ tu as seulement besoin de faire une fonction de comparaison par trie.
Par exemple:
bool SortAddressBookByFirstname(const Contact& c1, const Contact& c2)
{
if (c1.GetFirstname().compare(c2.GetFirstname() < 0)
return true;
return false;
}
bool SortAddressBookByAge(const Contact& c1, const Contact& c2)
{
if (c1.GetAge() < c2.GetAge())
return true;
return false;
}
sort(addressBook.begin(), addressBook.end(), SortAddressBookByFirstname);
sort(addressBook.begin(), addressBook.end(), SortAddressBookByAge);
By Anonyme on 10:29
Effectivement, ça peut sembler complexe si tu compare à C++. C'est parce qu'en Java, on a un vrai langage objet. ;)
Nous n'avons pas la possibilité de faire une fonction n'appartenant pas à un objet. Il devient donc impossible d'avoir un pointeur sur une fonction. C'est de là qu'est apparue le concept de "functor" qui permet grosso modo de faire la même chose...
Par conte, on m'a soumis un autre objet plus générique qui fait la même chose qui parait plus simple parce que toute la fonctionnalité de recherche de la méthode est encapsulé dans un autre objet faisant partie d'une librairie d'Apache : BeanUtils
import java.lang.reflect.InvocationTargetException;
import java.util.Comparator;
import org.apache.commons.beanutils.PropertyUtils;
public class ComparateurPropriete implements Comparator {
private String propriete;
private Comparator comparateur;
public ComparateurPropriete(String propriete) {
this(propriete, null);
}
public ComparateurPropriete(String propriete, Comparator comparateur) {
this.propriete = propriete;
this.comparateur = comparateur;
}
public int compare(Object o1, Object o2) {
if (comparateur == null) {
Comparable c1 = (Comparable) getPropriete(o1);
Comparable c2 = (Comparable) getPropriete(o2);
return c1.compareTo(c2);
} else {
Object p1 = getPropriete(o1);
Object p2 = getPropriete(o2);
return comparateur.compare(p1, p2);
}
}
private Object getPropriete(Object bean) {
try {
return (Comparable) PropertyUtils.getProperty(bean, propriete);
} catch (NoSuchMethodException e) {
throw new IllegalArgumentException(
"Aucun getter pour la propriété " + propriete + " dans la classe " + bean.getClass().getName());
} catch (IllegalAccessException e) {
throw new IllegalArgumentException( "Le getter pour la propriété " + propriete + " dans la classe " + bean.getClass().getName() + " doit être publique");
} catch (InvocationTargetException e) {
Throwable cause = e.getCause();
if (cause instanceof Error) {
throw (Error) cause;
} else if (cause instanceof RuntimeException) {
throw (RuntimeException) cause;
} else {
throw new RuntimeException(cause);
}
}
}
}
By Anonyme on 12:18
En C# aussi une fonction doit appartenir à une classe et c'est aussi fessable très simplement.
class Contact
{
...
static public int CompareByFirstName(Contact c)
{
...
// < 0 si plus petit
// == 0 si égal
// > 0 si plus grand
return result;
}
}
J'aurais pu mettre la fonction dans une classe en C++ aussi. Hum... Étrange ton café ;-)
By Anonyme on 17:56
cro :
Un tri en Java peut être fait aussi court que ton exemple :
Collections.sort(contacts, new Comparator<Contact>() {
public int compare(Contact c1, Contact c2) {
return c1.getFirstName().compareTo(c2.getFirstName());
}
});
L'avantage de l'approche du comparateur générique c'est qu'une fois définit, il peut être réutilisé pour tous les types d'objet à trier.
e.g. Collection.sort(contacts, new PropertyComparator("firstName"));
Fneuch :
La version que j'ai suggéré dans l'intranet Nurun fait pas exactement la même chose, il y a deux différences. Premièrement, ta version était seulement pour les propriétés de type Integer, ma version supporte tout les types de propriété, qu'ils soit comparables où non.
Deuxièmement, l'utilisation de BeanUtils ne fait pas seulement limité la quantité de code, mais BeanUtils utilise java.beans.Introspector, c'que ne fait pas ton approche. La spec des JavaBeans permet de définir des propriétés avec méthode accessor autre "getPropertyName", en utilisant les BeanInfos. L'approche indirectement par l'Introspector permet de supportés les accessors custom. Et le layer BeanUtils ajoute aussi les propriétés nested et indexées.
Guillaume
By Fneuch on 08:38
Cool! J'ai du volume dans mes commentaires... lol
Merci Guillaume pour ta précision! Je suis content de voir que tu lis mon blog (À moins que ce soit Steve qui t'a dit va lui fermé la trappe...lol )
Effectivement, ma version ne faisait que comparer de propriété de type Integer. Mais, Comme j'ai dit, ce n'étais qu'un draft... Si tes drafts sont toujours comme ça, ben j'ai hâte de travailler avec toi, on va avoir du plaisir à se "challenger" pis on devrait avoir des design de fou...
Mon but original dans mon billet était de soulever une réflexion. (Ce que je semble avoir réussis puisque j'ai eu 2 commentaires qui ne sont pas de moi...lol) Car, de par mon expérience, 90% des gens vont se servir de la base de donnée pour faire des tris sur des objets qui ont une persistance. Or, cette façon de faire n'est pas toujours une bonne idée et les gens ont rarement tendance à changer leur façon de faire quand ils ont trouvé une façon qui fonctionne.
En passant, j'en ai fait une version de ton comparateur qui prends un booléen, ce qui permet de faire une comparaison dîtes d'ordre naturel ou inverse. Si ça t'intéresse...