Как в Java 8 преобразовать карту <K, V> в другую карту <K, V> с помощью лямбда?


145

Я только начал смотреть на Java 8 и попробовать лямбды, я подумал, что попробую переписать очень простую вещь, которую написал недавно. Мне нужно превратить карту строки в столбец в другую карту строки в столбец, где столбец на новой карте является защитной копией столбца на первой карте. Столбец имеет конструктор копирования. Ближайшее, что у меня есть, это:

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

но я уверен, что должен быть лучший способ сделать это, и я был бы признателен за совет.

Ответы:


230

Вы можете использовать Коллекционер :

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

7
Я думаю, что важная (и немного противоречащая интуиции) вещь, которую следует отметить в приведенном выше, заключается в том, что преобразование происходит в сборщике, а не в операции потока map ()
— Брайан Агнью

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

И поскольку мы просто вызываем существующий метод, мы можем использовать ссылку на метод , которая дает нам:

map.forEach(map2::put);

8
Мне нравится, как вы точно объяснили, что здесь происходит за кулисами, это делает это намного яснее. Но я думаю, что это дает мне прямую копию моей исходной карты, а не новую карту, на которой значения были превращены в защитные копии. Правильно ли я понимаю, что причина, по которой мы можем просто использовать (map2 :: put), заключается в том, что в лямбда-выражение входят те же аргументы, например (s, integer), что и в методе put? Так что, чтобы сделать защитную копию, она должна быть originalColumnMap.forEach((string, column) -> newColumnMap.put(string, new Column(column)));или я могу ее сократить?
— annesadleir

Да Вы. Если вы просто передаете те же аргументы, вы можете использовать ссылку на метод, однако в этом случае, поскольку у вас есть параметр new Column(column)в качестве параметра, вам придется пойти с этим.
— Arrem

Мне этот ответ больше нравится, потому что он работает, если обе карты уже предоставлены вам, например, при обновлении сеанса.
— Дэвид Стэнли

Не уверен, что понял смысл такого ответа. Он переписывает конструктор в основном всех реализаций Map из существующей Map - вот так .
— Кинеолян

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есть EnumMapили 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

6

Если вы используете в своем проекте 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 переоценивается каждый раз при доступе к записи (что может быть нежелательно, если функция сопоставления дорогая)
— Erric

Верный. Если преобразование обходится дорого, вам, вероятно, не страшны накладные расходы, связанные с копированием его на новую карту, как это предлагается в документации. 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.
— Андреа Бергонцо

Верно, но, возможно, стоит добавить в ответ в качестве сноски для тех, кто склонен пропускать чтение документации.
— Erric

@Erric: В этом есть смысл, только что добавил.
— Андреа Бергонцо

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 имеет расширения для всех типов JDK Collection , включая Map . Вы можете напрямую использовать методы map или bimap для преобразования вашей карты. MapX может быть построен из существующей карты, например.

  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
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.