본문으로 바로가기

[Java] DTO List 정렬 방법 (Comparator API)

category Backend/Java 2021. 12. 22. 00:16

목차

    1. DTO 생성

    • DTO(Data Transfer Object)란 계층 간 데이터 교환을 위해 사용하는 객체(Java Beans)
    • 예시로 사용할 Student 클래스를 생성합니다.
    public class Student {
    
        public enum ClassName {
            A, B, C
        }
    
        int age;
        String name;
        ClassName className;
    
        Student(int age, String name, ClassName className) {
            this.age = age;
            this.name = name;
            this.className = className;
        }
    
        public int getAge() {
            return age;
        }
    
        public String getName() {
            return name;
        }
    
        public ClassName getClassName() {
            return className;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public void setClassName(ClassName className) {
            this.className = className;
        }
    }

    2. 주요 함수

    [Comparator.comparing()]

    • comparing()은 정렬에 사용할 Comparator를 구현하여 반환하는 Comparator의 static method로 매개변수로는 keyExtractor(키 추출기), keyComparator(키 비교기)를 가지고 있습니다. (키 = 정렬 대상)
    • 함수의 매개변수가 어려워 보이나 정렬할 값을 keyExtractor로 추출하여 keyComparator로 값을 비교하여 정렬하는 내용입니다.
    • keyExtractorFunction을 반환합니다. (대상 값 추출)
    • keyComparatorComparator를 반환합니다. (Comparator 함수형 인터페이스 구현체를 반환하는 것으로 대상 값을 비교하는 데 사용합니다.)
    • Function은 자바에서 제공하는 표준 함수형 인터페이스입니다. 자주 사용되는 함수형 인터페이스 이므로 내용을 잘 모르는 경우 아래의 링크를 확인해보세요.
    • 함수형 인터페이스
    • 자바 8에서 제공하는 표준 함수형 인터페이스
    public static <T, U> Comparator<T> comparing(
            Function<? super T, ? extends U> keyExtractor,
            Comparator<? super U> keyComparator)
    {
    	// ( . . . ) 생략
    }

     

    [Comparator.naturalOrder(), Comparator.reverseOrder()]

    • Comparator.naturalOrder() : 오름차순 Comparator를 반환합니다. (데이터 정렬 시 숫자의 순서를 자연스럽게 맞추어 주는 것)
    • Comparator.reverseOrder() : 내림차순 Comparator를 반환합니다.
    • 따로 Comparator를 구현할 필요 없이 Static Method로 제공합니다. 
    public static <T extends Comparable<? super T>> Comparator<T> naturalOrder() {
        return (Comparator<T>) Comparators.NaturalOrderComparator.INSTANCE;
    }
    
    public static <T extends Comparable<? super T>> Comparator<T> reverseOrder() {
        return Collections.reverseOrder();
    }

     

    [thenComparing()]

    • Comparator의 Default Method로 comparing()과 같이 활용하여 정렬 조건을 여러 개 추가할 수 있습니다.
    default Comparator<T> thenComparing(Comparator<? super T> other) {
    	// ( . . . ) 생략
    }

    3. 정렬

    • 자바 8부터는 인터페이스에 Default Method랑 Static Method를 포함할 수 있게 되었습니다. 그에 따라 Comparator 함수형 인터페이스는 매우 편하게 데이터를 정렬할 수 있도록 도와줍니다.
    • List<Student>와 Comparator API를 예시로 사용합니다.
    • Comparator.comparing(), Comparator.naturalOrder(), Comparator.reverseOrder()를 이용해 정렬합니다.

    3.1 나이 순 정렬(숫자 데이터)

    [사용 데이터]

    List<Student> students = Arrays.asList(
            new Student(2, "Kim", Student.ClassName.A),
            new Student(9, "Shin", Student.ClassName.B),
            new Student(4, "Lee", Student.ClassName.C),
            new Student(6, "Kang", Student.ClassName.C),
            new Student(5, "Chul", Student.ClassName.A),
            new Student(1, "Jang", Student.ClassName.B),
            new Student(7, "Ahn", Student.ClassName.A),
            new Student(3, "Hawng", Student.ClassName.C),
            new Student(8, "Lim", Student.ClassName.B)
    );

     

    [오름차순 정렬]

    // 오름차순
    Comparator<Student> comparingAgeNatural = Comparator.comparing(Student::getAge, Comparator.naturalOrder());
    
    // List로 반환하고 싶은 경우
    List<Student> studentList = students.stream()
            .sorted(comparingAgeNatural)
            .collect(Collectors.toList());
    
    // 결과출력
    students.stream()
            .sorted(comparingAgeNatural)
            .forEach(o -> {
                System.out.println(o.getAge() + " : " + o.getName() + " : " + o.getClassName());
            });
    /*
    [결과]
    1 : Jang : B
    2 : Kim : A
    3 : Hawng : C
    4 : Lee : C
    5 : Chul : A
    6 : Kang : C
    7 : Ahn : A
    8 : Lim : B
    9 : Shin : B
    */

     

    [내림차순 정렬]

    // 내림차순
    Comparator<Student> comparingAgeReverse = Comparator.comparing(Student::getAge, Comparator.reverseOrder());
    
    // List로 반환하고 싶은 경우
    List<Student> studentList = students.stream()
            .sorted(comparingAgeReverse)
            .collect(Collectors.toList());
    
    // 결과출력
    students.stream()
            .sorted(comparingAgeReverse)
            .forEach(o -> {
                System.out.println(o.getAge() + " : " + o.getName() + " : " + o.getClassName());
            });
    /*
    [결과]
    9 : Shin : B
    8 : Lim : B
    7 : Ahn : A
    6 : Kang : C
    5 : Chul : A
    4 : Lee : C
    3 : Hawng : C
    2 : Kim : A
    1 : Jang : B
    */

    3.2. 이름 순 정렬(문자열 데이터)

    [사용 데이터]

    List<Student> students = Arrays.asList(
            new Student(3, "coke", Student.ClassName.B),
            new Student(2, "ant", Student.ClassName.A),
            new Student(3, "Bee", Student.ClassName.B),
            new Student(3, "bee", Student.ClassName.C),
            new Student(3, "Ant", Student.ClassName.B),
            new Student(3, "Coke", Student.ClassName.B)
    );

    3.2.1. 대소문자 구분함

    • 대소문자 구분 시 대문자가 소문자보다 더 우선순위에 있습니다.

    [오름차순]

    // 오름차순
    Comparator<Student> comparingNameNatural = Comparator.comparing(Student::getName, Comparator.naturalOrder());
    
    // 결과출력
    students.stream()
            .sorted(comparingNameNatural)
            .forEach(o -> {
                System.out.println(o.getAge() + " : " + o.getName() + " : " + o.getClassName());
            });
    /*
    [결과]
    3 : Ant : B
    3 : Bee : B
    3 : Coke : B
    2 : ant : A
    3 : bee : C
    3 : coke : B
    */

     

    [내림차순]

    // 내림차순
    Comparator<Student> comparingNameReverse = Comparator.comparing(Student::getName, Comparator.reverseOrder());
    
    // 결과출력
    students.stream()
            .sorted(comparingNameReverse)
            .forEach(o -> {
                System.out.println(o.getAge() + " : " + o.getName() + " : " + o.getClassName());
            });
    /*
    [결과]
    3 : coke : B
    3 : bee : C
    2 : ant : A
    3 : Coke : B
    3 : Bee : B
    3 : Ant : B
    */

    3.2.1. 대소문자 구분하지 않음

    • 대소문자를 구분하지 않을 경우엔 String 클래스의 String.CASE_INSENSITIVE_ORDER, String::compareToIgnoreCase를 활용합니다.

    [오름차순]

    // 오름차순
    Comparator<Student> comparingName = Comparator.comparing(Student::getName, String.CASE_INSENSITIVE_ORDER);
    
    // 결과출력
    students.stream()
            .sorted(comparingName)
            .forEach(o -> {
                System.out.println(o.getAge() + " : " + o.getName() + " : " + o.getClassName());
            });
    /*
    [결과]
    2 : ant : A
    3 : Ant : B
    3 : Bee : B
    3 : bee : C
    3 : coke : B
    3 : Coke : B
    */

     

    [내림차순]

    • Comparator Default Method reversed()를 사용하여 내림차순(정렬의 반대)으로 변경합니다.
    // 내림차순
    Comparator<Student> comparingNameReversed = Comparator.comparing(Student::getName, String.CASE_INSENSITIVE_ORDER).reversed();
    
    // 결과출력
    students.stream()
            .sorted(comparingNameReversed)
            .forEach(o -> {
                System.out.println(o.getAge() + " : " + o.getName() + " : " + o.getClassName());
            });
    /*
    [결과]
    3 : coke : B
    3 : Coke : B
    3 : Bee : B
    3 : bee : C
    2 : ant : A
    3 : Ant : B
    */

    4. 여러 조건으로 정렬

    • Comparator의 Default Method인 thenComparing()를 사용해 정렬 조건을 여러 개 추가할 수 있습니다.

    [사용 데이터]

    List<Student> students = Arrays.asList(
            new Student(2, "Kim", Student.ClassName.A),
            new Student(3, "Shin", Student.ClassName.B),
            new Student(3, "Lee", Student.ClassName.C),
            new Student(2, "Kang", Student.ClassName.C),
            new Student(1, "Chul", Student.ClassName.A),
            new Student(1, "Jang", Student.ClassName.B),
            new Student(3, "Ahn", Student.ClassName.A),
            new Student(1, "Hawng", Student.ClassName.C),
            new Student(2, "Lim", Student.ClassName.B)
    );

     

    [예시]

    반 이름을 오름차순(A~Z)으로 정렬하고, 나이를 내림차순(나이 많은 사람부터)으로 정렬합니다. 

    // 반 이름 정렬(오름차순)
    Comparator<Student> comparingClassName = Comparator.comparing(Student::getClassName, Comparator.naturalOrder());
    
    // 나이 정렬(내림차순)
    Comparator<Student> comparingAge = Comparator.comparing(Student::getAge, Comparator.reverseOrder());
    
    // 결과출력
    students.stream()
            .sorted(comparingClassName.thenComparing(comparingAge))
            .forEach(o -> {
                System.out.println(o.getAge() + " : " + o.getName() + " : " + o.getClassName());
            });
    /*
    [결과]
    3 : Ahn : A
    2 : Kim : A
    1 : Chul : A
    3 : Shin : B
    2 : Lim : B
    1 : Jang : B
    3 : Lee : C
    2 : Kang : C
    1 : Hawng : C
    */

    5. Null Data 처리

    Java 데이터 정렬 Null처리(nullsFirst(), nullsLast())