【问题标题】:How do I sort a list by different parameters at different timed如何在不同的时间按不同的参数对列表进行排序
【发布时间】:2010-11-28 02:59:10
【问题描述】:

我有一个名为Person 的类具有多个属性,例如:

public class Person {
    private int id;
    private String name, address;
    // Many more properties.
}

很多Person 对象都存储在ArrayList<Person> 中。我想通过多个排序参数对这个列表进行排序,并且有时会有所不同。例如,我可能有一次想按name 升序排序,然后address 降序排序,另一次只想按id 降序排序。

而且我不想创建自己的排序方法(即,我想使用Collections.sort(personList, someComparator)。实现这一目标的最优雅的解决方案是什么?

【问题讨论】:

    标签: java sorting


    【解决方案1】:

    一种方法是创建一个Comparator,它将一个属性列表作为参数进行排序,如本例所示。

    public class Person {
        private int id;
        private String name, address;
    
        public static Comparator<Person> getComparator(SortParameter... sortParameters) {
            return new PersonComparator(sortParameters);
        }
    
        public enum SortParameter {
            ID_ASCENDING, ID_DESCENDING, NAME_ASCENDING,
            NAME_DESCENDING, ADDRESS_ASCENDING, ADDRESS_DESCENDING
        }
    
        private static class PersonComparator implements Comparator<Person> {
            private SortParameter[] parameters;
    
            private PersonComparator(SortParameter[] parameters) {
                this.parameters = parameters;
            }
    
            public int compare(Person o1, Person o2) {
                int comparison;
                for (SortParameter parameter : parameters) {
                    switch (parameter) {
                        case ID_ASCENDING:
                            comparison = o1.id - o2.id;
                            if (comparison != 0) return comparison;
                            break;
                        case ID_DESCENDING:
                            comparison = o2.id - o1.id;
                            if (comparison != 0) return comparison;
                            break;
                        case NAME_ASCENDING:
                            comparison = o1.name.compareTo(o2.name);
                            if (comparison != 0) return comparison;
                            break;
                        case NAME_DESCENDING:
                            comparison = o2.name.compareTo(o1.name);
                            if (comparison != 0) return comparison;
                            break;
                        case ADDRESS_ASCENDING:
                            comparison = o1.address.compareTo(o2.address);
                            if (comparison != 0) return comparison;
                            break;
                        case ADDRESS_DESCENDING:
                            comparison = o2.address.compareTo(o1.address);
                            if (comparison != 0) return comparison;
                            break;
                    }
                }
                return 0;
            }
        }
    }
    

    然后可以在代码中使用它,例如:

    cp = Person.getComparator(Person.SortParameter.ADDRESS_ASCENDING,
                              Person.SortParameter.NAME_DESCENDING);
    Collections.sort(personList, cp);
    

    【讨论】:

    • 是的。如果您希望您的代码非常通用,您的枚举可以仅指定要读取的属性(您可以使用反射来使用枚举名称获取属性),并且您可以使用第二个枚举指定其余部分:ASC 和 DESC,并且可能是第三个(NULL_FIRST 或 NULL_LAST)。
    【解决方案2】:

    比较器可让您轻松自然地做到这一点。您可以在 Person 类本身或与您的需要相关联的 Service 类中创建比较器的单个实例。
    示例,使用匿名内部类:

        public static final Comparator<Person> NAME_ASC_ADRESS_DESC
         = new Comparator<Person>() {
          public int compare(Person p1, Person p2) {
             int nameOrder = p1.getName().compareTo(p2.getName);
             if(nameOrder != 0) {
               return nameOrder;
             }
             return -1 * p1.getAdress().comparedTo(p2.getAdress());
             // I use explicit -1 to be clear that the order is reversed
          }
        };
    
        public static final Comparator<Person> ID_DESC
         = new Comparator<Person>() {
          public int compare(Person p1, Person p2) {
             return -1 * p1.getId().comparedTo(p2.getId());
             // I use explicit -1 to be clear that the order is reversed
          }
        };
        // and other comparator instances as needed... 
    

    如果您有很多,您还可以按您喜欢的方式构建比较器代码。例如,您可以:

    • 从另一个比较器继承,
    • 有一个 CompositeComparator 来聚合一些现有的比较器
    • 有一个 NullComparator 来处理 null 情况,然后委托给另一个比较器
    • 等等……

    【讨论】:

      【解决方案3】:

      一种方法是编写Comparators。这可能是一个库方法(我确信它存在于某个地方)。

      public static <T> Comparator<T> compose(
          final Comparator<? super T> primary,
          final Comparator<? super T> secondary
      ) {
          return new Comparator<T>() {
              public int compare(T a, T b) {
                  int result = primary.compare(a, b);
                  return result==0 ? secondary.compare(a, b) : result;
              }
              [...]
          };
      }
      

      用途:

      Collections.sort(people, compose(nameComparator, addressComparator));
      

      另外,请注意Collections.sort 是一个稳定的排序。如果性能不是绝对重要的,那么您可以先排序为次要顺序。

      Collections.sort(people, addressComparator);
      Collections.sort(people, nameComparator);
      

      【讨论】:

      • 聪明的方法,但是,它是否可以变得更通用,以便它包含可变数量的比较器,可能包括零?
      • compose(nameComparator, compose(addressComparator, idComparator)) 如果 Java 有扩展方法,那读起来会更好。
      【解决方案4】:

      我认为将排序器耦合到 Person 类,就像你的回答一样,不是一个好主意,因为它将比较(通常是业务驱动的)和模型对象耦合到彼此接近。 每次你想在排序器中更改/添加一些东西时,你都需要触摸 person 类,这通常是你不想做的。

      使用提供 Comparator 实例的 Service 或类似的东西,就像 KLE 提议的那样,听起来更加灵活和可扩展。

      【讨论】:

      • 对我而言,这会导致紧密耦合,因为不知何故,比较器持有者类必须知道 Person 类的详细数据结构(基本上要比较 Person 类的哪些字段)以及是否要更改某些内容在 Persons 字段中,这会导致在比较器类中跟踪相同的更改。我猜 Person 比较器应该是 Person 类的一部分。 blog.sanaulla.info/2008/06/26/…
      【解决方案5】:

      您可以为您可能想要排序的每个属性创建比较器,然后尝试“比较器链接”:-) 像这样:

      public class ChainedComparator<T> implements Comparator<T> {
          private List<Comparator<T>> simpleComparators; 
          public ChainedComparator(Comparator<T>... simpleComparators) {
              this.simpleComparators = Arrays.asList(simpleComparators);
          }
          public int compare(T o1, T o2) {
              for (Comparator<T> comparator : simpleComparators) {
                  int result = comparator.compare(o1, o2);
                  if (result != 0) {
                      return result;
                  }
              }
              return 0;
          }
      }
      

      【讨论】:

      • 你可能会在使用它时收到警告(尽管在 JDK7 中你应该能够抑制它)。
      • 我也喜欢这个。您能否提供一个示例,说明如何在给定的示例中使用它?
      • @runaros:使用 KLE 答案中的比较器:Collections.sort(/* Collection */ people, new ChainedComparator(NAME_ASC_ADRESS_DESC, ID_DESC));
      【解决方案6】:

      我认为您的枚举方法基本上是合理的,但是 switch 语句确实需要一种更加面向对象的方法。考虑:

      enum PersonComparator implements Comparator<Person> {
          ID_SORT {
              public int compare(Person o1, Person o2) {
                  return Integer.valueOf(o1.getId()).compareTo(o2.getId());
              }},
          NAME_SORT {
              public int compare(Person o1, Person o2) {
                  return o1.getFullName().compareTo(o2.getFullName());
              }};
      
          public static Comparator<Person> decending(final Comparator<Person> other) {
              return new Comparator<Person>() {
                  public int compare(Person o1, Person o2) {
                      return -1 * other.compare(o1, o2);
                  }
              };
          }
      
          public static Comparator<Person> getComparator(final PersonComparator... multipleOptions) {
              return new Comparator<Person>() {
                  public int compare(Person o1, Person o2) {
                      for (PersonComparator option : multipleOptions) {
                          int result = option.compare(o1, o2);
                          if (result != 0) {
                              return result;
                          }
                      }
                      return 0;
                  }
              };
          }
      }
      

      使用示例(使用静态导入)。

      public static void main(String[] args) {
          List<Person> list = null;
          Collections.sort(list, decending(getComparator(NAME_SORT, ID_SORT)));
      }
      

      【讨论】:

      • +1 智能使用枚举。我喜欢你对枚举、“降序”和“复合”的优雅组合。我猜是缺少空值处理,但很容易添加与“降序”相同的方式。
      • 许多很好的答案提供了思考的食物。由于没有答案作为明确的选择,我会接受这个,因为我喜欢优雅,但我敦促任何查看此答案的人也检查其他方法。
      • @TheLittleNaruto,如果 o2 更大,compare 方法返回负数,如果 o1 更大,则返回正数,如果它们相等,则返回零。乘以 -1 会反转结果,这是降序(与通常的升序相反)的想法,如果它们相等则将其保留为零。
      • 请注意,从 Java 8 开始,您可以使用 comparator.reversed() 进行降序,也可以使用 comparator1.thenComparing(comparator2) 链接比较器。
      • @JohnBaum,如果第一个比较器返回非零结果,则返回该结果并且不执行链的其余部分。
      【解决方案7】:

      我最近编写了一个比较器来对分隔字符串记录中的多个字段进行排序。它允许您定义分隔符、记录结构和排序规则(其中一些是特定于类型的)。您可以通过将 Person 记录转换为分隔字符串来使用它。

      所需信息以编程方式或通过 XML 文件植入 Comparator 本身。

      XML 由包内嵌的 XSD 文件验证。例如,下面是一个制表符分隔的记录布局,有四个字段(其中两个是可排序的):

      <?xml version="1.0" encoding="ISO-8859-1"?> 
      <row xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
      
          <delimiter>&#009;</delimiter>
      
          <column xsi:type="Decimal">
              <name>Column One</name>
          </column>
      
          <column xsi:type="Integer">
              <name>Column Two</name>
          </column>
      
          <column xsi:type="String">
              <name>Column Three</name>
              <sortOrder>2</sortOrder>
              <trim>true</trim>
              <caseSensitive>false</caseSensitive>        
              <stripAccents>true</stripAccents>
          </column>
      
          <column xsi:type="DateTime">
              <name>Column Four</name>
              <sortOrder>1</sortOrder>
              <ascending>true</ascending>
              <nullLowSortOrder>true</nullLowSortOrder>
              <trim>true</trim>
              <pattern>yyyy-MM-dd</pattern>
          </column>
      
      </row>
      

      然后你可以像这样在java中使用它:

      Comparator<String> comparator = new RowComparator(
                    new XMLStructureReader(new File("layout.xml")));
      

      图书馆可以在这里找到:

      http://sourceforge.net/projects/multicolumnrowcomparator/

      【讨论】:

        【解决方案8】:

        假设有一个类Coordinate,并且必须根据X 坐标和Y 坐标以两种方式对其进行排序。它需要两个不同的比较器。下面是示例

        class Coordinate
        {
        
            int x,y;
        
            public Coordinate(int x, int y) {
                this.x = x;
                this.y = y;
            }
        
            static Comparator<Coordinate> getCoordinateXComparator() {
                return new Comparator<Coordinate>() {
        
                    @Override
                    public int compare(Coordinate Coordinate1, Coordinate Coordinate2) {
                        if(Coordinate1.x < Coordinate2.x)
                            return 1;
                        else
                            return 0;
                    }
                    // compare using Coordinate x
                };
            }
        
            static Comparator<Coordinate> getCoordinateYComparator() {
                return new Comparator<Coordinate>() {
        
                    @Override
                    public int compare(Coordinate Coordinate1, Coordinate Coordinate2) {
                        if(Coordinate1.y < Coordinate2.y)
                            return 1;
                        else
                            return 0;
                    }
                    // compare using Coordinate y
                };
            }
        }
        

        【讨论】:

          【解决方案9】:

          我的方法是建立在 Yishai 的基础上的。主要的差距是没有办法先对一个属性进行升序排序,然后再对另一个属性进行降序排序。这不能通过枚举来完成。为此,我使用了课程。因为 SortOrder 强烈依赖于我喜欢将其实现为 person 的内部类的类型。

          具有内部类“SortOrder”的类“Person”:

          import java.util.Comparator;
          
          public class Person {
              private int id;
              private String firstName; 
              private String secondName;
          
              public Person(int id, String firstName, String secondName) {
                  this.id = id;
                  this.firstName = firstName;
                  this.secondName = secondName;   
              }
          
              public abstract static class SortOrder implements Comparator<Person> {
                  public static SortOrder PERSON_ID = new SortOrder() {
                      public int compare(Person p1, Person p2) {
                          return Integer.valueOf(p1.getId()).compareTo(p2.getId());
                      }
                  };
                  public static SortOrder PERSON_FIRST_NAME = new SortOrder() {
                      public int compare(Person p1, Person p2) {
                          return p1.getFirstName().compareTo(p2.getFirstName());
                      }
                  };
                  public static SortOrder PERSON_SECOND_NAME = new SortOrder() {
                      public int compare(Person p1, Person p2) {
                          return p1.getSecondName().compareTo(p2.getSecondName());
                      }
                  };
          
                  public static SortOrder invertOrder(final SortOrder toInvert) {
                      return new SortOrder() {
                          public int compare(Person p1, Person p2) {
                              return -1 * toInvert.compare(p1, p2);
                          }
                      };
                  }
          
                  public static Comparator<Person> combineSortOrders(final SortOrder... multipleSortOrders) {
                      return new Comparator<Person>() {
                          public int compare(Person p1, Person p2) {
                              for (SortOrder personComparator: multipleSortOrders) {
                                  int result = personComparator.compare(p1, p2);
                                  if (result != 0) {
                                      return result;
                                  }
                              }
                              return 0;
                          }
                      };
                  }
              }
          
              public int getId() {
                  return id;
              }
          
              public String getFirstName() {
                  return firstName;
              }
          
              public String getSecondName() {
                  return secondName;
              }
          
              @Override
              public String toString() {
                  StringBuilder result = new StringBuilder();
          
                  result.append("Person with id: ");
                  result.append(id);
                  result.append(" and firstName: ");
                  result.append(firstName);
                  result.append(" and secondName: ");
                  result.append(secondName);
                  result.append(".");
          
                  return result.toString();
              }
          }
          

          使用类 Person 及其 SortOrder 的示例:

          import static multiplesortorder.Person.SortOrder.*;
          
          import java.util.ArrayList;
          import java.util.Arrays;
          import java.util.Collections;
          import java.util.List;
          
          import multiplesortorder.Person;
          
          public class Application {
          
              public static void main(String[] args) {
                  List<Person> listPersons = new ArrayList<Person>(Arrays.asList(
                           new Person(0, "...", "..."),
                           new Person(1, "...", "...")
                       ));
          
                   Collections.sort(listPersons, combineSortOrders(PERSON_FIRST_NAME, invertOrder(PERSON_ID)));
          
                   for (Person p: listPersons) {
                       System.out.println(p.toString());
                   }
              }
          }
          

          oRUMOo

          【讨论】:

          • 这种比较器链接的复杂性是多少?每次我们链接比较器时,我们本质上是在排序吗?那么我们对每个比较器进行 n*log(n) 操作?
          猜你喜欢
          • 2010-11-28
          • 2014-03-23
          • 2021-12-26
          • 2017-09-09
          • 2011-03-16
          • 2016-04-17
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多