Comment comparer des objets par plusieurs champs

237

Supposons que vous ayez certains objets qui ont plusieurs champs, ils peuvent être comparés par:

public class Person {

    private String firstName;
    private String lastName;
    private String age;

    /* Constructors */

    /* Methods */

}

Donc, dans cet exemple, lorsque vous demandez si:

a.compareTo(b) > 0

vous vous demandez peut-être si le nom de famille de a précède b, ou si a est plus ancien que b, etc ...

Quelle est la façon la plus propre de permettre une comparaison multiple entre ces types d'objets sans ajouter de fouillis ou de frais généraux inutiles?

  • java.lang.Comparable l'interface permet la comparaison par un seul champ
  • L'ajout de nombreuses méthodes de comparaison (ie compareByFirstName(), compareByAge()etc ...) est encombré à mon avis.

Alors, quelle est la meilleure façon de procéder?

Yuval Adam
la source
3
pourquoi est-ce un cw? C'est une question de programmation parfaitement valable.
Elie
2
Savez-vous que Comparable permet de comparer autant de champs que vous le souhaitez?
DJClayworth

Réponses:

81

Vous pouvez implémenter un Comparatorqui compare deux Personobjets et vous pouvez examiner autant de champs que vous le souhaitez. Vous pouvez mettre une variable dans votre comparateur qui lui indique à quel champ comparer, bien qu'il serait probablement plus simple d'écrire simplement plusieurs comparateurs.

Elie
la source
5
Je préfère en fait l'idée d'utiliser un seul comparateur. Je ne pense pas que cette réponse soit fausse, mais quiconque la lit devrait certainement vérifier la réponse de Steve Kuo ci-dessous.
Felipe Leão
Les comparateurs multiples ne l'étaient que si vous voulez des méthodes de comparaison différentes qui ne sont pas fonction des données elles-mêmes - c'est-à-dire que parfois vous voulez comparer par nom, d'autres fois par âge, etc. Pour comparer par plusieurs champs en même temps, un seul comparateur serait nécessaire.
Elie
399

Avec Java 8:

Comparator.comparing((Person p)->p.firstName)
          .thenComparing(p->p.lastName)
          .thenComparingInt(p->p.age);

Si vous avez des méthodes d'accesseur:

Comparator.comparing(Person::getFirstName)
          .thenComparing(Person::getLastName)
          .thenComparingInt(Person::getAge);

Si une classe implémente Comparable, un tel comparateur peut être utilisé dans la méthode compareTo:

@Override
public int compareTo(Person o){
    return Comparator.comparing(Person::getFirstName)
              .thenComparing(Person::getLastName)
              .thenComparingInt(Person::getAge)
              .compare(this, o);
}
Enigo
la source
5
La distribution (Person p)est particulièrement importante pour les comparateurs chaînés.
membersound
5
Quelle est son efficacité lors de la comparaison d'un grand nombre d'objets, par exemple lors du tri? Doit-elle créer de nouvelles Comparatorinstances à chaque appel?
jjurm
4
J'obtiens une exception NullPointerException lorsqu'un des champs que je compare est nul, comme une chaîne. Existe-t-il de toute façon de conserver ce format de comparaison mais de lui permettre d'être null safe?
rveach
3
@jjurm .thenComparing(Person::getLastName, Comparator.nullsFirst(Comparator.naturalOrder()))- premier sélecteur de champ, puis comparateur
gavenkoa
2
@jjurm lorsque vous l'utilisez compareTocomme indiqué ci-dessus, le Comparatorest créé à chaque appel de la méthode. Vous pouvez empêcher cela en stockant le comparateur dans un champ final statique privé.
Gandalf
165

Vous devez implémenter Comparable <Person>. En supposant que tous les champs ne seront pas nuls (par souci de simplicité), que l'âge est un entier et que le classement de comparaison est premier, dernier, âge, la compareTométhode est assez simple:

