【问题标题】:How to sort by two fields in Java?如何按Java中的两个字段排序?
【发布时间】:2011-06-15 21:23:18
【问题描述】:

我有对象数组person (int age; String name;)

如何按名称和年龄的字母顺序对这个数组进行排序?

您会为此使用哪种算法?

【问题讨论】:

    标签: java sorting


    【解决方案1】:

    您可以使用Collections.sort,如下:

    private static void order(List<Person> persons) {
    
        Collections.sort(persons, new Comparator() {
    
            public int compare(Object o1, Object o2) {
    
                String x1 = ((Person) o1).getName();
                String x2 = ((Person) o2).getName();
                int sComp = x1.compareTo(x2);
    
                if (sComp != 0) {
                   return sComp;
                } 
    
                Integer x1 = ((Person) o1).getAge();
                Integer x2 = ((Person) o2).getAge();
                return x1.compareTo(x2);
        }});
    }
    

    List&lt;Persons&gt; 现在按名称排序,然后按年龄排序。

    String.compareTo“按字典顺序比较两个字符串”——来自docs

    Collections.sort 是原生 Collections 库中的静态方法。它进行实际排序,您只需要提供一个 Comparator 来定义列表中的两个元素应如何进行比较:这是通过提供您自己的 compare 方法实现来实现的。

    【讨论】:

    • 您还可以向Comparator 添加类型参数以避免强制转换输入。
    • @Ralph:我已经修改了我的答案,并添加了一个简短的描述。
    • 既然 OP 已经有了自己的对象类,那么实现Comparable 会更有意义。查看@berry120 的答案
    • 迷你代码审查:else 子句是多余的,因为第一个 return 充当保护子句。不过很好的答案,对我来说是一种享受。
    • 由于这个问题/答案仍然有关联,请注意,使用 Java SE 8,这变得更加简单。如果有吸气剂,你可以写Comparator&lt;Person&gt; comparator = Comparator.comparing(Person::getName).thenComparingInt(Person::getAge);
    【解决方案2】:

    对于那些能够使用 Java 8 流 API 的人,这里有一个更简洁的方法,有详细的文档: Lambdas and sorting

    我正在寻找 C# LINQ 的等效项:

    .ThenBy(...)
    

    我在 Comparator 上找到了 Java 8 中的机制:

    .thenComparing(...)
    

    所以这里是演示算法的 sn-p。

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

    查看上面的链接以了解更简洁的方式以及关于 Java 的类型推断如何使其与 LINQ 相比更笨拙的定义。

    这是完整的单元测试供参考:

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

    【讨论】:

    • 这种比较器链接的复杂性是多少?每次我们链接比较器时,我们本质上是在排序吗?那么我们对每个比较器做一次 NlogN 操作?
    • 如果有getter可以写Comparator&lt;Person&gt; comparator = Comparator.comparing(Person::getName).thenComparing(Person::getAge);
    • 使用thenComparingInt 表示年龄(整数)
    • labda '->' 的语法对我不起作用。 Person::getLastName 可以。
    • 创建比较器后,需要创建一个流,用比较器排序,然后收集?你能改用Collections.sort(people, comparator);吗?
    【解决方案3】:

    使用 Java 8 Streams 方法...

    //Creates and sorts a stream (does not sort the original list)       
    persons.stream().sorted(Comparator.comparing(Person::getName).thenComparing(Person::getAge));
    

    还有 Java 8 Lambda 方法...

    //Sorts the original list Lambda style
    persons.sort((p1, p2) -> {
            if (p1.getName().compareTo(p2.getName()) == 0) {
                return p1.getAge().compareTo(p2.getAge());
            } else {
                return p1.getName().compareTo(p2.getName());
            } 
        });
    

    最后...

    //This is similar SYNTAX to the Streams above, but it sorts the original list!!
    persons.sort(Comparator.comparing(Person::getName).thenComparing(Person::getAge));
    

    【讨论】:

      【解决方案4】:

      你需要实现自己的Comparator,然后使用它:例如

      Arrays.sort(persons, new PersonComparator());
      

      您的比较器可能看起来像这样:

      public class PersonComparator implements Comparator<? extends Person> {
      
        public int compare(Person p1, Person p2) {
           int nameCompare = p1.name.compareToIgnoreCase(p2.name);
           if (nameCompare != 0) {
              return nameCompare;
           } else {
             return Integer.valueOf(p1.age).compareTo(Integer.valueOf(p2.age));
           }
        }
      }
      

      比较器首先比较姓名,如果不相等则返回比较结果,否则在比较两个人的年龄时返回比较结果。

      此代码只是草稿:因为类是不可变的,您可以考虑构建它的单例,而不是为每个排序创建一个新实例。

      【讨论】:

        【解决方案5】:

        让你的 person 类实现 Comparable&lt;Person&gt; 然后实现 compareTo 方法,例如:

        public int compareTo(Person o) {
            int result = name.compareToIgnoreCase(o.name);
            if(result==0) {
                return Integer.valueOf(age).compareTo(o.age);
            }
            else {
                return result;
            }
        }
        

        这将首先按名称(不区分大小写)然后按年龄排序。然后,您可以在 Person 对象的集合或数组上运行 Arrays.sort()Collections.sort()

        【讨论】:

        • 我通常更喜欢这个而不是制作一个比较器,因为正如 berry120 所说,您可以使用内置方法进行排序,而不是需要始终使用您的自定义比较器。
        【解决方案6】:

        Guava 的ComparisonChain 提供了一种简洁的方法。参考这个link

        用于执行链式比较语句的实用程序。例如:

           public int compareTo(Foo that) {
             return ComparisonChain.start()
                 .compare(this.aString, that.aString)
                 .compare(this.anInt, that.anInt)
                 .compare(this.anEnum, that.anEnum, Ordering.natural().nullsLast())
                 .result();
           }
        

        【讨论】:

          【解决方案7】:

          你可以这样做:

          List<User> users = Lists.newArrayList(
            new User("Pedro", 12), 
            new User("Maria", 10), 
            new User("Rafael",12)
          );
          
          users.sort(
            Comparator.comparing(User::getName).thenComparing(User::getAge)
          );
          

          【讨论】:

            【解决方案8】:

            在使用 Guava 的 ComparisonChain 时我会小心,因为它会为每个被比较的元素创建一个实例,因此您将查看 N x Log N 比较链的创建只是为了比较您是否正在排序,或者 N如果您正在迭代并检查是否相等,则为实例。

            如果可能的话,我会使用最新的 Java 8 API 或 Guava 的 Ordering API 创建一个静态的 Comparator,它允许您这样做,这里是 Java 8 的示例:

            import java.util.Comparator;
            import static java.util.Comparator.naturalOrder;
            import static java.util.Comparator.nullsLast;
            
            private static final Comparator<Person> COMPARATOR = Comparator
              .comparing(Person::getName, nullsLast(naturalOrder()))
              .thenComparingInt(Person::getAge);
            
            @Override
            public int compareTo(@NotNull Person other) {
              return COMPARATOR.compare(this, other);
            }
            

            这里是如何使用 Guava 的 Ordering API:https://github.com/google/guava/wiki/OrderingExplained

            【讨论】:

            • "...为每个被比较的元素创建一个实例..." - 这是不正确的。至少在现代版本的 Guava 中,调用 compare 方法不会创建任何内容,但会根据比较结果返回单例实例 LESSGREATERACTIVE 之一。这是一种高度优化的方法,不会增加任何内存或性能开销。
            • 是的;我现在刚刚看了源代码,我明白你的意思了,但为了依赖,我更倾向于使用新的 Java 8 比较 API。
            【解决方案9】:

            使用Comparator,然后将对象放入Collection,然后Collections.sort();

            class Person {
            
                String fname;
                String lname;
                int age;
            
                public Person() {
                }
            
                public int getAge() {
                    return age;
                }
            
                public void setAge(int age) {
                    this.age = age;
                }
            
                public String getFname() {
                    return fname;
                }
            
                public void setFname(String fname) {
                    this.fname = fname;
                }
            
                public String getLname() {
                    return lname;
                }
            
                public void setLname(String lname) {
                    this.lname = lname;
                }
            
                public Person(String fname, String lname, int age) {
                    this.fname = fname;
                    this.lname = lname;
                    this.age = age;
                }
            
                @Override
                public String toString() {
                    return fname + "," + lname + "," + age;
                }
            }
            
            public class Main{
            
                public static void main(String[] args) {
                    List<Person> persons = new java.util.ArrayList<Person>();
                    persons.add(new Person("abc3", "def3", 10));
                    persons.add(new Person("abc2", "def2", 32));
                    persons.add(new Person("abc1", "def1", 65));
                    persons.add(new Person("abc4", "def4", 10));
                    System.out.println(persons);
                    Collections.sort(persons, new Comparator<Person>() {
            
                        @Override
                        public int compare(Person t, Person t1) {
                            return t.getAge() - t1.getAge();
                        }
                    });
                    System.out.println(persons);
            
                }
            }
            

            【讨论】:

              【解决方案10】:

              根据需要创建尽可能多的比较器。之后,为每个订单类别调用方法“thenComparing”。这是 Streams 的一种做法。见:

              //Sort by first and last name
              System.out.println("\n2.Sort list of person objects by firstName then "
                                                      + "by lastName then by age");
              Comparator<Person> sortByFirstName 
                                          = (p, o) -> p.firstName.compareToIgnoreCase(o.firstName);
              Comparator<Person> sortByLastName 
                                          = (p, o) -> p.lastName.compareToIgnoreCase(o.lastName);
              Comparator<Person> sortByAge 
                                          = (p, o) -> Integer.compare(p.age,o.age);
              
              //Sort by first Name then Sort by last name then sort by age
              personList.stream().sorted(
                  sortByFirstName
                      .thenComparing(sortByLastName)
                      .thenComparing(sortByAge)
                   ).forEach(person->
                      System.out.println(person));        
              

              看:Sort user defined object on multiple fields – Comparator (lambda stream)

              【讨论】:

                【解决方案11】:

                或者您可以利用Collections.sort()(或Arrays.sort())是稳定的这一事实(它不会对相等的元素重新排序)并使用Comparator先按年龄排序,然后再使用另一个按年龄排序名字。

                在这种特定情况下,这不是一个好主意,但如果您必须能够在运行时更改排序顺序,它可能会很有用。

                【讨论】:

                  【解决方案12】:

                  您可以使用通用串行比较器按多个字段对集合进行排序。

                  import org.apache.commons.lang3.reflect.FieldUtils;
                  import java.util.Arrays;
                  import java.util.Comparator;
                  import java.util.List;
                  
                  /**
                  * @author MaheshRPM
                  */
                  public class SerialComparator<T> implements Comparator<T> {
                  List<String> sortingFields;
                  
                  public SerialComparator(List<String> sortingFields) {
                      this.sortingFields = sortingFields;
                  }
                  
                  public SerialComparator(String... sortingFields) {
                      this.sortingFields = Arrays.asList(sortingFields);
                  }
                  
                  @Override
                  public int compare(T o1, T o2) {
                      int result = 0;
                      try {
                          for (String sortingField : sortingFields) {
                              if (result == 0) {
                                  Object value1 = FieldUtils.readField(o1, sortingField, true);
                                  Object value2 = FieldUtils.readField(o2, sortingField, true);
                                  if (value1 instanceof Comparable && value2 instanceof Comparable) {
                                      Comparable comparable1 = (Comparable) value1;
                                      Comparable comparable2 = (Comparable) value2;
                                      result = comparable1.compareTo(comparable2);
                                  } else {
                                      throw new RuntimeException("Cannot compare non Comparable fields. " + value1.getClass()
                                              .getName() + " must implement Comparable<" + value1.getClass().getName() + ">");
                                  }
                              } else {
                                  break;
                              }
                          }
                      } catch (IllegalAccessException e) {
                          throw new RuntimeException(e);
                      }
                      return result;
                  }
                  }
                  

                  【讨论】:

                    【解决方案13】:
                    Arrays.sort(persons, new PersonComparator());
                    
                    
                    
                    import java.util.Comparator;
                    
                    public class PersonComparator implements Comparator<? extends Person> {
                    
                        @Override
                        public int compare(Person o1, Person o2) {
                            if(null == o1 || null == o2  || null == o1.getName() || null== o2.getName() ){
                                throw new NullPointerException();
                            }else{
                                int nameComparisonResult = o1.getName().compareTo(o2.getName());
                                if(0 == nameComparisonResult){
                                    return o1.getAge()-o2.getAge();
                                }else{
                                    return nameComparisonResult;
                                }
                            }
                        }
                    }
                    
                    
                    class Person{
                        int age; String name;
                    
                        public int getAge() {
                            return age;
                        }
                    
                        public void setAge(int age) {
                            this.age = age;
                        }
                    
                        public String getName() {
                            return name;
                        }
                    
                        public void setName(String name) {
                            this.name = name;
                        }
                    
                    }
                    

                    更新版本:

                    public class PersonComparator implements Comparator<? extends Person> {
                    
                       @Override
                       public int compare(Person o1, Person o2) {
                    
                          int nameComparisonResult = o1.getName().compareToIgnoreCase(o2.getName());
                          return 0 == nameComparisonResult?o1.getAge()-o2.getAge():nameComparisonResult;
                    
                       }
                     }
                    

                    【讨论】:

                    • nullpointer 异常处理很好,并明确指出它不适用于 null,但无论如何都会引发
                    • 你是绝对正确的。我最近过去常常检查一些值以从一个地方复制到另一个地方,现在我到处都这样做。
                    【解决方案14】:

                    对于这样的课程Book

                    package books;
                    
                    public class Book {
                    
                        private Integer id;
                        private Integer number;
                        private String name;
                    
                        public Integer getId() {
                            return id;
                        }
                    
                        public void setId(Integer id) {
                            this.id = id;
                        }
                    
                        public Integer getNumber() {
                            return number;
                        }
                    
                        public void setNumber(Integer number) {
                            this.number = number;
                        }
                    
                        public String getName() {
                            return name;
                        }
                    
                        public void setName(String name) {
                            this.name = name;
                        }
                    
                        @Override
                        public String toString() {
                            return "book{" +
                                    "id=" + id +
                                    ", number=" + number +
                                    ", name='" + name + '\'' + '\n' +
                                    '}';
                        }
                    }
                    

                    用模拟对象对主类进行排序

                    package books;
                    
                    import java.util.ArrayList;
                    import java.util.Collections;
                    import java.util.Comparator;
                    import java.util.List;
                    
                    
                    public class Main {
                    
                        public static void main(String[] args) {
                            System.out.println("Hello World!");
                    
                            Book b = new Book();
                    
                            Book c = new Book();
                    
                            Book d = new Book();
                    
                            Book e = new Book();
                    
                            Book f = new Book();
                    
                            Book g = new Book();
                            Book g1 = new Book();
                            Book g2 = new Book();
                            Book g3 = new Book();
                            Book g4 = new Book();
                    
                    
                    
                    
                            b.setId(1);
                            b.setNumber(12);
                            b.setName("gk");
                    
                            c.setId(2);
                            c.setNumber(12);
                            c.setName("gk");
                    
                            d.setId(2);
                            d.setNumber(13);
                            d.setName("maths");
                    
                            e.setId(3);
                            e.setNumber(3);
                            e.setName("geometry");
                    
                            f.setId(3);
                            f.setNumber(34);
                            b.setName("gk");
                    
                            g.setId(3);
                            g.setNumber(11);
                            g.setName("gk");
                    
                            g1.setId(3);
                            g1.setNumber(88);
                            g1.setName("gk");
                            g2.setId(3);
                            g2.setNumber(91);
                            g2.setName("gk");
                            g3.setId(3);
                            g3.setNumber(101);
                            g3.setName("gk");
                            g4.setId(3);
                            g4.setNumber(4);
                            g4.setName("gk");
                    
                    
                    
                    
                    
                            List<Book> allBooks = new ArrayList<Book>();
                    
                            allBooks.add(b);
                            allBooks.add(c);
                            allBooks.add(d);
                            allBooks.add(e);
                            allBooks.add(f);
                            allBooks.add(g);
                            allBooks.add(g1);
                            allBooks.add(g2);
                            allBooks.add(g3);
                            allBooks.add(g4);
                    
                    
                    
                            System.out.println(allBooks.size());
                    
                    
                            Collections.sort(allBooks, new Comparator<Book>() {
                    
                                @Override
                                public int compare(Book t, Book t1) {
                                    int a =  t.getId()- t1.getId();
                    
                                    if(a == 0){
                                        int a1 = t.getNumber() - t1.getNumber();
                                        return a1;
                                    }
                                    else
                                        return a;
                                }
                            });
                            System.out.println(allBooks);
                    
                        }
                    
                    
                       }
                    

                    【讨论】:

                      【解决方案15】:

                      我不确定在这种情况下在 Person 类中编写比较器是否难看。是这样的吗:

                      public class Person implements Comparable <Person> {
                      
                          private String lastName;
                          private String firstName;
                          private int age;
                      
                          public Person(String firstName, String lastName, int BirthDay) {
                              this.firstName = firstName;
                              this.lastName = lastName;
                              this.age = BirthDay;
                          }
                      
                          public int getAge() {
                              return age;
                          }
                      
                          public String getFirstName() {
                              return firstName;
                          }
                      
                          public String getLastName() {
                              return lastName;
                          }
                      
                          @Override
                          public int compareTo(Person o) {
                              // default compareTo
                          }
                      
                          @Override
                          public String toString() {
                              return firstName + " " + lastName + " " + age + "";
                          }
                      
                          public static class firstNameComperator implements Comparator<Person> {
                              @Override
                              public int compare(Person o1, Person o2) {
                                  return o1.firstName.compareTo(o2.firstName);
                              }
                          }
                      
                          public static class lastNameComperator implements Comparator<Person> {
                              @Override
                              public int compare(Person o1, Person o2) {
                                  return o1.lastName.compareTo(o2.lastName);
                              }
                          }
                      
                          public static class ageComperator implements Comparator<Person> {
                              @Override
                              public int compare(Person o1, Person o2) {
                                  return o1.age - o2.age;
                              }
                          }
                      }
                      public class Test {
                          private static void print() {
                             ArrayList<Person> list = new ArrayList();
                              list.add(new Person("Diana", "Agron", 31));
                              list.add(new Person("Kay", "Panabaker", 27));
                              list.add(new Person("Lucy", "Hale", 28));
                              list.add(new Person("Ashley", "Benson", 28));
                              list.add(new Person("Megan", "Park", 31));
                              list.add(new Person("Lucas", "Till", 27));
                              list.add(new Person("Nicholas", "Hoult", 28));
                              list.add(new Person("Aly", "Michalka", 28));
                              list.add(new Person("Adam", "Brody", 38));
                              list.add(new Person("Chris", "Pine", 37));
                              Collections.sort(list, new Person.lastNameComperator());
                              Iterator<Person> it = list.iterator();
                              while(it.hasNext()) 
                                  System.out.println(it.next().toString()); 
                           }  
                      }    
                      

                      【讨论】:

                      • 确实不推荐将类绑定到特定比较器的模式。
                      猜你喜欢
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 2021-08-18
                      • 2019-03-19
                      • 1970-01-01
                      • 2013-04-26
                      相关资源
                      最近更新 更多