일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- port&adapter architecture
- sql사이트
- 자바
- 중복컬럼dto매핑
- Stream
- hibernate 쿼리실행 순서
- 쓰기지연저장소
- Java
- 쿼리실행사이트
- ls -lgaf
- error 2002 (hy000): can't connect to local mysql server through socket '/tmp/mysql.sock' (2)
- sql 테스트 사이트
- 오라클쿼리테스트사이트
- spring kakfa
- 컬럼명중복
- 쓰기지연sql저장소 쿼리실행순서
- JPA
- Oracle
- IntelliJ
- Flush
- 쿼리테스트사이트
- spring cloud stream
- 설치없이쿼리실행
- 스트림
- 쿼리사이트
- 포트앤어댑터 아키텍처
- 쿼리실행순서
- dto매핑우선순위
- Kafka
- group by group by rollup 차이
- Today
- Total
개린이 탈출기
[Java] Stream 을 이용한 정렬 중 null 값이 존재할 때! (Compartor.nulls-) 본문
스트림을 이용하여 Dto 리스트를 정렬하던 중 NullPointerException 을 마주하게 되었다.
Jpa를 통해 Dto 리스트를 전달받아 자바에서 정렬처리를 하려고 했더니 NullPointerException 예외가 발생한 것이다.
실제로 데이터를 확인해보니 정렬하려는 컬럼이 nullable 하였고 정렬 중 null 값을 마주하여 정상적으로 처리하지 못하고 NullPointerException 이 발생한 것 같아 보였다.
해당 상황을 재현해보자면 하단의 코드와 같다.
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class Dto {
private String nullableString;
private Long nullableLong;
}
@PostMapping("/testt")
public void testMethod() {
List<Dto> testList = new ArrayList<>();
for (int i = 0; i < 5; i++) {
testList.add(new Dto("str1_" + i, 0L));
testList.add(new Dto("str2_" + i, 2L));
testList.add(new Dto(null, null));
testList.add(new Dto("str3_" + i, null));
testList.add(new Dto(null, 5L));
}
List<Dto> sortedList = testList.stream().sorted(
Comparator.comparing(Dto::getNullableLong).reversed()
.thenComparing(Dto::getNullableString)
).toList();
System.out.println("end method.");
}
첫번째 nullableLong 값을 역순 정렬하는 도중 nullableLong 값이 null 인 Dto 를 만나면
Cannot read field "value" because "anotherLong" is null
예외가 발생한다.
따라서 비교 시 null 값이 있는 경우를 어떻게 처리할 지 지정해주어 위의 에러를 피할 수 있었는데 수정한 코드는 다음과 같다.
List<Dto> sortedList = testList.stream().sorted(
Comparator.comparing(Dto::getNullableLong, Comparator.nullsFirst(Comparator.reverseOrder()))
.thenComparing(Dto::getNullableString, Comparator.nullsLast(Comparator.naturalOrder()))
).toList();
오라클 db 를 사용하고 있기 때문에 DESC(역순정렬) 을 할 경우, null 값은 상위로 노출되어야 하므로 nullsFirst 메서서드를 호출하였고, 정렬 순서는 역순이므로 reverseOrder 메서드를 호출하였다.
뒤따라 오는 정렬도 동일한 이유로 작성하였다.
여담
스트림이 동작하는 원리와 함꼐 위의 문제가 왜 발생하는지 생각해보았다.
우선 위의 예외 메시지를 읽어 보았을 때, 전달받은 두 개의 Long 타입 파라미터를 Long 타입으로 변환하여 비교하던 중, 어떤 Long 타입 데이터가 null 이기 때문에 문제가 발생한 것으로 보였다.
로그를 따라 거슬러 올라가보니 첫번째 문제는 Long.compareTo 에서 발생한 것을 보니 나의 추측이 맞았던 것으로 보였다.
스트림의 comparing 메서드를 확인해보니 다음과 같았다.
// Comparator.comparing 메서드
public static <T, U extends Comparable<? super U>> Comparator<T> comparing(
Function<? super T, ? extends U> keyExtractor)
{
Objects.requireNonNull(keyExtractor);
return (Comparator<T> & Serializable)
(c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
}
코드를 보면 전달받은 메서드에 따라 T->Long 클래스로 지정되므로 keyCompartor.compare 는 결국 Long.compareTo 메서드를 호출하게 된다. 그리고 Long.compareTo 는 다음과 같다.
public int compareTo(Long anotherLong) {
return compare(this.value, anotherLong.value);
}
여기서 anotherLong 의 value 메서드를 호출하는 과정에서 넘어온 파라미터가 null 이므로 null -> long 으로 받을 수 없어서 오류가 발생하는 것으로 보인다.
그렇다면 Long 타입을 원시타입 long 으로 수정하면 null 값이 발생하지 않고 초기화가 자동으로 되니 문제가 발생하지 않을지도 모르겠다는 추측이 들었고 Long 을 long 으로 수정해보니 문제가 발생하지 않았다.
그리고 Comparator.nulls- 메서드를 사용했을 때, nullPointer 가 발생하지 않는 이유를 알기 위해 코드를 읽어보니, 결국 null 값에 Long 클래스의 필드를 사용하려고 했기 때문에 발생했던 문제였던 것이고, Comparator.nulls- 메서드를 사용하면 NullComparator 클래스가 새로 생성되어 반환되는데, 이 클래스의 compare 메서드는 null 값을 전달받을 시 어떻게 처리할 지에 대한 로직이 있으므로 문제가 발생하지 않은 것이었다.
public static <T> Comparator<T> nullsLast(Comparator<? super T> comparator) {
return new Comparators.NullComparator<>(false, comparator);
}
// NullCompartor 의 compare 메서드
@Override
public int compare(T a, T b) {
if (a == null) {
return (b == null) ? 0 : (nullFirst ? -1 : 1);
} else if (b == null) {
return nullFirst ? 1: -1;
} else {
return (real == null) ? 0 : real.compare(a, b);
}
}
'에러 해결 목록' 카테고리의 다른 글
[IntelliJ] 인텔리제이가 프로젝트를 잘 인식하지 못할 때 (0) | 2025.04.21 |
---|---|
[자바퀴즈] 정적메서드, try-finally return (0) | 2025.03.05 |
[IntelliJ] properties 파일 한글 깨짐 현상 (인코딩) (0) | 2024.11.26 |
[JAVA] Unable to make protected final java.lang.Class 예외 해결 (0) | 2024.11.06 |
[JAVA] Gradle 구동 과정 간단 정리 (1) | 2024.11.05 |