Java에서 사용자 기준으로 정렬하기

Tina Jeong·2020년 10월 4일
0

Java Collection 클래스에서 정렬할 때 사용되는 ComparableComparator를 DTO와 결부시켜 정리한다.

DTO가 원소인 Collection 정렬하기

Comparable과 Comparator를 사용하지 않는 일반적인 경우의 정렬이다.
계층 간에 데이터를 넘기고 받기 위해 DTO 객체를 자주 사용하는데,이로 인해 발생하는 상황이 있다. DTO 객체의 멤버 변수를 기준으로 sort를 하는 경우이다. 이런 경우는 보통 다음과 같이 처리해줬다.

List<MyDTO> sortedList = TotalList
    .stream()
    .sorted(Comparator.comparing(MyDTO::getCount).reversed())
    .collect(Collectors.toList());

위의 코드를 설명하면 다음과 같다.

  1. stream(): 정렬이 되지 않은 리스트를 시퀀셜한 스트림형태로 변환

  2. sorted(): sorted 함수 안에 값을 비교하는 로직을 넣어서 정렬해줌

Comparator.comparing(MyDTO::getCount)
=> Comparator 클래스의 comparing 함수에 정렬의 기준이 될 멤버변수를 getter로 얻어서 넣어준다.

reversed()
=> 사용한 comparator의 결과를 반대로 정렬하겠다는 뜻이다. 여기서는 오름차순으로 정렬된 결과를 내림차순으로 바꿔주겠다는 의미가 된다.

  1. collect: 정렬된 결과를 Collectors.toList() 함수를 이용하여 List 형태로 반환한다.

문제 상황

이번에 경험한 상황은 해당 DTO의 멤버변수로 또 DTO가 있고, 멤버변수DTO 안의 멤버변수를 기준으로 정렬하는 것이었다. 즉, selected DTO의 count를 기준으로 정렬하는 것이 문제였다.

class MyDTO {
String id;
Long count;
MyDTO selectedDTO;
    (...)
}

해결 방법

Comparator class의 comparing을 그대로 사용해줄 수 없으므로 비교작업에 대한 customizing이 필요하다. 이런 상황에서는 두 가지 방법이 있다.

Solution 1

MyDTO가 Comparable interface를 implements 한후 comareTo 함수를 override하도록 만든다.

   class MyDTO implements Comparable {
   (...)
       @Override
       int compareTo(T o){
            if (this.getSelectedMyDTO() == null && o.getSelectedMyDTO() != null)
                           return -1;
            if (this.getSelectedMyDTO() != null && o.getSelectedMyDTO() == null)
                           return 1;
            if (this.getSelectedMyDTO() == null && o.getSelectedMyDTO() == null)
                           return o1.getCount().compareTo(o2.getCount());
   
            return this.getSelectedMyDTO().getCount()
                           .compareTo(o2.getSelectedMyDTO().getCount());
       }
   }

사용방법:

   List<MyDTO> myDTOs;
   Collections.sort(myDTOs);

Solution 2

Custom Comparater를 구현하고 Solution 1에서 사용한 Comparator.comparing 함수 대신 구현한 Comparator를 넣어준다.

    Comparator<MyDTO> comparator = new Comparator<MyDTO>() {
            @Override
            public int compare(MyDTO o1, MyDTO o2) {
                  if (o1.getSelectedMyDTO() == null && o2.getSelectedMyDTO() != null)
                            return -1;
                  if (o1.getSelectedMyDTO() != null && o2.getSelectedMyDTO() == null)
                            return 1;
                  if (o1.getSelectedMyDTO() == null && o2.getSelectedMyDTO() == null)
                            return o1.getCount().compareTo(o2.getCount());

                  return o1.getSelectedMyDTO().getCount()
                            .compareTo(o2.getSelectedMyDTO().getCount());
            }
    };

사용방법

list().stream().sorted(comparator.reversed())...

override해서 구현할 때는 다음의 내용을 기억하고, 기준으로 삼으면 된다.

리턴값의미
negative integer왼쪽 < 오른쪽
zero왼쪽 = 오른쪽
positive integer왼쪽 > 오른쪽

결론

DTO의 멤버변수가 자기 자신일 경우에는, compareTo 함수를 override하면 compareTo에서 selectedDTO를 참조하여 compareTo를 보낸다.

이 때, selectedDTO의 compareTo 함수를 다시 한번 호출하게 되므로 NullPointerException이 발생할 수 밖에 없다.(selectedDTO의 selectedDTO는 NULL이다.) 그러므로 이 문제 상황에서 1번은 적절한 방법이 아니다.

Comparable interface를 구현하는 방법을 사용할 때는

해당 DTO의 비교 방법이 모든 상황에 동일하고, 멤버변수가 자기 자신이 아닌 경우에 사용하면 좋다.

Comparator를 구현하는 방법을 사용할 때는

정렬의 기준이 되는 멤버변수가 상황에 따라 다양한 경우, 멤버변수에 해당 클래스타입이 재귀적으로 존재하는 경우에 사용한다.

profile
Keep exploring, 계속 탐색하세요.

0개의 댓글