Java 8에서 람다를 사용하여 Map <K, V>를 다른 Map <K, V>로 어떻게 변환합니까?


140

방금 Java 8을 살펴보기 시작했고 람다를 시험해보기 위해 최근에 작성한 아주 간단한 것을 다시 작성하려고 생각했습니다. 새 맵의 열이 첫 번째 맵의 열에 대한 방어 적 사본 인 문자열에서 열로의 맵을 다른 문자열에서 열로의 맵으로 바꿔야합니다. 열에 복사 생성자가 있습니다. 내가 지금까지 가장 가까운 것은 다음과 같습니다.

    Map<String, Column> newColumnMap= new HashMap<>();
    originalColumnMap.entrySet().stream().forEach(x -> newColumnMap.put(x.getKey(), new Column(x.getValue())));

그러나 더 좋은 방법이 있어야한다고 확신하며 조언을 해 주셔서 감사합니다.

답변:


222

수집기를 사용할 수 있습니다 .

import java.util.*;
import java.util.stream.Collectors;

public class Defensive {

  public static void main(String[] args) {
    Map<String, Column> original = new HashMap<>();
    original.put("foo", new Column());
    original.put("bar", new Column());

    Map<String, Column> copy = original.entrySet()
        .stream()
        .collect(Collectors.toMap(Map.Entry::getKey,
                                  e -> new Column(e.getValue())));

    System.out.println(original);
    System.out.println(copy);
  }

  static class Column {
    public Column() {}
    public Column(Column c) {}
  }
}

6
위에서 주목해야 할 중요한 (그리고 약간의 반 직관적 인) 것은 map () 스트림 작업 보다는 콜렉터에서 변환이 일어난다는 것입니다.
Brian Agnew

24
Map<String, Integer> map = new HashMap<>();
map.put("test1", 1);
map.put("test2", 2);

Map<String, Integer> map2 = new HashMap<>();
map.forEach(map2::put);

System.out.println("map: " + map);
System.out.println("map2: " + map2);
// Output:
// map:  {test2=2, test1=1}
// map2: {test2=2, test1=1}

forEach방법을 사용하여 원하는 것을 수행 할 수 있습니다.

당신이하고있는 일은 :

map.forEach(new BiConsumer<String, Integer>() {
    @Override
    public void accept(String s, Integer integer) {
        map2.put(s, integer);     
    }
});

우리는 람다로 단순화 할 수 있습니다 :

map.forEach((s, integer) ->  map2.put(s, integer));

그리고 우리는 단지 기존 메소드를 호출하기 때문에 메소드 reference를 사용할 수 있습니다 .

map.forEach(map2::put);

8
나는 당신이 여기에서 뒤에서 일어나는 일을 정확하게 설명하는 방법을 좋아합니다. 그러나 나는 값이 방어 적 사본으로 바뀌는 새로운지도가 아니라 원래지도의 직선 사본을 제공한다고 생각합니다. 우리가 방금 사용할 수있는 이유 (map2 :: put)가 put 메소드로 갈 때와 같은 인수가 람다에 들어가기 때문에 정확하게 이해하고 있습니까? 그래서 방어적인 사본을 만들려면 그것이 필요 originalColumnMap.forEach((string, column) -> newColumnMap.put(string, new Column(column)));할까요?
annesadleir

네 그렇습니다. 동일한 인수를 전달하는 경우 메소드 참조를 사용할 수 있지만이 경우 new Column(column)에는 paremeter가 있으므로 그와 함께해야합니다.
Arrem

이 답변은 세션 갱신과 같이 두지도가 이미 모두 제공 된 경우 작동하기 때문에 더 좋습니다.
David Stanley

그런 대답의 요점을 이해하지 못합니다. 처럼 - 그것은 기존의지도에서 대부분의 모든 맵 구현의 생성자를 다시 작성되어 .
Kineolyan

13

HashMap.clone내부적으로 리해시를 수행 하기 때문에 모든 항목을 새 맵에 다시 삽입하지 않는 방법이 가장 빠릅니다 .

