Як в 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.