按多个字段对对象列表进行排序

45

我有一个Java对象列表,我想根据多个字段进行排序。

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

是否可以使用 ComparatorComparable 接口根据多个字段对列表进行排序?我看到的所有示例都只按一个字段排序。换句话说,可以按“校区”或“学院”或“建筑物”排序。我想按“校区”,然后按“学院”,最后按“建筑物”排序(就像在 SQL 中一样:ORDER BY campus, faculty, building

我认为这个问题以前被问过,但我不理解已接受的答案。有人能扩展或说明这个答案吗?


1
那个问题的第二个答案是一个很好的例证。 - Oliver Charlesworth
2
@sim,那你为什么不花时间去理解呢,而是在寻找代码? - Moonbeam
1
@Moonbeam,我的问题文本表明我已经研究了集合和排序,并且我已经阅读了其他类似的stackoverflow问题。你认为我只是在钓代码吗?下次,请不要无视Wheaton法则。 - sim
1
@Moonbeam,有时候你需要看代码才能理解一个概念。当然像“我的树形视图闪烁得很厉害!”“试试这个”“谢谢!”这样的回答并不能帮助任何人学习,但这就是为什么这里是StackOverflow而不是某个论坛。下课后来找我。 - MoSlo
有人能够扩展或说明这个答案吗?不行,除非你明确地说出你对这个问题及其答案的哪些部分不理解。 - Raedwald
显示剩余2条评论
6个回答

76

您的比较器应该是这样的:

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;
    }
}

基本上,每当已比较的属性相等时(== 0),它就会继续比较您类的每个后续属性。


谢谢。你的解释让我恍然大悟。现在我对compare()方法的使用有了比之前更清晰的理解。 - sim
1
不要忘记进行空值检查。如果o1为空,行'int value1 = o1.campus.compareTo(o2.campus);'将抛出NullPointerException异常。 - Mark W

43

是的,你绝对可以做到这一点。例如:

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());
    }
}

基本上你的意思是,“如果我可以通过看校园就知道哪个先来(在他们来自不同的校区之前,校区是最重要的字段),那么我就返回那个结果。否则,我将继续比较学院。同样地,如果这已经足以把它们分开了,那么就停止。否则,(如果两个人的校区和学院都相同)就用通过建筑物比较它们的结果。”


5
易读性。这应该是正确的答案! - BBaker

6
如果您事先知道要使用哪些字段进行比较,那么其他人给出了正确的答案。
如果您在编译时不知道要应用哪些标准,您可能会对对集合进行排序感兴趣。 想象一下,您有一个处理城市的程序:


    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());
        }
    }

你可以在程序中通过用户请求推测出字段名并替换硬编码的字段名。
在这个例子中,City.CityMultiComparator<City>City类的静态嵌套类,实现了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;
        }
    }

您可能希望增加精确度,为每个字段指定升序或降序排序。我想解决方案是将 Field 对象替换为一个类的对象,您可以称其为 SortedField,其中包含一个 Field 对象,再加上另一个表示 升序降序 的字段。


我正在寻找的最佳答案 +10 - ThinkTank

2

Hope this Helps:

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

你只需要让你的类继承Comparable接口。

然后以你喜欢的方式实现compareTo方法。


0

你需要编写自己的compareTo()方法,其中包含执行比较所需的Java代码。

例如,如果我们想要比较两个公共字段campus和faculty,我们可以这样做:

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);
    }
}

这只是简化版,但希望能给您一个想法。请参阅Comparable和Comparator文档以获取更多信息。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接