Map<String, Column> newColumnMap = originalColumnMap.clone();
newColumnMap.replaceAll((s, c) -> new Column(c));

그것은 매우 간결하고 읽기 쉬운 방법입니다. HashMap.clone ()처럼 보입니다 Map<String,Column> newColumnMap = new HashMap<>(originalColumnMap);. 나는 그것이 커버에서 다른지 모르겠다.
annesadleir

2
이 접근 방식은 Map구현의 동작 을 유지하는 이점이 있습니다. 즉, Map가 a EnumMap또는 a SortedMap인 경우 결과 Map도 마찬가지입니다. SortedMap특수한 경우에는 Comparator의미 상 큰 차이가있을 수 있습니다. 오, IdentityHashMap
이것도

8

간단하게 유지하고 Java 8 :-를 사용하십시오.

 Map<String, AccountGroupMappingModel> mapAccountGroup=CustomerDAO.getAccountGroupMapping();
 Map<String, AccountGroupMappingModel> mapH2ToBydAccountGroups = 
              mapAccountGroup.entrySet().stream()
                         .collect(Collectors.toMap(e->e.getValue().getH2AccountGroup(),
                                                   e ->e.getValue())
                                  );

이 경우 : map.forEach (map2 :: put); 간단합니다 :)
ses

5

프로젝트에서 Guava (v11 최소)를 사용하는 경우 Maps :: transformValues를 사용할 수 있습니다 .

Map<String, Column> newColumnMap = Maps.transformValues(
  originalColumnMap,
  Column::new // equivalent to: x -> new Column(x) 
)

참고 :이 맵의 값은 느리게 평가됩니다. 변환 비용이 비싸면 Guava 문서에서 제안한 것과 같이 결과를 새 맵에 복사 할 수 있습니다.

To avoid lazy evaluation when the returned map doesn't need to be a view, copy the returned map into a new map of your choosing.

참고 : 문서에 따르면 originalColumnMap에서 지연된 평가 된 뷰를 반환하므로 항목에 액세스 할 때마다 Column :: new 함수가 재평가됩니다 (매핑 기능이
비싸면

옳은. 변환 비용이 비싸다면 문서에서 제안한 것과 같이 새 맵에 복사하는 오버 헤드가 좋을 것입니다. To avoid lazy evaluation when the returned map doesn't need to be a view, copy the returned map into a new map of your choosing.
Andrea Bergonzo 19

사실이지만 문서 읽기를 건너 뛰려는 사람들에게는 답에 각주로 추가 할 가치가 있습니다.
Erric

@Erric 방금 추가했습니다.
Andrea Bergonzo

3

다음은 일종의 변환을 수행해야하는 경우 키와 값에 동시에 액세스 할 수있는 또 다른 방법입니다.

Map<String, Integer> pointsByName = new HashMap<>();
Map<String, Integer> maxPointsByName = new HashMap<>();

Map<String, Double> gradesByName = pointsByName.entrySet().stream()
        .map(entry -> new AbstractMap.SimpleImmutableEntry<>(
                entry.getKey(), ((double) entry.getValue() /
                        maxPointsByName.get(entry.getKey())) * 100d))
        .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

1

타사 라이브러리를 사용하는 것이 마음에 들지 않으면 cyclops-react lib에 Map을 포함한 모든 JDK Collection 유형에 대한 확장이 있습니다 . map 또는 bimap 메소드를 직접 사용하여 맵을 변환 할 수 있습니다. MapX는 기존 Map으로 구성 할 수 있습니다.

  MapX<String, Column> y = MapX.fromMap(orgColumnMap)
                               .map(c->new Column(c.getValue());

키를 변경하려면 다음을 쓸 수 있습니다

  MapX<String, Column> y = MapX.fromMap(orgColumnMap)
                               .bimap(this::newKey,c->new Column(c.getValue());

bimap을 사용하여 키와 값을 동시에 변환 할 수 있습니다.

MapX가 Map을 확장함에 따라 생성 된 맵을 다음과 같이 정의 할 수도 있습니다.

  Map<String, Column> y
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.