public int compareTo(Person other) {
    int i = firstName.compareTo(other.firstName);
    if (i != 0) return i;

    i = lastName.compareTo(other.lastName);
    if (i != 0) return i;

    return Integer.compare(age, other.age);
}
Steve Kuo
la source
10
si vous implémentez Comparable <Person> alors la méthode est compareTo (Person p) .. il semble que cette réponse ait été mélangée avec la méthode compare <T o1, T o2> de Comparator
Mike
5
Ce n'est pas recommandé. Utilisez Comparateur lorsque vous avez plusieurs champs.
indika
1
c'est la meilleure solution pour le moment, (mieux que plus de comparateurs)
Vasile Surdu
4
@indika, je suis curieux: pourquoi n'est-ce pas recommandé? Comparer l'utilisation de plusieurs propriétés me semble parfaitement correct.
ars-longa-vita-brevis
4
@ ars-longa-vita-brevis, Si vous utilisez Comparable, la logique de tri doit être dans la même classe dont les objets sont triés, c'est ce que l'on appelle l'ordre naturel des objets. Avec l'utilisation de Comparator, vous pouvez écrire une logique de tri personnalisée en dehors de la classe Person. Si vous souhaitez comparer les objets Personne uniquement par leur prénom ou leur nom, vous ne pouvez pas utiliser cette logique. Vous devez l'écrire à nouveau,
indika
78

(de façons de trier les listes d'objets en Java en fonction de plusieurs champs )

Code de travail dans cet essentiel

Utilisation des lambda de Java 8 (ajouté le 10 avril 2019)

Java 8 résout bien cela par lambda (bien que Guava et Apache Commons puissent encore offrir plus de flexibilité):

Collections.sort(reportList, Comparator.comparing(Report::getReportKey)
            .thenComparing(Report::getStudentNumber)
            .thenComparing(Report::getSchool));

Merci à la réponse de @ gaoagong ci-dessous .

Désordonné et alambiqué: Tri à la main

Collections.sort(pizzas, new Comparator<Pizza>() {  
    @Override  
    public int compare(Pizza p1, Pizza p2) {  
        int sizeCmp = p1.size.compareTo(p2.size);  
        if (sizeCmp != 0) {  
            return sizeCmp;  
        }  
        int nrOfToppingsCmp = p1.nrOfToppings.compareTo(p2.nrOfToppings);  
        if (nrOfToppingsCmp != 0) {  
            return nrOfToppingsCmp;  
        }  
        return p1.name.compareTo(p2.name);  
    }  
});  

Cela nécessite beaucoup de frappe, de maintenance et est sujet aux erreurs.

La manière réfléchie: Tri avec BeanComparator

ComparatorChain chain = new ComparatorChain(Arrays.asList(
   new BeanComparator("size"), 
   new BeanComparator("nrOfToppings"), 
   new BeanComparator("name")));

Collections.sort(pizzas, chain);  

Évidemment, cela est plus concis, mais encore plus sujet aux erreurs car vous perdez votre référence directe aux champs en utilisant des chaînes à la place (pas de sécurité de type, auto-refactorisation). Maintenant, si un champ est renommé, le compilateur ne signalera même pas de problème. De plus, cette solution utilisant la réflexion, le tri est beaucoup plus lent.

Comment y arriver: Trier avec la chaîne de comparaison de Google Guava

Collections.sort(pizzas, new Comparator<Pizza>() {  
    @Override  
    public int compare(Pizza p1, Pizza p2) {  
        return ComparisonChain.start().compare(p1.size, p2.size).compare(p1.nrOfToppings, p2.nrOfToppings).compare(p1.name, p2.name).result();  
        // or in case the fields can be null:  
        /* 
        return ComparisonChain.start() 
           .compare(p1.size, p2.size, Ordering.natural().nullsLast()) 
           .compare(p1.nrOfToppings, p2.nrOfToppings, Ordering.natural().nullsLast()) 
           .compare(p1.name, p2.name, Ordering.natural().nullsLast()) 
           .result(); 
        */  
    }  
});  

C'est beaucoup mieux, mais nécessite un code de plaque de chaudière pour le cas d'utilisation le plus courant: les valeurs nulles doivent être moins valorisées par défaut. Pour les champs nuls, vous devez fournir une directive supplémentaire à Guava que faire dans ce cas. C'est un mécanisme flexible si vous voulez faire quelque chose de spécifique, mais vous voulez souvent le cas par défaut (c'est-à-dire 1, a, b, z, null).

Tri avec Apache Commons CompareToBuilder

Collections.sort(pizzas, new Comparator<Pizza>() {  
    @Override  
    public int compare(Pizza p1, Pizza p2) {  
        return new CompareToBuilder().append(p1.size, p2.size).append(p1.nrOfToppings, p2.nrOfToppings).append(p1.name, p2.name).toComparison();  
    }  
});  

