• 티스토리 홈
  • 프로필사진
    알쓸개잡
  • 방명록
  • 공지사항
  • 태그
  • 블로그 관리
  • 글 작성
알쓸개잡
  • 프로필사진
    알쓸개잡
    • 분류 전체보기 (92)
      • 스프링부트 (52)
      • AWS (5)
      • 쿠버네티스 (7)
      • 자바 (19)
      • 인프라 (0)
      • ETC (8)
  • 방문자 수
    • 전체:
    • 오늘:
    • 어제:
  • 최근 댓글
      등록된 댓글이 없습니다.
    • 최근 공지
        등록된 공지가 없습니다.
      • 반응형
      # Home
      # 공지사항
      #
      # 태그
      # 검색결과
      # 방명록
      • java collection sort using Comparator
        2023년 10월 17일
        • 알쓸개잡
        • 작성자
        • 2023.10.17.:08

        java  collection 프레임워크에서 Comparator 인터페이스를 이용하여 sorting 처리 방법에 대해서 기록한다.

        Comparator 인터페이스

        • Comparator 인터페이스는 int compare(T o1, T o2) 형태의 FunctionalInterface이다.
        • Comparator 인터페이스 구현체를 collection 인스턴스의 sort() 메서드에 전달할 수 있다.
          • sort() 메서드 내부적으로 Comparator 인스턴스의 compare() 메서드를 호출한다.
          • sort() 메서드는 내부적으로 인자로 전달된 Comparator 인스턴스의 compare() 호출의 결과가 > 0인 경우 swap 한다.

        Comparator.comparing()

        Comparator 인스턴스는 몇 가지 유용한 default 및 static 메서드를 제공하는데 그중 하나가 comparing() 메서드다.

        2개의 comparing() 메서드를 제공하는데 메서드 정의는 아래와 같다.

        public static <T, U> Comparator<T> comparing(
                Function<? super T, ? extends U> keyExtractor,
                Comparator<? super U> keyComparator)
        {
            Objects.requireNonNull(keyExtractor);
            Objects.requireNonNull(keyComparator);
            return (Comparator<T> & Serializable)
                (c1, c2) -> keyComparator.compare(keyExtractor.apply(c1),
                                                  keyExtractor.apply(c2));
        }

        Comparator.comparing(Function<> keyExtractor, Comparator<> keyComparator) 메서드는

        • keyExtractro.apply() 호출 결과 리턴되는 인스턴스가 정렬의 대상이 된다.
        • keyComparator 인스턴스의 compare()를 통해서 정렬을 한다.

         

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

        Comparator.comparing(Function<> keyExtractor) 메서드는

        • keyExtractor.apply() 호출 결과 리턴되는 인스턴스가 정렬의 대상이 된다.
        • 정렬의 대상이 되는 인스턴스는 compareTo 메서드를 구현하고 있어야 한다.

         

        간단한 샘플코드를 통해서 사용법을 알아보자.

        package com.example.collection.sorting.dto;
        
        import lombok.Getter;
        import lombok.RequiredArgsConstructor;
        import lombok.ToString;
        
        @RequiredArgsConstructor
        @Getter
        @ToString
        public class SortObject {
        	private final Long id;
        	private final String name;
        	private final Grade grade;
        
        	public enum Grade {
        		A,
        		B,
        		C,
        		D,
        		E,
        		;
        	}
        }

        위와 같은 정렬 대상 객체를 정의하였다.

         

        다음은 Comparator 인터페이스를 구현하는 구현체 클래스다.

        Comparator 인터페이스 구현 클래스를 작성했지만 Comparator는 FunctionalInterface 이므로 lambda로 바로 사용이 가능하다.

        package com.example.collection.sorting.comparator;
        
        import com.example.collection.sorting.dto.SortObject;
        
        import java.util.Comparator;
        
        public class NameComparator implements Comparator<SortObject> {
        	/*
        	o1.getName() > o2.getName : return > 0
        	o1.getName() == o2.getName : return 0
        	o1.getName() < o2.getName() : return < 0
        	*/
        	@Override
        	public int compare(SortObject o1, SortObject o2) {
        		return o1.getName().compareTo(o2.getName());
        	}
        }

        Comparator 인터페이스를 구현하는 구현체 클래스다.

         

        다음과 같은 SortObject를 저장하는 List가 있다.

        List<SortObject> collection;
        
        @BeforeEach
        void createList() {
            collection = new java.util.ArrayList<>(List.of(
                new SortObject(1L, "Kim", SortObject.Grade.A),
                new SortObject(2L, "Park", SortObject.Grade.E),
                new SortObject(3L, "Jung", SortObject.Grade.B),
                new SortObject(4L, "Kim", SortObject.Grade.D),
                new SortObject(5L, "Lee", SortObject.Grade.C),
                new SortObject(6L, "Choi", SortObject.Grade.E)
            ));
        }

         

        다음 테스트 코드는 SortObject의 name 속성에 따라서 오름차순으로 정렬한다.

        @Test
        @DisplayName("이름별 오름차순 정렬 테스트")
        void name_ascending_sort_test() {
            collection.sort(new NameComparator());
            collection.forEach(System.out::println);
        
            System.out.println("================================");
        
            collection.sort(Comparator.comparing(SortObject::getName));
            collection.forEach(System.out::println);
        }
        SortObject(id=6, name=Choi, grade=E)
        SortObject(id=3, name=Jung, grade=B)
        SortObject(id=1, name=Kim, grade=A)
        SortObject(id=4, name=Kim, grade=D)
        SortObject(id=5, name=Lee, grade=C)
        SortObject(id=2, name=Park, grade=E)
        ================================
        SortObject(id=6, name=Choi, grade=E)
        SortObject(id=3, name=Jung, grade=B)
        SortObject(id=1, name=Kim, grade=A)
        SortObject(id=4, name=Kim, grade=D)
        SortObject(id=5, name=Lee, grade=C)
        SortObject(id=2, name=Park, grade=E)

         

        다음 테스트 코드는 SortObject의 name 속성에 따라서 내림차순으로 정렬한다.

        package com.example.collection.sorting.comparator;
        
        import com.example.collection.sorting.dto.SortObject;
        
        import java.util.Comparator;
        
        public class NameComparatorReverse implements Comparator<SortObject> {
        	@Override
        	public int compare(SortObject o1, SortObject o2) {
        		return o2.getName().compareTo(o1.getName());
        	}
        }
        @Test
        @DisplayName("이름별 내림차순 정렬 테스트")
        void name_descending_sort_test() {
            collection.sort(new NameComparator().reversed());
            collection.forEach(System.out::println);
        
            System.out.println("================================");
        
            collection.sort(Comparator.comparing(SortObject::getName, Comparator.reverseOrder()));
            collection.forEach(System.out::println);
        
            System.out.println("================================");
        
            collection.sort(new NameComparatorReverse());
            collection.forEach(System.out::println);
        }

        Comparator 인터페이스의 default 메서드인 reversed()를 호출하여 compare()의 수행 결과를 뒤집을 수 있다 (reversed).

        혹은 NameComparatorReverse 인스턴스를 sort() 메서드에 전달하여도 결과는 동일하다.

        SortObject(id=2, name=Park, grade=E)
        SortObject(id=5, name=Lee, grade=C)
        SortObject(id=1, name=Kim, grade=A)
        SortObject(id=4, name=Kim, grade=D)
        SortObject(id=3, name=Jung, grade=B)
        SortObject(id=6, name=Choi, grade=E)
        ================================
        SortObject(id=2, name=Park, grade=E)
        SortObject(id=5, name=Lee, grade=C)
        SortObject(id=1, name=Kim, grade=A)
        SortObject(id=4, name=Kim, grade=D)
        SortObject(id=3, name=Jung, grade=B)
        SortObject(id=6, name=Choi, grade=E)
        ================================
        SortObject(id=2, name=Park, grade=E)
        SortObject(id=5, name=Lee, grade=C)
        SortObject(id=1, name=Kim, grade=A)
        SortObject(id=4, name=Kim, grade=D)
        SortObject(id=3, name=Jung, grade=B)
        SortObject(id=6, name=Choi, grade=E)

         

        Comparator.thenComparing()

        Comparator 인터페이스는 thenComparing() default 메서드를 제공한다.

        thenComparing(Comparator<? super T> other) 메서드는 동일한 값을 가진 요소 내에서 또 다른 정렬을 하고자 할 때 사용한다.

        테스트 코드에서도 name이 동일한 경우 grade 속성으로 내림 차순으로 정렬하도록 thenComparing()을 호출하였다.

        thenComparing(Comparator<? super T> other) 메서드 정의는 아래와 같다.

        default Comparator<T> thenComparing(Comparator<? super T> other) {
            Objects.requireNonNull(other);
            return (Comparator<T> & Serializable) (c1, c2) -> {
                int res = compare(c1, c2);
                //compare 수행결과 서로 동일한 경우 other Comparator로 비교함
                return (res != 0) ? res : other.compare(c1, c2);
            };
        }

        추가적으로 thenComparing을 오버로딩한 메서드가 있는데 결국에는 위의 thenComparing을 호출한다.

        default <U> Comparator<T> thenComparing(
                Function<? super T, ? extends U> keyExtractor,
                Comparator<? super U> keyComparator)
        {
            return thenComparing(comparing(keyExtractor, keyComparator));
        }
        
        default <U extends Comparable<? super U>> Comparator<T> thenComparing(
                Function<? super T, ? extends U> keyExtractor)
        {
            return thenComparing(comparing(keyExtractor));
        }

         

         

        다음 테스트 코드는 SortObject의 name 속성에 따라서 오름차순으로 정렬하고 동일한 name의 경우 grade는 내림차순으로 정렬한다. 

        @Test
        @DisplayName("이름별 오름차순 그리고 성적별 내림차순 테스트")
        void name_descending_grade_ascending_sort_test() {
            Comparator<SortObject> comparator = new NameComparator()
                .thenComparing(SortObject::getGrade, Comparator.reverseOrder());
        
            collection.sort(comparator);
            collection.forEach(System.out::println);
        
            System.out.println("================================");
        
            Comparator<SortObject> comparatorLambda =
                Comparator.comparing(SortObject::getName)
                    .thenComparing(SortObject::getGrade, Comparator.reverseOrder());
            collection.sort(comparatorLambda);
            collection.forEach(System.out::println);
        }

         

        다음은 실행 결과이다.

        SortObject(id=6, name=Choi, grade=E)
        SortObject(id=3, name=Jung, grade=B)
        SortObject(id=4, name=Kim, grade=D)
        SortObject(id=1, name=Kim, grade=A)
        SortObject(id=5, name=Lee, grade=C)
        SortObject(id=2, name=Park, grade=E)
        ================================
        SortObject(id=6, name=Choi, grade=E)
        SortObject(id=3, name=Jung, grade=B)
        SortObject(id=4, name=Kim, grade=D)
        SortObject(id=1, name=Kim, grade=A)
        SortObject(id=5, name=Lee, grade=C)
        SortObject(id=2, name=Park, grade=E)

         

        null 요소 정렬

        Comparator 인터페이스는

        • public static <T> Comparator<T> nullsFirst(Comparator<? super T> comparator)
        • public static <T> Comparator<T> nullsLast(Comparator<? super T> comparator) 

        static 메서드를 제공하는데 이는 collection 내부의 null 요소를 제일 앞 혹은 제일 뒤로 정렬하는 역할을 한다.

        @Test
        @DisplayName("null 요소 정렬 테스트")
        void name_ascending_sort_nulls_test() {
            List<SortObject> list = new ArrayList<>();
            list.add(new SortObject(1L, "Kim", SortObject.Grade.A));
            list.add(new SortObject(2L, "Park", SortObject.Grade.E));
            list.add(null);
            list.add(new SortObject(3L, "Jung", SortObject.Grade.B));
            list.add(new SortObject(4L, "Kim", SortObject.Grade.D));
            list.add(new SortObject(5L, "Lee", SortObject.Grade.C));
            list.add(null);
            list.add(new SortObject(6L, "Choi", SortObject.Grade.E));
        
            list.sort(Comparator.nullsFirst(Comparator.comparing(SortObject::getName)));
            list.forEach(System.out::println);
        
            System.out.println("================================");
        
            list.sort(Comparator.nullsLast(Comparator.comparing(SortObject::getName)));
            list.forEach(System.out::println);
        }
        null
        null
        SortObject(id=6, name=Choi, grade=E)
        SortObject(id=3, name=Jung, grade=B)
        SortObject(id=1, name=Kim, grade=A)
        SortObject(id=4, name=Kim, grade=D)
        SortObject(id=5, name=Lee, grade=C)
        SortObject(id=2, name=Park, grade=E)
        ================================
        SortObject(id=6, name=Choi, grade=E)
        SortObject(id=3, name=Jung, grade=B)
        SortObject(id=1, name=Kim, grade=A)
        SortObject(id=4, name=Kim, grade=D)
        SortObject(id=5, name=Lee, grade=C)
        SortObject(id=2, name=Park, grade=E)
        null
        null

         

        map의 value를 리스트로 정렬하기

        hashmap의 value를 추출하여 정렬된 List로 추출하는 stream 내에서 사용할 수 있다.

        @Test
        @DisplayName("map value list 정렬")
        void map_sort_test() {
            Map<Long, SortObject> map = new HashMap<>();
            map.put(1L, new SortObject(1L, "Kim", SortObject.Grade.A));
            map.put(2L, new SortObject(2L, "Park", SortObject.Grade.E));
            map.put(3L, new SortObject(3L, "Jung", SortObject.Grade.B));
            map.put(4L, new SortObject(4L, "Kim", SortObject.Grade.D));
            map.put(5L, new SortObject(5L, "Lee", SortObject.Grade.C));
            map.put(6L, new SortObject(6L, "Choi", SortObject.Grade.E));
        
            Stream<SortObject> sortedList = map.entrySet()
                .stream()
                .map(Map.Entry::getValue)
                .sorted(Comparator.comparing(SortObject::getName));
        
            sortedList.forEach(System.out::println);
        }
        SortObject(id=6, name=Choi, grade=E)
        SortObject(id=3, name=Jung, grade=B)
        SortObject(id=1, name=Kim, grade=A)
        SortObject(id=4, name=Kim, grade=D)
        SortObject(id=5, name=Lee, grade=C)
        SortObject(id=2, name=Park, grade=E)

         

        TreeMap key 정렬

        TreeMap의 key를 정렬하는데 Comparator 인터페이스가 사용된다.

        Comparator 인스턴스가 지정되지 않은 경우 TreeMap의 Key 타입은 compareTo 메서드를 구현해야 한다.

         

        Comparator 인스턴스 지정하지 않은 경우

        SortObject를 key로 하는 TreeMap의 SortObject 인스턴스의 hash code 기준으로 정렬하는 코드이다.

        package com.example.collection.sorting.dto;
        
        import lombok.Getter;
        import lombok.RequiredArgsConstructor;
        import lombok.ToString;
        
        @RequiredArgsConstructor
        @Getter
        @ToString
        public class SortObject implements Comparable<SortObject>{
        	private final Long id;
        	private final String name;
        	private final Grade grade;
        
        	@Override
        	public int compareTo(SortObject o) {
        		return Integer.compare(hashCode(), o.hashCode());
        	}
        
        	public enum Grade {
        		A,
        		B,
        		C,
        		D,
        		E,
        		;
        	}
        }
        @Test
        @DisplayName("TreeMap key 인스턴스의 compareTo 호출에 의한 정렬")
        void treemap_test() {
        	//TreeMap에 Comparator 인스턴스를 지정하지 않으면
            //SortObject 인스턴스의 compareTo를 호출하여 정렬한다.
            Map<SortObject, Long> treeMap = new TreeMap<>();
            treeMap.put(new SortObject(1L, "Kim", SortObject.Grade.A), 1L);
            treeMap.put(new SortObject(2L, "Park", SortObject.Grade.E), 2L);
            treeMap.put(new SortObject(3L, "Jung", SortObject.Grade.B), 3L);
            treeMap.put(new SortObject(4L, "Kim", SortObject.Grade.D), 4L);
            treeMap.put(new SortObject(5L, "Lee", SortObject.Grade.C), 5L);
            treeMap.put(new SortObject(6L, "Choi", SortObject.Grade.E), 6L);
        
            treeMap.forEach(((sortObject, aLong) ->
                System.out.println(sortObject + ", hash=" + sortObject.hashCode())));
        }
        SortObject(id=1, name=Kim, grade=A), hash=109228794
        SortObject(id=2, name=Park, grade=E), hash=561959774
        SortObject(id=4, name=Kim, grade=D), hash=580871917
        SortObject(id=5, name=Lee, grade=C), hash=823723302
        SortObject(id=6, name=Choi, grade=E), hash=1714078840
        SortObject(id=3, name=Jung, grade=B), hash=2110756088

         

        Comparator 인스턴스 지정

        @Test
        @DisplayName("TreeMap에 Comparator 지정에 의한 정렬 - 이름 정렬")
        void treemap_sort_test() {
            Comparator<SortObject> comparator = Comparator.comparing(SortObject::getName);
            //SortObject에 compareTo 메서드 구현은 없어도 무방하다.
            SortedMap<SortObject, Long> treeMap = new TreeMap<>(comparator);
            treeMap.put(new SortObject(1L, "Kim", SortObject.Grade.A), 1L);
            treeMap.put(new SortObject(2L, "Park", SortObject.Grade.E), 2L);
            treeMap.put(new SortObject(3L, "Jung", SortObject.Grade.B), 3L);
            treeMap.put(new SortObject(4L, "Kim", SortObject.Grade.D), 4L);
            treeMap.put(new SortObject(5L, "Lee", SortObject.Grade.C), 5L);
            treeMap.put(new SortObject(6L, "Choi", SortObject.Grade.E), 6L);
        
            treeMap.forEach(((sortObject, aLong) ->
                System.out.println(sortObject + ", id=" + aLong)));
        }
        SortObject(id=6, name=Choi, grade=E), id=6
        SortObject(id=3, name=Jung, grade=B), id=3
        SortObject(id=1, name=Kim, grade=A), id=4
        SortObject(id=5, name=Lee, grade=C), id=5
        SortObject(id=2, name=Park, grade=E), id=2

        결과를 보면 SortObject(1L, "Kim", SortObject.Grade.A) 키의 값이 4로 대체되었다.

        이유는 TreeMap의 put() 메서드는 동일한 정렬(여기서는 SortObject의 name 속성) 값이 있는 경우에는 기존 value를 대체한다.

        동일한 정렬 값에 대해서 기존 value를 대체하지 않도록 하려면 putIfAbsent() 메서드를 사용한다.

        @Test
        @DisplayName("TreeMap에 Comparator 지정에 의한 정렬 - 이름 정렬")
        void treemap_sort_test() {
            Comparator<SortObject> comparator = Comparator.comparing(SortObject::getName);
            //SortObject에 compareTo 메서드 구현은 없어도 무방하다.
            SortedMap<SortObject, Long> treeMap = new TreeMap<>(comparator);
            treeMap.putIfAbsent(new SortObject(1L, "Kim", SortObject.Grade.A), 1L);
            treeMap.putIfAbsent(new SortObject(2L, "Park", SortObject.Grade.E), 2L);
            treeMap.putIfAbsent(new SortObject(3L, "Jung", SortObject.Grade.B), 3L);
            treeMap.putIfAbsent(new SortObject(4L, "Kim", SortObject.Grade.D), 4L);
            treeMap.putIfAbsent(new SortObject(5L, "Lee", SortObject.Grade.C), 5L);
            treeMap.putIfAbsent(new SortObject(6L, "Choi", SortObject.Grade.E), 6L);
        
            treeMap.forEach(((sortObject, aLong) ->
                System.out.println(sortObject + ", id=" + aLong)));
        }
        SortObject(id=6, name=Choi, grade=E), id=6
        SortObject(id=3, name=Jung, grade=B), id=3
        SortObject(id=1, name=Kim, grade=A), id=1
        SortObject(id=5, name=Lee, grade=C), id=5
        SortObject(id=2, name=Park, grade=E), id=2

        SortObject(1L, "Kim", SortObject.Grade.A) 키의 값이 1로 유지되지만 SortObject(4L, "Kim", SortObject.Grade.D) 키가 중복으로 인식되어 누락되었다.

        TreeMap 사용 시 이러한 문제를 해결하기 위해서는 키 정렬 속성이 되도록 중복되지 않는 속성으로 정하는 것이 좋다.

        중복되는 속성 값이 있을 수 있는 경우에는 sub 정렬 속성을 지정하여 복합 정렬이 실행되도록 하는 것이 좋다.

         

        @Test
        @DisplayName("TreeMap에 Comparator 지정에 의한 정렬 - 이름 정렬")
        void treemap_sort_test() {
            Comparator<SortObject> comparator =
                Comparator.comparing(SortObject::getName).thenComparing(SortObject::getId);
            //SortObject에 compareTo 메서드 구현은 없어도 무방하다.
            SortedMap<SortObject, Long> treeMap = new TreeMap<>(comparator);
            treeMap.putIfAbsent(new SortObject(1L, "Kim", SortObject.Grade.A), 1L);
            treeMap.putIfAbsent(new SortObject(2L, "Park", SortObject.Grade.E), 2L);
            treeMap.putIfAbsent(new SortObject(3L, "Jung", SortObject.Grade.B), 3L);
            treeMap.putIfAbsent(new SortObject(4L, "Kim", SortObject.Grade.D), 4L);
            treeMap.putIfAbsent(new SortObject(5L, "Lee", SortObject.Grade.C), 5L);
            treeMap.putIfAbsent(new SortObject(6L, "Choi", SortObject.Grade.E), 6L);
        
            treeMap.forEach(((sortObject, aLong) ->
                System.out.println(sortObject + ", id=" + aLong)));
        }
        SortObject(id=6, name=Choi, grade=E), id=6
        SortObject(id=3, name=Jung, grade=B), id=3
        SortObject(id=1, name=Kim, grade=A), id=1
        SortObject(id=4, name=Kim, grade=D), id=4
        SortObject(id=5, name=Lee, grade=C), id=5
        SortObject(id=2, name=Park, grade=E), id=2

        Comparator.thenComparing() 메서드를 사용하여 이름이 중복되는 경우 중복되는 요소 내에서 다시 Id로 정렬하도록 하였다.

        저작자표시 비영리 변경금지 (새창열림)

        '자바' 카테고리의 다른 글

        java 21 처리량 향상을 위한 대안 - virtual thread 알아보자  (0) 2023.11.24
        [Java] 특정 디렉토리의 파일 목록 가져오기  (0) 2023.11.04
        java generic class  (0) 2023.09.24
        java time convert (시간 변환)  (0) 2023.09.17
        Java - 람다 표현식(lambda expression) - 4개의 주요 functional interface  (0) 2023.09.15
        다음글
        다음 글이 없습니다.
        이전글
        이전 글이 없습니다.
        댓글
      조회된 결과가 없습니다.
      스킨 업데이트 안내
      현재 이용하고 계신 스킨의 버전보다 더 높은 최신 버전이 감지 되었습니다. 최신버전 스킨 파일을 다운로드 받을 수 있는 페이지로 이동하시겠습니까?
      ("아니오" 를 선택할 시 30일 동안 최신 버전이 감지되어도 모달 창이 표시되지 않습니다.)
      목차
      표시할 목차가 없습니다.
        • 안녕하세요
        • 감사해요
        • 잘있어요

        티스토리툴바