오랜만에 올리는 게시글은 Java 8 의 Comparator 사용하여 복잡하고 null-safe 한 정렬 비즈니스를 녹였던 방법에 대한 정리 글이다.
* 여기서 복잡하다는 건, 같은 정렬조건이면 한번 더 정렬 할 우선순위가 존재함을 뜻함
전제 조건
1. 정렬할 대상은 서울에 있는 (모든)학교 리스트다. ==> schools
2. 정렬조건
[1순위] 각 학교에 있는 도서관의 사서 선생님 연세 내림차순 ==> school > library > teacher.age (학교1:도서관1:사서쌤1 관계)
[2순위] (나이가 같으면) 학교 seq 오름차순
3. 이때 학교 정보가 없을 수도 있고, 사서 선생님의 연세 또한 null 일 수 있다고 가정하자.
생각나는대로 적은 정렬 코드
모든학교리스트.sort(
Comparator.comparing(
School::getLibaray,
Comparator.comparing(Library::getTeacher, Comparator.comparing(Teacher::getAge).reversed())
).thenComparing(School::getSchoolSeq)
);
문제점
너무 길고 장황하다. 한 눈에 들어오지도 않는다.
무엇보다 가장 큰 문제점은 null-safe 하지 않다. 학교 정보가 없거나 학생의 나이가 없은 경우 바로 NPE 가 발생한다.
보완할 점
자바는 이런 경우를 대비해 `Comparator.nullsFirst()` 그리고 `Comparator.nullsLast()` 메소드를 지원한다.
직관적인 메소드명 덕분에 해당 메소드가 무슨 역할을 하는지 유추가 가능하다. (유추한게 맞다)
사용은 아래처럼 하면 된다.
// teacher 의 null-safe + teacher.age 의 null-safe 한 Comparator 정의
public static final Comparator<Teacher> BY_TEACHER_AGE =
Comparator.nullsLast( // teacher 가 null 이면 맨 뒤로 보낸다.
Comparator.comparing(
// teacher.age 를 내림차순으로 정렬하되, 해당 값이 null 이면 맨 뒤로 보낸다.
Teacher::getAge, Comparator.nullsLast(Comparator.reverseOrder())
);
);
문제점 인지 후 보완점을 적용한 리팩토링한 코드
모든학교리스트.sort(
Comparator.comparing(
(School sc) -> Optional.ofNullable(sc.getLibrary())
.map(Library::getTeacher)
.map(Teacher::getAge).orElse(null)
, Comparator.nullsLast(Comparator.reverseOrder())
).thenComparing(School::getSchoolSeq)
);
세 번째 줄에 명시적 형변환 (School sc) 이 포인트다. thenComparing 구문까지 추가하면 선언한 lambda 표현이 많기 때문에 컴파일러가 어떤 객체로 정렬하는지 알 수 없어서 Object 로 인식하기 때문이다. 명시적 형변환을 해주지 않는다면 Optional.ofNullable() 쪽에서 컴파일 오류가 발생하니 유의하자.
=> Cannot resolve method 'getLibrary' in 'Object'
Collections.sort() 가 아닌 Stream 을 사용하고 싶다면 아래 코드처럼 사용하면 된다.
Stream 을 사용한 정렬 코드
var sorted = 모든학교리스트.stream()
.filter(asch -> asch.getSchool() != null)
.sorted(Comparator.comparing((School sc) -> sc.getLibrary().getTeacher().getAge(), Comparator.nullsLast(Comparator.reverseOrder())).thenComparing(School::getSchoolSeq))
.collect(toList());
Library 가 있다면 Teacher 가 무조건 있다는 가정하에 작성된 코드다.
이렇게 써놓고 비교해 보니 확실히 Stream 을 사용한게 (개인적으로) 가독성이 좋은듯하다 :)
참고 Reference
https://stackoverflow.com/questions/45925825/comparator-nullsfirst-with-null-safe-comparator
https://stackoverflow.com/questions/40500280/comparing-and-thencomparing-gives-compile-error
https://codereview.stackexchange.com/questions/229497/handle-null-in-comparator-class
'Backend > JAVA' 카테고리의 다른 글
[Java] 운영 도중에 XML Parsing Error 가 발생했다! (with. NoSuchMethodError) (0) | 2021.08.06 |
---|---|
[Java] Root(/) path 진입 시 Swagger 로 이동시키는 방법 (0) | 2021.07.14 |
[JAVA] SHA256 암호화 Util (0) | 2021.01.13 |
[JAVA] CURL 로 다른 API 호출하는 방법 (with. HttpClient) (0) | 2020.12.01 |
[JAVA] byte[] to File, File to MultipartFile (fin. byte[] to MultipartFile) (0) | 2020.09.09 |
댓글