Comme ComparisonChain de Guava, cette classe de bibliothèque trie facilement sur plusieurs champs, mais définit également le comportement par défaut pour les valeurs nulles (c'est-à-dire 1, a, b, z, null). Cependant, vous ne pouvez pas spécifier autre chose non plus, sauf si vous fournissez votre propre comparateur.

Donc

En fin de compte, cela se résume à la saveur et au besoin de flexibilité (ComparaisonChain de Guava) par rapport au code concis (CompareToBuilder d'Apache).

Méthode bonus

J'ai trouvé une belle solution qui combine plusieurs comparateurs par ordre de priorité sur CodeReview dans un MultiComparator:

class MultiComparator<T> implements Comparator<T> {
    private final List<Comparator<T>> comparators;

    public MultiComparator(List<Comparator<? super T>> comparators) {
        this.comparators = comparators;
    }

    public MultiComparator(Comparator<? super T>... comparators) {
        this(Arrays.asList(comparators));
    }

    public int compare(T o1, T o2) {
        for (Comparator<T> c : comparators) {
            int result = c.compare(o1, o2);
            if (result != 0) {
                return result;
            }
        }
        return 0;
    }

    public static <T> void sort(List<T> list, Comparator<? super T>... comparators) {
        Collections.sort(list, new MultiComparator<T>(comparators));
    }
}

Ofcourse Apache Commons Collections a déjà une utilité pour cela:

ComparatorUtils.chainedComparator (comparatorCollection)

Collections.sort(list, ComparatorUtils.chainedComparator(comparators));
Benny Bottema
la source
22

@Patrick Pour trier plusieurs champs consécutivement, essayez ComparatorChain

Un ComparatorChain est un comparateur qui encapsule un ou plusieurs comparateurs en séquence. ComparatorChain appelle chaque comparateur en séquence jusqu'à ce que 1) un comparateur unique renvoie un résultat différent de zéro (et ce résultat est ensuite renvoyé), ou 2) la chaîne Comparator est épuisée (et zéro est renvoyé). Ce type de tri est très similaire au tri multi-colonnes en SQL, et cette classe permet aux classes Java d'émuler ce type de comportement lors du tri d'une liste.

Pour faciliter davantage le tri de type SQL, l'ordre de n'importe quel comparateur unique dans la liste peut> être inversé.

L'appel d'une méthode qui ajoute de nouveaux comparateurs ou modifie le tri ascendant / descendant après l'appel de la comparaison (objet, objet) entraînera une exception UnsupportedOperationException. Cependant, veillez à ne pas modifier la liste sous-jacente de comparateurs ou le BitSet qui définit l'ordre de tri.

Les instances de ComparatorChain ne sont pas synchronisées. La classe n'est pas thread-safe au moment de la construction, mais elle est thread-safe pour effectuer plusieurs comparaisons une fois toutes les opérations de configuration terminées.

Nigel_V_Thomas
la source
20

Apache Commons est une autre option que vous pouvez toujours envisager. Il offre de nombreuses options.

import org.apache.commons.lang3.builder.CompareToBuilder;

Ex:

public int compare(Person a, Person b){

   return new CompareToBuilder()
     .append(a.getName(), b.getName())
     .append(a.getAddress(), b.getAddress())
     .toComparison();
}
Xeroiris
la source
10
import com.google.common.collect.ComparisonChain;

/**
 * @author radler
 * Class Description ...
 */
public class Attribute implements Comparable<Attribute> {

    private String type;
    private String value;

    public String getType() { return type; }
    public void setType(String type) { this.type = type; }

    public String getValue() { return value; }
    public void setValue(String value) { this.value = value; }

    @Override
    public String toString() {
        return "Attribute [type=" + type + ", value=" + value + "]";
    }

    @Override
    public int compareTo(Attribute that) {
        return ComparisonChain.start()
            .compare(this.type, that.type)
            .compare(this.value, that.value)
            .result();
    }

}
Ran Adler
la source
1
J'aime beaucoup cette stratégie. Merci!
M. Polywhirl
Le moyen le plus efficace! Merci
Zakaria Bouazza
8

Pour ceux qui sont capables d'utiliser l'API de streaming Java 8, il existe une approche plus soignée qui est bien documentée ici: Lambdas et le tri

Je cherchais l'équivalent du C # LINQ:

.ThenBy(...)

J'ai trouvé le mécanisme en Java 8 sur le comparateur:

.thenComparing(...)

Voici donc l'extrait qui illustre l'algorithme.

    Comparator<Person> comparator = Comparator.comparing(person -> person.name);
    comparator = comparator.thenComparing(Comparator.comparing(person -> person.age));

Consultez le lien ci-dessus pour une manière plus nette et une explication sur la façon dont l'inférence de type Java le rend un peu plus maladroit à définir par rapport à LINQ.

Voici le test unitaire complet pour référence:

@Test
public void testChainedSorting()
{
    // Create the collection of people:
    ArrayList<Person> people = new ArrayList<>();
    people.add(new Person("Dan", 4));
    people.add(new Person("Andi", 2));
    people.add(new Person("Bob", 42));
    people.add(new Person("Debby", 3));
    people.add(new Person("Bob", 72));
    people.add(new Person("Barry", 20));
    people.add(new Person("Cathy", 40));
    people.add(new Person("Bob", 40));
    people.add(new Person("Barry", 50));

    // Define chained comparators:
    // Great article explaining this and how to make it even neater:
    // http://blog.jooq.org/2014/01/31/java-8-friday-goodies-lambdas-and-sorting/
    Comparator<Person> comparator = Comparator.comparing(person -> person.name);
    comparator = comparator.thenComparing(Comparator.comparing(person -> person.age));

    // Sort the stream:
    Stream<Person> personStream = people.stream().sorted(comparator);

    // Make sure that the output is as expected:
    List<Person> sortedPeople = personStream.collect(Collectors.toList());
    Assert.assertEquals("Andi",  sortedPeople.get(0).name); Assert.assertEquals(2,  sortedPeople.get(0).age);
    Assert.assertEquals("Barry", sortedPeople.get(1).name); Assert.assertEquals(20, sortedPeople.get(1).age);
    Assert.assertEquals("Barry", sortedPeople.get(2).name); Assert.assertEquals(50, sortedPeople.get(2).age);
    Assert.assertEquals("Bob",   sortedPeople.get(3).name); Assert.assertEquals(40, sortedPeople.get(3).age);
    Assert.assertEquals("Bob",   sortedPeople.get(4).name); Assert.assertEquals(42, sortedPeople.get(4).age);
    Assert.assertEquals("Bob",   sortedPeople.get(5).name); Assert.assertEquals(72, sortedPeople.get(5).age);
    Assert.assertEquals("Cathy", sortedPeople.get(6).name); Assert.assertEquals(40, sortedPeople.get(6).age);
    Assert.assertEquals("Dan",   sortedPeople.get(7).name); Assert.assertEquals(4,  sortedPeople.get(7).age);
    Assert.assertEquals("Debby", sortedPeople.get(8).name); Assert.assertEquals(3,  sortedPeople.get(8).age);
    // Andi     : 2
    // Barry    : 20
    // Barry    : 50
    // Bob      : 40
    // Bob      : 42
    // Bob      : 72
    // Cathy    : 40
    // Dan      : 4
    // Debby    : 3
}

/**
 * A person in our system.
 */
public static class Person
{
    /**
     * Creates a new person.
     * @param name The name of the person.
     * @param age The age of the person.
     */
    public Person(String name, int age)
    {
        this.age = age;
        this.name = name;
    }

    /**
     * The name of the person.
     */
    public String name;

    /**
     * The age of the person.
     */
    public int age;

    @Override
    public String toString()
    {
        if (name == null) return super.toString();
        else return String.format("%s : %d", this.name, this.age);
    }
}
Luke Machowski
la source
7

Écrire un Comparatormanuel pour un tel cas d'utilisation est une terrible solution OMI. Ces approches ad hoc présentent de nombreux inconvénients:

  • Pas de réutilisation de code. Violent SEC.
  • Plaque de chaudière.
  • Augmentation des possibilités d'erreurs.

Alors, quelle est la solution?

D'abord une théorie.

Notons la proposition "type Aprend en charge la comparaison" par Ord A. (Du point de vue du programme, vous pouvez penser Ord Aà un objet contenant une logique pour comparer deux As. Oui, tout comme Comparator.)

Maintenant, si Ord Aet Ord B, alors leur composite (A, B)devrait également prendre en charge la comparaison. ie Ord (A, B). Si Ord A, Ord Bet Ord C, alors Ord (A, B, C).

Nous pouvons étendre cet argument à l'arité arbitraire et dire:

Ord A, Ord B, Ord C, ..., Ord ZOrd (A, B, C, .., Z)

Appelons cette déclaration 1.

La comparaison des composites fonctionnera exactement comme vous l'avez décrit dans votre question: la première comparaison sera tentée en premier, puis la suivante, puis la suivante, etc.

C'est la première partie de notre solution. Maintenant la deuxième partie.

Si vous le savez Ord A, et savez comment vous transformer Ben A(appelez cette fonction de transformation f), alors vous pouvez aussi l'avoir Ord B. Comment? Eh bien, lorsque les deux Binstances doivent être comparées, vous les transformez d'abord en Autilisant f, puis appliquez Ord A.

Ici, nous mappons la transformation B → Avers Ord A → Ord B. Ceci est connu sous le nom de cartographie contravariante (ou comappour faire court).

Ord A, (B → A)comap Ord B

Appelons cette déclaration 2.


Appliquons maintenant ceci à votre exemple.

Vous avez un type de données nommé Personqui comprend trois champs de type String.

  • Nous le savons Ord String. Par déclaration 1, Ord (String, String, String).

  • Nous pouvons facilement écrire une fonction de Personà (String, String, String). (Renvoyez simplement les trois champs.) Puisque nous savons Ord (String, String, String)et Person → (String, String, String), par l'énoncé 2, nous pouvons utiliser comappour obtenir Ord Person.

QED.


Comment mettre en œuvre tous ces concepts?

La bonne nouvelle, c'est que vous n'avez pas à le faire. Il existe déjà une bibliothèque qui implémente toutes les idées décrites dans cet article. (Si vous êtes curieux de savoir comment ceux-ci sont mis en œuvre, vous pouvez regarder sous le capot .)

Voici à quoi ressemblera le code:

Ord<Person> personOrd = 
 p3Ord(stringOrd, stringOrd, stringOrd).comap(
   new F<Person, P3<String, String, String>>() {
     public P3<String, String, String> f(Person x) {
       return p(x.getFirstName(), x.getLastname(), x.getAge());
     }
   }
 );

Explication:

  • stringOrdest un objet de type Ord<String>. Cela correspond à notre proposition originale de "prend en charge la comparaison".
  • p3Ordest une méthode qui prend Ord<A>, Ord<B>, Ord<C>, et les rendements Ord<P3<A, B, C>>. Cela correspond à l'énoncé 1. ( P3signifie produit à trois éléments. Produit est un terme algébrique pour les composites.)
  • comapcorrespond bien comap,.
  • F<A, B>représente une fonction de transformation A → B.
  • p est une méthode d'usine pour créer des produits.
  • L'expression entière correspond à l'énoncé 2.

J'espère que cela pourra aider.

missingfaktor
la source
5

Au lieu de méthodes de comparaison, vous souhaiterez peut-être simplement définir plusieurs types de sous-classes "Comparateur" à l'intérieur de la classe Person. De cette façon, vous pouvez les transmettre aux méthodes de tri des collections standard.

Marc Novakowski
la source
3

Je pense que ce serait plus déroutant si votre algorithme de comparaison était "intelligent". J'irais avec les nombreuses méthodes de comparaison que vous avez suggérées.

La seule exception pour moi serait l'égalité. Pour les tests unitaires, il m'a été utile de remplacer les .Equals (en .net) afin de déterminer si plusieurs champs sont égaux entre deux objets (et non pas que les références soient égales).

Michael Haren
la source
3

S'il existe plusieurs façons pour un utilisateur de commander une personne, vous pouvez également configurer plusieurs comparateurs sous forme de constantes quelque part. La plupart des opérations de tri et des collections triées prennent un comparateur comme paramètre.

sblundy
la source
3
//Following is the example in jdk 1.8
package com;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

class User {
    private String firstName;
    private String lastName;
    private Integer age;

    public Integer getAge() {
        return age;
    }

    public User setAge(Integer age) {
        this.age = age;
        return this;
    }

    public String getFirstName() {
        return firstName;
    }

    public User setFirstName(String firstName) {
        this.firstName = firstName;
        return this;
    }

    public String getLastName() {
        return lastName;
    }

    public User setLastName(String lastName) {
        this.lastName = lastName;
        return this;
    }

}

public class MultiFieldsComparision {

    public static void main(String[] args) {
        List<User> users = new ArrayList<User>();

        User u1 = new User().setFirstName("Pawan").setLastName("Singh").setAge(38);
        User u2 = new User().setFirstName("Pawan").setLastName("Payal").setAge(37);
        User u3 = new User().setFirstName("Anuj").setLastName("Kumar").setAge(60);
        User u4 = new User().setFirstName("Anuj").setLastName("Kumar").setAge(43);
        User u5 = new User().setFirstName("Pawan").setLastName("Chamoli").setAge(44);
        User u6 = new User().setFirstName("Pawan").setLastName("Singh").setAge(5);

        users.add(u1);
        users.add(u2);
        users.add(u3);
        users.add(u4);
        users.add(u5);
        users.add(u6);

        System.out.println("****** Before Sorting ******");

        users.forEach(user -> {
            System.out.println(user.getFirstName() + " , " + user.getLastName() + " , " + user.getAge());
        });

        System.out.println("****** Aftre Sorting ******");

        users.sort(
                Comparator.comparing(User::getFirstName).thenComparing(User::getLastName).thenComparing(User::getAge));

        users.forEach(user -> {
            System.out.println(user.getFirstName() + " , " + user.getLastName() + " , " + user.getAge());
        });

    }

}
Pawan
la source
3

L'implémentation du code est la même si nous devons trier l'objet Person en fonction de plusieurs champs.

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;

public class Person {

private String firstName;
private String lastName;
private int age;

public String getFirstName() {
    return firstName;
}

public void setFirstName(String firstName) {
    this.firstName = firstName;
}

public String getLastName() {
    return lastName;
}

public void setLastName(String lastName) {
    this.lastName = lastName;
}

public int getAge() {
    return age;
}

public void setAge(int age) {
    this.age = age;
}

public Person(String firstName, String lastName, int age) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.age = age;
}


static class PersonSortingComparator implements Comparator<Person> {

    @Override
    public int compare(Person person1, Person person2) {

        // for first name comparison
        int firstNameCompare = person1.getFirstName().compareTo(person2.getFirstName());

        // for last name comparison
        int lastNameCompare = person1.getLastName().compareTo(person2.getLastName());

        // for last name comparison
        int ageCompare = person1.getAge() - person2.getAge();

        // Now comparing
        if (firstNameCompare == 0) {
            if (lastNameCompare == 0) {
                return ageCompare;
            }
            return lastNameCompare;
        }
        return firstNameCompare;
    }
}

public static void main(String[] args) {
    Person person1 = new Person("Ajay", "Kumar", 27);
    Person person2 = new Person("Ajay","Gupta", 23);
    Person person3 = new Person("Ajay","Kumar", 22);


    ArrayList<Person> persons = new ArrayList<>();
    persons.add(person1);
    persons.add(person2);
    persons.add(person3);


    System.out.println("Before Sorting:\n");
    for (Person person : persons) {
        System.out.println(person.firstName + " " + person.lastName + " " + person.age);
    }

    Collections.sort(persons, new PersonSortingComparator());

    System.out.println("After Sorting:\n");
    for (Person person : persons) {
        System.out.println(person.firstName + " " + person.lastName + " " + person.age);
    }
}

}
Sachchit Bansal
la source
2
//here threshold,buyRange,targetPercentage are three keys on that i have sorted my arraylist 
final Comparator<BasicDBObject> 

    sortOrder = new Comparator<BasicDBObject>() {
                    public int compare(BasicDBObject e1, BasicDBObject e2) {
                        int threshold = new Double(e1.getDouble("threshold"))
                        .compareTo(new Double(e2.getDouble("threshold")));
                        if (threshold != 0)
                            return threshold;

                        int buyRange = new Double(e1.getDouble("buyRange"))
                        .compareTo(new Double(e2.getDouble("buyRange")));
                        if (buyRange != 0)
                            return buyRange;

                        return (new Double(e1.getDouble("targetPercentage")) < new Double(
                                e2.getDouble("targetPercentage")) ? -1 : (new Double(
                                        e1.getDouble("targetPercentage")) == new Double(
                                                e2.getDouble("targetPercentage")) ? 0 : 1));
                    }
                };
                Collections.sort(objectList, sortOrder);
Pradeep Singh
la source
Je suis venu à cette question parce que mon code a commencé à aimer votre réponse;)
jan groth
0

