Daily Notebook

[Java] Null Safe한 자바 컬렉션 정렬

by 배부른코딩로그
💡 자바 컬렉션을 정렬할 때, 값이 Null 일 수 있다는 것을 배제할 수 없다. Null Safe한 정렬을 해보자!

목표

  • Stream을 통해 컬렉션을 정렬할 수 있다.
  • Null Safe한 정렬을 통해 NullPointException 예외를 방지할 수 있다.
  • 정렬에 대한 예제 공간을 만든다.

 

Collection.sort() 를 이용한 정렬이 아닌, 스트림(Stream)을 이용한 정렬을 정리해보고자 한다.

 

Stream.sorted

만약, 숫자 리스트를 오름차순으로 정렬하고자 한다면 다음과 같을 것이다.

List<Integer> numbers = Arrays.asList(9, 5, 7, 1, 8);
List<Integer> sortedNumbers = numbers.stream()
        .sorted(Comparator.comparing(Integer::intValue))
        .collect(Collectors.toList());
System.out.println(sortedNumbers);

위 예제에 대한 결과는 [1, 5, 7, 8, 9] 이다.

Stream에서 제공하는 sorted 헬퍼 메서드를 활용하면 편하게 정렬이 가능하다.

정렬에 대한 기준은 Comparator 클래스에서 제공하는 comparing 메서드를 통해 람다식으로 쉽게 표현할 수 있다.

 

Comparator 클래스

Comparator 의 comparing() 메서드는 첫 번째 인자(keyExtractor)로 정렬 기준 키를 추출할 때 사용되는 함수를 
두 번째 인자(keyComparator)로 추출된 키를 비교하는데 사용될 Comparator를 설정할 수 있다.

 

Comparator.class를 살펴보면 static하게 지원하는 keyComparator는 다음과 같다.

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

메서드 명을 읽어보면 언제 써야할지 바로 감이 오게 된다.

List<Integer> numbers = Arrays.asList(9, 5, 7, 1, 8);
List<Integer> sortedNumbers = numbers.stream()
        .sorted(Comparator.comparing(Integer::intValue, Comparator.reverseOrder()))
        .collect(Collectors.toList());
// [9, 8, 7, 5, 1]

 

만약, 정렬 키값이 Null이라면 어떻게 될까?

그렇다. 개발자라면 자주 접하게 될 NullPointException을 마주하게 된다.

Exception in thread "main" java.lang.NullPointerException
    at java.base/java.util.Comparator.lambda$comparing$ea9a8b3a$1(Comparator.java:436)
    at java.base/java.util.TimSort.binarySort(TimSort.java:296)
    at java.base/java.util.TimSort.sort(TimSort.java:221)
    at java.base/java.util.Arrays.sort(Arrays.java:1306)
    at java.base/java.util.stream.SortedOps$SizedRefSortingSink.end(SortedOps.java:353)
    at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:485)
    at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)
    at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:913)
    at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
    at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:578)
    at com.example.App.main(App.java:25)

 

Comparator Null Safe 정렬

메서드 구현은 Null Safe 하게 작동하는 것을 권장한다.
Java 8부터 Stream, Optional 등 Null Safe한 코드를 짤 수 있도록 도와주는 클래스들이 다수 출현했으며,
정렬 역시 Null Safe 는 권장이 아닌 해야할 처리이다. 정렬하는데 터지면 웃프자너유?

Comparator.class의 static 메서드들 중 아래를 활용할 수 있다.

/**
 * Returns a null-friendly comparator that considers {@code null} to be
 * less than non-null. When both are {@code null}, they are considered
 * equal. If both are non-null, the specified {@code Comparator} is used
 * to determine the order. If the specified comparator is {@code null},
 * then the returned comparator considers all non-null values to be equal.
 *
 * <p>The returned comparator is serializable if the specified comparator
 * is serializable.
 *
 * @param  <T> the type of the elements to be compared
 * @param  comparator a {@code Comparator} for comparing non-null values
 * @return a comparator that considers {@code null} to be less than
 *         non-null, and compares non-null objects with the supplied
 *         {@code Comparator}.
 * @since 1.8
 */
public static <T> Comparator<T> nullsFirst(Comparator<? super T> comparator) {
    return new Comparators.NullComparator<>(true, comparator);
}

/**
 * Returns a null-friendly comparator that considers {@code null} to be
 * greater than non-null. When both are {@code null}, they are considered
 * equal. If both are non-null, the specified {@code Comparator} is used
 * to determine the order. If the specified comparator is {@code null},
 * then the returned comparator considers all non-null values to be equal.
 *
 * <p>The returned comparator is serializable if the specified comparator
 * is serializable.
 *
 * @param  <T> the type of the elements to be compared
 * @param  comparator a {@code Comparator} for comparing non-null values
 * @return a comparator that considers {@code null} to be greater than
 *         non-null, and compares non-null objects with the supplied
 *         {@code Comparator}.
 * @since 1.8
 */
public static <T> Comparator<T> nullsLast(Comparator<? super T> comparator) {
    return new Comparators.NullComparator<>(false, comparator);
}

Java Docs에서 볼 수 있듯이 Null과 친근한 메서드임을 첫 줄에서부터 강조한다.

각각의 기능은 잘 지어진 이름에서 바로 파악이 가능하다.

 

Null Safe하게 개선된 정렬은 다음과 같다.

List<Integer> numbers = Arrays.asList(9, 5, 7, 1, null);
numbers.stream()
        .sorted(Comparator.nullsFirst(Comparator.reverseOrder()))
        .forEach(x -> System.out.print(x + " "));

Comparator.nullsFirst의 결과 값은 [null, 9, 7, 5, 1]가 된다. 이와 반대로

Comparator.nullsLast의 결과 값은 [9, 7, 5, 1, null]가 된다고 쉽게 예상할 수 있다.

 

예제에서 Integer::intValue 부분이 갑자기 사라진 것을 볼 수 있는데, 이는 객체가 아닌 원자값이기 때문이다.

Integer가 null이라서 null.inValue()가 불가능하여, 삭제시켜 버렸다. 주의하자 ㅎㅎ..

 

 

정렬 예제

추후에 채울 예정...

 

 

출처

 

Last Updated. 2022. 05. 24.

 

 

반응형

'Java' 카테고리의 다른 글

[Java] 파일 입출력 소화하기  (0) 2022.06.09
[Java] Lombok 소화하기  (0) 2022.06.07
[Java] Enum 소화하기  (0) 2022.05.09
[Java] YAML 활용하기  (0) 2022.05.04
[Java] @Required 직접 만들어보기  (0) 2022.04.23

블로그의 정보

배부른코딩로그

배부른코딩로그

활동하기