简体   繁体   English

如何按Java中的两个字段排序?

[英]How to sort by two fields in Java?

I have array of objects person (int age; String name;) .我有对象数组person (int age; String name;)

How can I sort this array alphabetically by name and then by age?如何按名称和年龄的字母顺序对这个数组进行排序?

Which algorithm would you use for this ?您会为此使用哪种算法?

You can use Collections.sort as follows:您可以使用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<Persons> is now sorted by name, then by age. List<Persons>现在按名称排序,然后按年龄排序。

String.compareTo "Compares two strings lexicographically" - from the docs . String.compareTo “按字典顺序比较两个字符串” - 来自docs

Collections.sort is a static method in the native Collections library. Collections.sort是原生 Collections 库中的静态方法。 It does the actual sorting, you just need to provide a Comparator which defines how two elements in your list should be compared: this is achieved by providing your own implementation of the compare method.它进行实际的排序,您只需要提供一个 Comparator 来定义应该如何比较列表中的两个元素:这是通过提供您自己的compare方法实现来实现的。

For those able to use the Java 8 streaming API, there is a neater approach that is well documented here: Lambdas and sorting对于那些能够使用 Java 8 流 API 的人来说,这里有一个更简洁的方法有很好的记录: Lambdas and sort

I was looking for the equivalent of the C# LINQ:我正在寻找相当于 C# LINQ 的东西:

.ThenBy(...)

I found the mechanism in Java 8 on the Comparator:我在 Comparator 上找到了 Java 8 中的机制:

.thenComparing(...)

So here is the snippet that demonstrates the algorithm.所以这里是演示算法的片段。

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

Check out the link above for a neater way and an explanation about how Java's type inference makes it a bit more clunky to define compared to LINQ.查看上面的链接以获得更简洁的方式以及关于 Java 的类型推断如何使其与 LINQ 相比更笨拙的定义的解释。

Here is the full unit test for reference:这是完整的单元测试供参考:

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

Using the Java 8 Streams approach...使用 Java 8 Streams 方法...

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

And the Java 8 Lambda approach... 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());
        } 
    });

Lastly...最后...

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

You can use Java 8 Lambda approach to achieve this.您可以使用 Java 8 Lambda 方法来实现这一点。 Like this:像这样:

persons.sort(Comparator.comparing(Person::getName).thenComparing(Person::getAge));

You need to implement your own Comparator , and then use it: for example您需要实现自己的Comparator ,然后使用它:例如

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

Your Comparator could look a bit like this:您的 Comparator 可能看起来像这样:

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

The comparator first compares the names, if they are not equals it returns the result from comparing them, else it returns the compare result when comparing the ages of both persons.比较器首先比较姓名,如果不相等则返回比较结果,否则在比较两个人的年龄时返回比较结果。

This code is only a draft: because the class is immutable you could think of building an singleton of it, instead creating a new instance for each sorting.这段代码只是一个草稿:因为这个类是不可变的,你可以考虑构建一个单例,而不是为每个排序创建一个新实例。

Have your person class implement Comparable<Person> and then implement the compareTo method, for instance:让你的 person 类实现Comparable<Person>然后实现 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;
    }
}

That will sort first by name (case insensitively) and then by age.这将首先按名称(不区分大小写)排序,然后按年龄排序。 You can then run Arrays.sort() or Collections.sort() on the collection or array of Person objects.然后,您可以在 Person 对象的集合或数组上运行Arrays.sort()Collections.sort()

Guava's ComparisonChain provides a clean way of doing it. Guava 的ComparisonChain链提供了一种简洁的方式。 Refer to this link .请参阅此链接

A utility for performing a chained comparison statement.用于执行链式比较语句的实用程序。 For example:例如:

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

I would be careful when using Guava's ComparisonChain because it creates an instance of it per element been compared so you would be looking at a creation of N x Log N comparison chains just to compare if you are sorting, or N instances if you are iterating and checking for equality.在使用 Guava 的ComparisonChain时我会小心,因为它会为每个被比较的元素创建一个实例,因此您将查看创建N x Log N比较链只是为了比较您是否正在排序,或者N个实例如果您正在迭代和检查是否相等。

I would instead create a static Comparator using the newest Java 8 API if possible or Guava's Ordering API which allows you to do that, here is an example with Java 8:如果可能的话,我会使用最新的 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);
}

Here is how to use the Guava's Ordering API: https://github.com/google/guava/wiki/OrderingExplained以下是如何使用 Guava 的Ordering API: https ://github.com/google/guava/wiki/OrderingExplained

You can do like this:你可以这样做:

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

Create as many comparators as necessary.根据需要创建尽可能多的比较器。 After, call the method "thenComparing" for each order category.之后,为每个订单类别调用方法“thenComparing”。 It's a way of doing by Streams.这是 Streams 的一种做法。 See:看:

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

Look: Sort user defined object on multiple fields – Comparator (lambda stream)看: 在多个字段上对用户定义的对象进行排序 - 比较器(lambda 流)

Use Comparator and then put objects into Collection , then Collections.sort();使用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);

    }
}

You can use generic serial Comparator to sort collections by multiple fields.您可以使用通用串行比较器按多个字段对集合进行排序。

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

Or you can exploit the fact that Collections.sort() (or Arrays.sort() ) is stable (it doesn't reorder elements that are equal) and use a Comparator to sort by age first and then another one to sort by name.或者您可以利用Collections.sort() (或Arrays.sort() )是稳定的事实(它不会重新排序相等的元素)并使用Comparator先按年龄排序,然后再使用另一个按名称排序.

In this specific case this isn't a very good idea but if you have to be able to change the sort order in runtime, it might be useful.在这种特定情况下,这不是一个好主意,但如果您必须能够在运行时更改排序顺序,它可能会很有用。

For a class Book like this:对于这样的课程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' +
                '}';
    }
}

sorting main class with mock objects使用模拟对象对主类进行排序

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

    }


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

}

Updated version:更新后的版本:

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;

   }
 }

I'm not sure if it's ugly to write the compartor inside the Person class in this case.我不确定在这种情况下在 Person 类中编写比较器是否难看。 Did it like this:是这样的吗:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM