Как отсортировать по двум полям в Java?

171

У меня есть массив объектов person (int age; String name;).

Как я могу отсортировать этот массив по алфавиту по имени, а затем по возрасту?

Какой алгоритм вы бы использовали для этого?

Дамир
источник

Ответы:

221

Вы можете использовать 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> теперь сортируется по имени, затем по возрасту.

String.compareTo«Сравнивает две строки лексикографически» - из документов .

Collections.sortстатический метод в родной библиотеке коллекций Он выполняет фактическую сортировку, вам просто нужно предоставить Comparator, который определяет, как должны сравниваться два элемента в вашем списке: это достигается путем обеспечения собственной реализации compareметода.

Ричард Х
источник
10
Вы также можете добавить параметр типа, Comparatorчтобы избежать приведения входных данных.
biziclop
@Ralph: я исправил свой ответ и добавил краткое описание.
Ричард Х
Поскольку у OP уже есть свой собственный объектный класс, было бы разумнее реализовать его Comparable. Смотрите ответ @ berry120
Zulaxia
1
Обзор мини-кода: предложение else является избыточным, поскольку первое возвращение действует как защитное предложение. Тем не менее, отличный ответ сработал для меня.
Том Салеба
30
Поскольку этот вопрос / ответ все еще связан, обратите внимание, что с Java SE 8 это стало намного проще. Если есть добытчики, вы можете написатьComparator<Person> comparator = Comparator.comparing(Person::getName).thenComparingInt(Person::getAge);
Puce
143

Для тех, кто в состоянии использовать потоковый API Java 8, есть более аккуратный подход, который хорошо документирован здесь: лямбды и сортировка

Я искал эквивалент C # LINQ:

.ThenBy(...)

Я нашел механизм в Java 8 на Comparator:

.thenComparing(...)

Итак, вот фрагмент, который демонстрирует алгоритм.

    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 для каждого компаратора?
Джон Баум
17
Если есть добытчики, вы можете написатьComparator<Person> comparator = Comparator.comparing(Person::getName).thenComparing(Person::getAge);
Puce
1
Использовать thenComparingIntдля возраста (int)
Puce
Синтаксис с labda '->' не работает для меня. Person :: getLastName делает.
Нолди
После создания компаратора, зачем создавать поток, сортировать его с помощью компаратора, а затем собирать его? Вы можете просто использовать Collections.sort(people, comparator);вместо этого?
Аниша Сана,
107

Использование подхода 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));
Брэдли Д
источник
19

Вы должны реализовать свой собственный 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));
     }
  }
}

Компаратор сначала сравнивает имена, если они не равны, он возвращает результат их сравнения, иначе он возвращает результат сравнения при сравнении возрастов обоих людей.

Этот код является всего лишь черновиком: поскольку класс является неизменяемым, вы можете подумать о создании его единственного элемента вместо создания нового экземпляра для каждой сортировки.

Ральф
источник
16

Вы можете использовать Java 8 Lambda подход для достижения этой цели. Как это:

persons.sort(Comparator.comparing(Person::getName).thenComparing(Person::getAge));
Зия Ул Мустафа
источник
15

Сделайте так, чтобы класс 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;
    }
}

Это будет сортировать сначала по имени (без учета регистра), а затем по возрасту. Вы можете запуститьArrays.sort() или Collections.sort()коллекцию или массив объектов Person.

Майкл Берри
источник
Я обычно предпочитаю это делать компаратор, так как, как говорит berry120, вы можете сортировать со встроенными методами, а не всегда использовать свой собственный компаратор.
Сулаксия
4

Гуава ComparisonChainобеспечивает чистый способ сделать это. Ссылка на эту ссылку .

Утилита для выполнения связанного оператора сравнения. Например:

   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();
   }
Притеш Мхатре
источник
4

Вы можете сделать так:

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)
);
rafambbr
источник
3

Используйте, 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);

    }
}
Джигар Джоши
источник
3

Создайте столько компараторов, сколько необходимо. После этого вызовите метод 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));        

Посмотрите: Сортировать пользовательский объект по нескольким полям - Компаратор (лямбда-поток)

Фабиан Брандао
источник
3

Я был бы осторожен при использовании Guava, ComparisonChainпотому что он создает экземпляр этого для каждого сравниваемого элемента, поэтому вы будете смотреть на создание N x Log Nцепочек сравнения просто для сравнения, если вы сортируете, или Nэкземпляров, если вы повторяете и проверяете на равенство.

Вместо этого я бы создал статический файл Comparatorс использованием новейшего API-интерфейса Java 8, если это возможно, или OrderingAPI-интерфейса Guava, который позволяет вам это делать, вот пример с 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);
}

Вот как использовать OrderingAPI Guava : https://github.com/google/guava/wiki/OrderingExplained

Гидо Медина
источник
1
«... создает экземпляр этого для каждого сравниваемого элемента ...» - это не так. По крайней мере , в современных версиях гуавы, призывающих к compareметоду ничего не создает, но возвращает один из одноэлементных экземпляров LESS, GREATERили в ACTIVEзависимости от результата сравнения. Это высокооптимизированный подход, не требующий увеличения памяти или производительности.
Йори Н.
Да; Я только что посмотрел на исходный код сейчас, я понимаю, что вы имеете в виду, но я был бы более склонен использовать новый API сравнения Java 8 для зависимости.
Гидо Медина
2

Или вы можете использовать тот факт, что Collections.sort()(или Arrays.sort()) является стабильным (он не меняет порядок элементов, которые равны) и использовать сначала Comparatorсортировку по возрасту, а затем другую сортировку по имени.

В данном конкретном случае это не очень хорошая идея, но если вам нужно изменить порядок сортировки во время выполнения, это может быть полезно.

biziclop
источник
2

Вы можете использовать универсальный последовательный компаратор для сортировки коллекций по нескольким полям.

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;
}
}
Maheshkumar
источник
0
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;

   }
 }
fmucar
источник
Обработка исключений nullpointer хороша и дает понять, что она не будет работать с нулем, но все равно будет повышена
Ralph
Ты абсолютно прав. Недавно я проверял некоторые значения для копирования из одного места в другое, и теперь я продолжаю делать это везде.
fmucar
0

Для такого класса 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);

    }


   }
Джеки Дада Боб
источник
0

Я не уверен, что в этом случае писать урок внутри класса 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()); 
     }  
}    
Тьяго Редаелли
источник