본문 바로가기
JAVA

[JAVA] 자바8 스트림 Map.Entry를 활용해 키(key) 또는 값(value)을 기준으로 Map(맵)정렬하기 1

by 딘딘은딘딘 2023. 9. 12.
반응형

이번 포스팅은 자바 8 스트림(Stream)을 이용해

HashMap의 키 또는 Value를 기준으로 내림차순, 오름차순 정렬을 해본다.

테스트를 위해 Map의 key,value 타입은 Map<String, Integer> 타입으로 설정 했으며,

Key는 순번, value 0~100까지의 랜덤 정수 10개를 put 했다.

먼저 map을 키 또는 값을 기준으로 정렬하기 위해서는 Map 인터페이스 내부의 Entry 인터페이스에 대해 알아야 한다.

Entry는 Map에 저장되는 key-value 쌍을 다루기 위해 Map의 내부 인터페이스로 정의되어 있다.

맵 컬렉션에 대한 자세한 설명은 아래 주소에 자세히 설명이 되어있다.

http://www.tcpschool.com/java/java_collectionFramework_map


* Map 정렬하기

Map을 Stream 객체로 만들기 위해서는 map.entrySet() 메서드를 사용해야 한다

Set<Map.Entry<String, Integer>> entries = map.entrySet();

System.out.println("map.entrySet() >>>>>>> " + map.entrySet());

Map에 entrySet() 메서드를 사용하면 Set에 담겨지게 되고 Set은 Collection이기 때문에 스트림을 사용할 수 있는 준비가 완료된다.

( Set은 모든 키값을을 하나로 뭉친 집합(Set)이라고 보면 된다. )

entries.stream().sorted().forEach(System.out::println);

이후 위에서 작성된 Set을 스트림을 사용해 일반 배열을 정렬하는 것처럼 sorted 메서드를 실행해봤다.

class java.util.HashMap$Node cannot be cast to class java.lang.Comparable 이 뜨면서 오류가 발생한다.

sort 메서드를 사용하기 위해서는 Comparable 인터페이스를 상속받아야 한다.

그러나 Map.Entry는 Comparable 를 상속받고 있지 않기 때문에 sorted의 인수로 함수형 인터페이스 Comparator를 주어야 한다. 

- Map의 오름차순 정렬

Map<String, Integer> entryMap = entries.stream()
    .sorted(Map.Entry.comparingByValue())       // Key를 기준으로 정렬하기 위해서는 sorted의 인자로 map.Entry.comparingByKey() 를 주면된다
    .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue,
          (oldValue, newValue) -> oldValue, LinkedHashMap::new));
          
          
 // {key_3=5, key_1=18, key_2=19, key_6=21, key_5=39, key_8=68, key_7=77, key_4=87, key_0=88, key_9=94}

 

- Map의 내림차순 정렬

Map<String, Integer> entryMap = entries.stream()
                .sorted(Map.Entry.comparingByValue(Comparator.reverseOrder())) 
                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue,
                        (oldValue, newValue) -> oldValue, LinkedHashMap::new));
                        
                        
                        
 // {key_7=91, key_0=88, key_1=82, key_2=77, key_3=69, key_5=61, key_9=55, key_8=40, key_4=13, key_6=7}

림차순 역시 Key를 기준으로 정렬하기 위해서는 sorted의 인자로 map.Entry.comparingByKey() 를 주면된다.

오름차순과 차이가 있다면 comparingByValue의 인자로 Comparator 함수형 인터페이스를 파라미터로 넘겨준다.

Comparator.reverseOrder()은 Comparable 인터페이스를 반환하고 

Collections의 reverseOrder은 Comparator을 반환하기 때문에 인수로 넘겨줄 수 있다.

Comparator 인터페이스의 메서드
Collections의 메서드

- Map의 내림차순 정렬 후 다시 Key를 기준으로 오름차순 정렬하여 List로 받기

Map<String, Integer> entryMap = entries.stream()
                .sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue,
                        (oldValue, newValue) -> oldValue, LinkedHashMap::new));

System.out.println(entryMap);


List<Map.Entry<String, Integer>> list = map.entrySet()
        .stream()
        .sorted(Map.Entry.comparingByKey())
        .collect(Collectors.toList());     // 리스트로 반환

System.out.println(list);   // Key 기준으로 정렬 각 리스트의 요소에  Map이 들어감

자세히 보면 entryMap은 중괄호 {} 로 감싸져 있고

list는 대괄호 [] 로 감사쪄 있는 것을 확인할 수 있다.

 

- collect(Collectors.toMap 설명)

마지막으로 Collectors.toMap에 대한 설명이다.

그냥 보기엔 이해가 어려울 수 있지만, toMap의 경우에는 3가지로 오버로딩 되어있다.

// 1번
Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,
                                Function<? super T, ? extends U> valueMapper) 
                                
// 2번
Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,
                                    Function<? super T, ? extends U> valueMapper,
                                    BinaryOperator<U> mergeFunction) 

// 3번
Collector<T, ?, M> toMap(Function<? super T, ? extends K> keyMapper,
                             Function<? super T, ? extends U> valueMapper,
                             BinaryOperator<U> mergeFunction,
                             Supplier<M> mapFactory)

위 예제에서는 정렬이 완료된 스트림을 Map으로 변환시키기 위해 파라미터가 4개인 3번째 Case를 사용했다.

간단히 설명하자면

첫번째 인자 (Map.Entry::getKey) : Key를 매핑할 인자로 Map의 key를 사용한다는 의미

 

두번째 인자 (Map.Entry::getValue) : Value를 매핑할 인자로 Map의 value를 사용한다는 의미

 

세번째 인자 (oldValue, newValue) -> oldValue : BinaryOperator<U>를 타입으로 받는 mergeFunction은 

동일한 Key로 인해 충돌이 발생한 경우 어떤 Value를 취할 것인지 결정할 때 사용한다.

람다의 파라미터로 oldValue와 newValue가 주어지는데 -> 로 리턴 시

oldValue를 리턴하면 처음 들어온 값을 유지할 때 사용하며

newValue를 리턴하면 이전의 값을 새로 들어온 값으로 변경할때 사용한다.

그 외 임의의 값을 리턴하면 중복되는 값이 있는 경우 임의의 값이 들어가게 된다.

 

마지막 네번째 인자 LinkedHashMap::new : Supplier<M> mapFactory의 경우 반환될 때 사용할 비어있는 Map을 리턴해줘야 한다.

HashMap의 경우 Map에 put이 될때 순서가 보장되지 않은 채로 put이 된다.

따라서 순서를 보장하기 위해 LinkedHashMap을 사용했다. 

 


이 포스팅에서는 Map을 이용해 간단한 오름차순과 내림차순 정렬 그리고 toMap에 대한 사용법을 작성했다.

다음 언제가 될진 모르겠지만, 스트림과 함수형 인터페이스를 활용해 객체, Object 등

다양한 데이터 형식에 대한 정렬 및 Map으로 변환에 대해 작성할 예정이다.

반응형