Si vous implémentez l' interface Comparable , vous souhaiterez choisir une propriété simple à classer. C'est ce qu'on appelle l'ordre naturel. Considérez-le comme la valeur par défaut. Il est toujours utilisé quand aucun comparateur spécifique n'est fourni. Il s'agit généralement d'un nom, mais votre cas d'utilisation peut nécessiter quelque chose de différent. Vous êtes libre d'utiliser n'importe quel nombre d'autres comparateurs que vous pouvez fournir à diverses API de collections pour remplacer l'ordre naturel.

Notez également que généralement si a.compareTo (b) == 0, alors a.equals (b) == true. Ce n'est pas grave, mais il y a des effets secondaires à connaître. Voir les excellents javadocs sur l'interface Comparable et vous trouverez beaucoup d'informations intéressantes à ce sujet.

Mark Renouf
la source
0

Blog suivant donné un bon exemple de comparateur enchaîné

http://www.codejava.net/java-core/collections/sorting-a-list-by-multiple-attributes-example

import java.util.Arrays;
import java.util.Comparator;
import java.util.List;

/**
 * This is a chained comparator that is used to sort a list by multiple
 * attributes by chaining a sequence of comparators of individual fields
 * together.
 *
 */
public class EmployeeChainedComparator implements Comparator<Employee> {

