[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()가 불가능하여, 삭제시켜 버렸다. 주의하자 ㅎㅎ..
정렬 예제
추후에 채울 예정...
출처
- Stack Excahnge - handle null in Comparator class, dariosicily, 2019-09-23
- Comparator (Java Platform SE 8 ) (oracle.com), Oracle Java8 Docs
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 |
블로그의 정보
배부른코딩로그
배부른코딩로그