How to sort by two fields in Java?

后端 未结 16 1955
离开以前
离开以前 2020-11-22 08:40

I have array of objects person (int age; String name;).

How can I sort this array alphabetically by name and then by age?

Which algorithm would

相关标签:
16条回答
  • 2020-11-22 08:57

    You need to implement your own Comparator, and then use it: for example

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

    Your Comparator could look a bit like this:

    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.

    0 讨论(0)
  • 2020-11-22 08:59

    You can use Java 8 Lambda approach to achieve this. Like this:

    persons.sort(Comparator.comparing(Person::getName).thenComparing(Person::getAge));
    
    0 讨论(0)
  • 2020-11-22 09:01

    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.

    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:

    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

    0 讨论(0)
  • 2020-11-22 09:04

    Create as many comparators as necessary. After, call the method "thenComparing" for each order category. It's a way of doing by 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)

    0 讨论(0)
  • 2020-11-22 09:04

    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;
    }
    }
    
    0 讨论(0)
  • 2020-11-22 09:09

    For those able to use the Java 8 streaming API, there is a neater approach that is well documented here: Lambdas and sorting

    I was looking for the equivalent of the C# LINQ:

    .ThenBy(...)
    

    I found the mechanism in Java 8 on the Comparator:

    .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.

    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);
        }
    }
    
    0 讨论(0)
提交回复
热议问题