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

@ 랄프 : 나는 내 대답을 수정하고 간단한 설명을 추가했습니다.
Richard H

OP에는 이미 자체 객체 클래스가 있으므로 구현하는 것이 더 합리적 Comparable입니다. 답변을 참조하십시오 @ berry120
Zulaxia

1
미니 코드 검토 : 첫 번째 리턴이 가드 절로 작동하므로 else 절은 중복됩니다. 그러나 큰 대답은 나를 위해 대접을했습니다.
Tom Saleeba

30
이 질문과 답변이 여전히 연결되어 있기 때문에 Java SE 8에서는 훨씬 간단 해졌습니다. 게터가 있다면 쓸 수 있습니다Comparator<Person> comparator = Comparator.comparing(Person::getName).thenComparingInt(Person::getAge);
Puce

143

Java 8 스트리밍 API를 사용할 수있는 사람들을 위해 여기에 잘 정리 된 깔끔한 접근 방식이 있습니다 : Lambdas 및 정렬

C # LINQ와 동등한 것을 찾고있었습니다.

.ThenBy(...)

비교기에서 Java 8의 메커니즘을 찾았습니다.

.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 연산을 수행합니까?
John Baum

17
게터가 있다면 쓸 수 있습니다Comparator<Person> comparator = Comparator.comparing(Person::getName).thenComparing(Person::getAge);
Puce

1
thenComparingInt연령대에 사용 (int)
Puce

labda '->'의 구문이 작동하지 않습니다. Person :: getLastName은 않습니다.
Noldy

비교기를 만든 후 스트림을 생성하고 비교기로 정렬 한 다음 수집해야합니까? Collections.sort(people, comparator);대신 사용할 수 있습니까 ?
Anish Sana

107

Java 8 스트림 접근 방식 사용 ...

//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()사람 개체의 컬렉션 또는 배열에.


berry120이 말했듯이 항상 사용자 정의 Comparator를 사용할 필요없이 내장 된 메소드로 정렬 할 수 있기 때문에 일반적으로 Comparator를 만드는 것이 좋습니다.
Zulaxia

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

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

구아바를 사용할 때 ComparisonChain요소별로 인스턴스를 생성하기 때문에 주의 해야합니다. 따라서 N x Log N정렬하는 경우 비교하기 위해 비교 체인 생성을 보거나N 동등성을 반복하고 확인하는 경우 인스턴스를 볼 수 있습니다.

대신 Comparator가능한 경우 최신 Java 8 API 또는 Guava의 OrderingAPI를 사용하여 정적 을 작성하십시오. 이는 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 를 사용하는 방법은 다음과 같습니다 : https://github.com/google/guava/wiki/OrderingExplained


1
"... 요소별로 비교 된 인스턴스를 작성합니다 ..."-이것은 사실이 아닙니다. 현대에 전화 구아바의 버전에서 적어도 compare아무것도 생성하지 않는 방법,하지만 반환 싱글 인스턴스 중 하나 LESS, GREATER또는 ACTIVE비교의 결과에 따라 달라집니다. 이것은 고도로 최적화 된 접근 방식이며 메모리 나 성능 오버 헤드를 추가하지 않습니다.
Yoory N.

예; 방금 소스 코드를 보았습니다. 무슨 의미인지 알지만 종속성을 위해 새로운 Java 8 비교 API를 사용하는 경향이 더 큽니다.
Guido Medina

2

또는 Collections.sort()(또는 Arrays.sort())가 안정적 이라는 사실을 이용하고 ( 같은 요소를 재정렬하지 않음)Comparator 를 연령별로 정렬 한 다음 다른 것을 사용하여 이름별로 정렬 할 수 있습니다.

이 특정 경우에 이것은 좋은 생각이 아니지만 런타임에 정렬 순서를 변경할 수 있어야하는 경우 유용 할 수 있습니다.


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

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;

   }
 }

nullpointer 예외 처리는 훌륭하고 null과 함께 작동하지 않지만 분명히 발생합니다.
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()); 
     }  
}    
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.