web-dev-qa-db-fra.com

Trier une liste d'objets par plusieurs champs

J'ai une liste d'objets Java que je veux trier en fonction de plusieurs champs.

public class graduationCeremony {
    String campus;
    String faculty;
    String building;
}

Est-il possible d'utiliser une interface Comparator ou Comparable pour trier la liste selon plusieurs champs? Tous les exemples que j'ai vus sont triés selon un seul champ. En d'autres termes, on peut trier par 'campus' OR 'faculty' OR 'building'. Je veux trier par 'campus', puis 'faculty' ', puis' building '(tel qu'il existe en SQL: ORDER BY campus, faculty, building)

Je pense que cette question a été posée avant , mais je ne comprends pas la réponse acceptée. Quelqu'un peut-il développer ou illustrer cette réponse?

34
sim

Votre comparateur ressemblerait à ceci:

public class GraduationCeremonyComparator implements Comparator<GraduationCeremony> {
    public int compare(GraduationCeremony o1, GraduationCeremony o2) {
        int value1 = o1.campus.compareTo(o2.campus);
        if (value1 == 0) {
            int value2 = o1.faculty.compareTo(o2.faculty);
            if (value2 == 0) {
                return o1.building.compareTo(o2.building);
            } else {
                return value2;
            }
        }
        return value1;
    }
}

Fondamentalement, il continue de comparer chaque attribut successif de votre classe chaque fois que les attributs comparés jusqu'à présent sont égaux (== 0).

61
Daniel DiPaolo

Oui, vous pouvez absolument le faire. Par exemple:

public class PersonComparator implements Comparator<Person>
{
    public int compare(Person p1, Person p2)
    {
        // Assume no nulls, and simple ordinal comparisons

        // First by campus - stop if this gives a result.
        int campusResult = p1.getCampus().compareTo(p2.getCampus());
        if (campusResult != 0)
        {
            return campusResult;
        }

        // Next by faculty
        int facultyResult = p1.getFaculty().compareTo(p2.getFaculty());
        if (facultyResult != 0)
        {
            return facultyResult;
        }

        // Finally by building
        return p1.getBuilding().compareTo(p2.getBuilding());
    }
}

Fondamentalement, vous dites: "Si je peux dire lequel vient en premier juste en regardant le campus (avant qu'ils ne viennent de différents campus, et le campus est le domaine le plus important), alors je vais simplement retourner ce résultat. Sinon, je Je vais continuer à comparer les facultés. Encore une fois, arrêtez-vous si cela suffit pour les distinguer. Sinon, (si le campus et la faculté sont les mêmes pour les deux personnes), utilisez simplement le résultat de la comparaison en construisant. "

34
Jon Skeet

Si vous savez à l'avance quels champs utiliser pour effectuer la comparaison, d'autres personnes ont donné les bonnes réponses.
Ce qui pourrait vous intéresser, c'est de trier votre collection au cas où vous ne savez pas au moment de la compilation quels critères appliquer. Imaginez que vous ayez un programme traitant des villes:



    protected Set<City> cities;
    (...)
    Field temperatureField = City.class.getDeclaredField("temperature");
    Field numberOfInhabitantsField = City.class.getDeclaredField("numberOfInhabitants");
    Field rainfallField = City.class.getDeclaredField("rainfall");
    program.showCitiesSortBy(temperatureField, numberOfInhabitantsField, rainfallField);
    (...)
    public void showCitiesSortBy(Field... fields) {
        List<City> sortedCities = new ArrayList<City>(cities);
        Collections.sort(sortedCities, new City.CityMultiComparator(fields));
        for (City city : sortedCities) {
            System.out.println(city.toString());
        }
    }

où vous pouvez remplacer les noms de champ codés en dur par des noms de champ déduits d'une demande utilisateur dans votre programme.

Dans cet exemple, City.CityMultiComparator<City> est une classe imbriquée statique de la classe City implémentant Comparator:



    public static class CityMultiComparator implements Comparator<City> {
        protected List<Field> fields;

        public CityMultiComparator(Field... orderedFields) {
            fields = new ArrayList<Field>();
            for (Field field : orderedFields) {
                fields.add(field);
            }
        }

        @Override
        public int compare(City cityA, City cityB) {
            Integer score = 0;
            Boolean continueComparison = true;
            Iterator itFields = fields.iterator();

            while (itFields.hasNext() && continueComparison) {
                Field field = itFields.next();
                Integer currentScore = 0;
                if (field.getName().equalsIgnoreCase("temperature")) {
                    currentScore = cityA.getTemperature().compareTo(cityB.getTemperature());
                } else if (field.getName().equalsIgnoreCase("numberOfInhabitants")) {
                    currentScore = cityA.getNumberOfInhabitants().compareTo(cityB.getNumberOfInhabitants());
                } else if (field.getName().equalsIgnoreCase("rainfall")) {
                    currentScore = cityA.getRainfall().compareTo(cityB.getRainfall());
                }
                if (currentScore != 0) {
                    continueComparison = false;
                }
                score = currentScore;
            }

            return score;
        }
    }


Vous voudrez peut-être ajouter une couche supplémentaire de précision, pour spécifier, pour chaque champ, si le tri doit être ascendant ou descendant. Je suppose qu'une solution consiste à remplacer Field objets par des objets d'une classe que vous pourriez appeler SortedField, contenant un objet Field, plus un autre champ signifiant ascendant ou descendant.

5
Marek Stanley

J'espère que cela t'aides:

import Java.util.ArrayList;
import Java.util.Collections;
import Java.util.Comparator;
import Java.util.Iterator;

class Person implements Comparable {
  String firstName, lastName;

  public Person(String f, String l) {
    this.firstName = f;
    this.lastName = l;
  }

  public String getFirstName() {
    return firstName;
  }

  public String getLastName() {
    return lastName;
  }

  public String toString() {
    return "[ firstname=" + firstName + ",lastname=" + lastName + "]";
  }

  public int compareTo(Object obj) {
    Person emp = (Person) obj;
    int deptComp = firstName.compareTo(emp.getFirstName());

    return ((deptComp == 0) ? lastName.compareTo(emp.getLastName()) : deptComp);
  }

  public boolean equals(Object obj) {
    if (!(obj instanceof Person)) {
      return false;
    }
    Person emp = (Person) obj;
    return firstName.equals(emp.getFirstName()) && lastName.equals(emp.getLastName());
  }
}

class PersonComparator implements Comparator<Person> {
  public int compare(Person emp1, Person emp2) {
    int nameComp = emp1.getLastName().compareTo(emp2.getLastName());
    return ((nameComp == 0) ? emp1.getFirstName().compareTo(emp2.getFirstName()) : nameComp);
  }
}

public class Main {
  public static void main(String args[]) {
    ArrayList<Person> names = new ArrayList<Person>();
    names.add(new Person("E", "T"));
    names.add(new Person("A", "G"));
    names.add(new Person("B", "H"));
    names.add(new Person("C", "J"));

    Iterator iter1 = names.iterator();
    while (iter1.hasNext()) {
      System.out.println(iter1.next());
    }
    Collections.sort(names, new PersonComparator());
    Iterator iter2 = names.iterator();
    while (iter2.hasNext()) {
      System.out.println(iter2.next());
    }
  }
}
1
Shaunak

Vous avez juste besoin que votre classe hérite de Comparable .

puis implémentez la méthode compareTo comme vous le souhaitez.

1
Yochai Timmer

Vous devez écrire votre propre méthode compareTo () qui a le code Java nécessaire pour effectuer la comparaison.

Si nous voulions par exemple comparer deux domaines publics, campus, puis faculté, nous pourrions faire quelque chose comme:

int compareTo(GraduationCeremony gc)
{
    int c = this.campus.compareTo(gc.campus);

    if( c != 0 )
    {
        //sort by campus if we can
        return c;
    }
    else
    {
        //campus equal, so sort by faculty
        return this.faculty.compareTo(gc.faculty);
    }
}

Ceci est simplifié mais, espérons-le, vous donne une idée. Consultez les documents Comparable et Comparator pour plus d'informations.

0
mwd