Як в 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
Я думаю, що важливим (і незначним контр-інтуїтивним), що слід зазначити у вищесказаному, є те, що перетворення відбувається в колекторі, а не в операції потоку на карті ()
Брайан Агнеу

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

Оскільки ми просто викликаємо існуючий метод, ми можемо використовувати посилання на метод , який дає нам:

map.forEach(map2::put);

8
Мені подобається, як ти точно пояснив, що тут відбувається за лаштунками, це робить набагато зрозуміліше. Але я думаю, що це дає мені прямо копію моєї оригінальної карти, а не нову карту, де значення перетворилися на оборонні копії. Чи правильно я розумію, що причиною, яку ми можемо просто використати (map2 :: put), є те, що в лямбда входять ті самі аргументи, наприклад (s, integer), як і в метод put? Тож для того, щоб зробити оборонну копію, це потрібно, originalColumnMap.forEach((string, column) -> newColumnMap.put(string, new Column(column)));чи я можу це скоротити?
annesadleir

Так і є. Якщо ви просто передаєте ті самі аргументи, ви можете використовувати посилання на метод, однак у цьому випадку, оскільки у вас є new Column(column)параметр, вам доведеться з цим піти.
Аррем

Мені подобається ця відповідь краще, тому що вона працює, якщо обидві карти вам уже надані, наприклад, під час оновлення сесії.
Девід Стенлі

Не впевнений, щоб зрозуміти сенс такої відповіді. Він переписує конструктор здебільшого всіх реалізацій 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це є EnumMapa 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, тому функція стовпець :: 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 Має сенс, щойно доданий.
Андреа Бергонцо

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

Якщо ви не заперечуєте над використанням сторонніх бібліотек, у моїй бібліотеці cilops- react є розширення для всіх типів колекції JDK , включаючи Map . Ви можете безпосередньо використовувати карту чи методи бімапу для перетворення Вашої Карти. 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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.