    private List<Comparator<Employee>> listComparators;

    @SafeVarargs
    public EmployeeChainedComparator(Comparator<Employee>... comparators) {
        this.listComparators = Arrays.asList(comparators);
    }

    @Override
    public int compare(Employee emp1, Employee emp2) {
        for (Comparator<Employee> comparator : listComparators) {
            int result = comparator.compare(emp1, emp2);
            if (result != 0) {
                return result;
            }
        }
        return 0;
    }
}

Comparateur d'appel:

Collections.sort(listEmployees, new EmployeeChainedComparator(
                new EmployeeJobTitleComparator(),
                new EmployeeAgeComparator(),
                new EmployeeSalaryComparator())
        );
vaquar khan
la source
0

À partir de la réponse de Steve, l'opérateur ternaire peut être utilisé:

public int compareTo(Person other) {
    int f = firstName.compareTo(other.firstName);
    int l = lastName.compareTo(other.lastName);
    return f != 0 ? f : l != 0 ? l : Integer.compare(age, other.age);
}
Gerold Broser
la source
0

Il est facile de comparer deux objets avec la méthode hashcode en java`

public class Sample{

  String a=null;
  String b=null;

  public Sample(){
      a="s";
      b="a";
  }
  public Sample(String a,String b){
      this.a=a;
      this.b=b;
  }
  public static void main(String args[]){
      Sample f=new Sample("b","12");
      Sample s=new Sample("b","12");
      //will return true
      System.out.println((s.a.hashCode()+s.b.hashCode())==(f.a.hashCode()+f.b.hashCode()));

      //will return false
      Sample f=new Sample("b","12");
      Sample s=new Sample("b","13");
      System.out.println((s.a.hashCode()+s.b.hashCode())==(f.a.hashCode()+f.b.hashCode()));

}
Exode
la source
Veuillez ne pas faire ça. Les codes de hachage ne doivent pas être utilisés pour des comparaisons d'égalité mais pour l'indexation de table de hachage. Les collisions de hachage peuvent entraîner l'égalité pour deux objets différents. Même les tables de hachage reposent sur une réelle égalité en cas de collision de hachage.
Noel Widmer
0

Habituellement, je remplace ma compareTo()méthode comme celle-ci chaque fois que je dois faire un tri à plusieurs niveaux.

public int compareTo(Song o) {
    // TODO Auto-generated method stub
    int comp1 = 10000000*(movie.compareTo(o.movie))+1000*(artist.compareTo(o.artist))+songLength;
    int comp2 = 10000000*(o.movie.compareTo(movie))+1000*(o.artist.compareTo(artist))+o.songLength;
    return comp1-comp2;
} 

Ici, la première préférence est donnée au nom du film, puis à l'artiste et enfin à SongLength. Il vous suffit de vous assurer que ces multiplicateurs sont suffisamment éloignés pour ne pas franchir les frontières les uns des autres.

jayanth
la source