자바 컬렉션을 Null Safe 하게 정렬하기

2020. 7. 4. 16:53·💻 개발/Java

자바 8에 도입된 스트림(Stream)을 사용하면 컬렉션을 쉽게 정렬할 수 있다. (Stream이 아니더라도 List 인터페이스의 sort() 메서드를 사용할 수도 있다.) 예를 들어 Member를 나이 순으로 정렬한다면 아래와 같이 할 수 있다.

 

List<Member> memberList = Arrays.asList(Member.of(10), Member.of(5), Member.of(20));
List<Member> sortedMemberList = memberList.stream()
        .sorted(Comparator.comparing(Member::getAge)) // member의 age 속성을 기준으로 정렬한다.
        .collect(toList());
System.out.println(sortedMemberList);

결과

 

[Member(age=5), Member(age=10), Member(age=20)]

 

위와 같이 Comparator 클래스에서 제공하는 comparing 메서드를 사용하면 원하는 정렬 기준을 람다식으로 표현하여 정렬을 쉽게 할 수 있다. (물론 정렬 기준이 되는 속성은 Comparable 타입이어야 한다. 여기서 Member의 age는 Integer이다.)

 

Comparator.comparing() 메서드는 두 번째 인자를 받을 수 있는 메서드가 하나 더 오버로드 되어있는데, 두 번째 인자는 정렬의 순서를 결정하는 Comparator를 받는다. 만약 Member를 나이를 기준으로 정렬하되 역순으로 정렬하고 싶으면 다음과 같이 작성한다.

 

List<Member> memberList = Arrays.asList(Member.of(10), Member.of(5), Member.of(20));
List<Member> sortedMemberList = memberList.stream()
        .sorted(Comparator.comparing(Member::getAge, Comparator.reverseOrder()))
        .collect(toList());
System.out.println(sortedMemberList);

정렬 기준 키로 Member::getAge 람다식을 넘기고, 정렬 순서로 Comparator.reverseOrder()를 넘기면 나이를 기준으로 역순 정렬 되는것을 알 수 있다.

 

정렬 기준이 Nullable 하다면?

자바는 언어의 특성상 항상 null을 주의해야한다. 위의 예제에서 Member의 나이가 null로 설정된 경우가 있다면 어떨까?

 

List<Member> memberList = Arrays.asList(Member.of(10), Member.of(null), Member.of(5));
List<Member> result = memberList.stream()
        .sorted(Comparator.comparing(Member::getAge))
        .collect(toList());
System.out.println(result);

결과

 

java.lang.NullPointerException
	at java.lang.Integer.compareTo(Integer.java:1216)
	at java.lang.Integer.compareTo(Integer.java:52)
	at java.util.Comparator.lambda$comparing$77a9974f$1(Comparator.java:469)
	at java.util.TimSort.countRunAndMakeAscending(TimSort.java:355)
	at java.util.TimSort.sort(TimSort.java:220)
	at java.util.Arrays.sort(Arrays.java:1512)
    ...

결과는 예상 했던대로 NullPointerException이 발생하게 된다. compareTo()를 호출할 대상 Integer(age)가 null이기 때문이다. 물론 처음부터 null이 설정될 수 없게 해 놓으면 좋겠지만 실무를 하다 보면 그렇지 않은 경우를 많이 만나게 된다. 뭐 어찌 됐던 개발자는 일방통행 도로라고 해도 양쪽을 모두 살피고 길을 건너야 하지 않겠는가.

 

자바는 이런 경우에 사용할 수 있는 2가지 Comparator를 제공한다. 바로 Comparator.nullsFirst()와 Comparator.nullsLast()이다.

 

Comparator.nullsFirst(), Comparator.nullsLast()

위 두 개의 Comparator를 사용하면 정렬 기준 값이 null이어도 NPE가 발생하지 않고 안전하게 정렬을 할 수 있다. 두 객체의 차이는 이름에서도 알 수 있듯이 null을 가진 객체를 앞으로 보내느냐 뒤로 보내느냐의 차이이다. 아래 코드처럼 사용할 수 있다.

 

List<Member> memberList = Arrays.asList(Member.of(10), Member.of(null), Member.of(5));
List<Member> result = memberList.stream()
        .sorted(Comparator.comparing(Member::getAge, Comparator.nullsLast(Comparator.naturalOrder())))
        .collect(toList());
System.out.println(result);

결과

 

[Member(age=5), Member(age=10), Member(age=null)]

Comparator.comparing() 메서드의 두 번째 인자를 넘길 때 Compartor.nullsLast()로 한 번 감싸서 넘겨주게 되면, null을 갖는 객체들은 전부 뒤쪽으로 정렬이 되고 나머지는 주어진 정렬 순서에 맞게 정렬이 된다. 만약 null을 앞으로 정렬하고 나머지는 나이의 역순으로 정렬하고 싶다면 아래와 같이 하면 된다.

 

List<Member> memberList = Arrays.asList(Member.of(10), Member.of(null), Member.of(5));
List<Member> result = memberList.stream()
        .sorted(Comparator.comparing(Member::getAge, Comparator.nullsFirst(Comparator.reverseOrder())))
        .collect(toList());
System.out.println(result);

결과

 

[Member(age=null), Member(age=10), Member(age=5)]

위와 같이 Comparator.nullsFirst()와 Comparator.nullsLast()를 잘 활용하면 null에 안전한 정렬 코드를 작성할 수 있다.

'💻 개발/Java' 카테고리의 다른 글
  • Guava EventBus 간단 사용법
  • Java BigDecimal 크기 비교하기
  • Mockito : Mock 리스트를 주입하고 테스트 하기
  • Java Stream에서 allMatch() 사용시 주의할 점
개발자영준
개발자영준
Java Backend 개발자의 블로그입니다.
  • 개발자영준
    Developer YoungJun
    개발자영준
  • 전체
    오늘
    어제
    • 분류 전체보기 (18)
      • 💻 개발 (14)
        • Java (8)
        • Spring Framework (0)
        • JPA (1)
        • Nginx (1)
        • MySQL (2)
        • Kafka (1)
        • 개발관련 툴 (1)
      • 🎮 컴퓨터,취미 (4)
  • 블로그 메뉴

    • 홈
    • 개발
    • 취미
    • 소개
    • 문의
    • 개인정보처리방침
    • 태그
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    Java8
    Guava
    Stream
    Mockito
    eventbus
    맥북 분해
    Windows10
    맥북 배터리 교체
    transaction
    윈도우10
    mysql
    Java
    배터리스웰링
    JPQL
    docker
    test
    Java Concurrency in Practice
    X220
    Kotlin
    Vacuous Truth
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.6
개발자영준
자바 컬렉션을 Null Safe 하게 정렬하기
상단으로

티스토리툴바