ใน Java 8 ฉันจะแปลง Map <K, V> เป็น Map <K, V> อื่นโดยใช้แลมบ์ดาได้อย่างไร?


140

ฉันเพิ่งเริ่มดู Java 8 และลอง lambdas ฉันคิดว่าฉันพยายามที่จะเขียนสิ่งที่ง่ายมากที่ฉันเขียนเมื่อเร็ว ๆ นี้ ฉันต้องเปลี่ยน Map of String to Column เป็น Map อื่นของ String to Column โดยที่ Column ใน Map ใหม่นั้นเป็นการป้องกันสำเนาของ Column ใน Map แรก คอลัมน์มีตัวสร้างสำเนา สิ่งที่ฉันได้ใกล้เคียงที่สุดคือ:

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

แต่ฉันแน่ใจว่าต้องมีวิธีที่ดีกว่าในการทำและฉันจะขอบคุณสำหรับคำแนะนำบางอย่าง

คำตอบ:


222

คุณสามารถใช้Collector :

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
ฉันคิดว่าสิ่งที่สำคัญ (และตอบโต้ได้ง่ายเล็กน้อย) ที่ควรทราบในข้างต้นคือการเปลี่ยนแปลงเกิดขึ้นในนักสะสมมากกว่าในแผนที่ () การดำเนินการสตรีม
ไบรอัน 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));

และเนื่องจากเราเพิ่งเรียกวิธีการที่มีอยู่เราจึงสามารถใช้การอ้างอิงวิธีการซึ่งทำให้เรา:

map.forEach(map2::put);

8
ฉันชอบที่คุณอธิบายได้อย่างชัดเจนว่าเกิดอะไรขึ้นหลังฉากนี่มันทำให้ชัดเจนมากขึ้น แต่ฉันคิดว่ามันให้สำเนาตรงของแผนที่ดั้งเดิมของฉันไม่ใช่แผนที่ใหม่ที่ค่ากลายเป็นสำเนาป้องกัน ฉันเข้าใจคุณอย่างถูกต้องหรือไม่ว่าเหตุผลที่เราสามารถใช้ (map2 :: put) ก็คืออาร์กิวเมนต์เดียวกันนั้นจะเข้าสู่แลมบ์ดา (เช่น s, จำนวนเต็ม) ตามวิธีการที่วางไว้หรือไม่ ดังนั้นในการทำสำเนาการป้องกันมันจะต้องเป็น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เป็นEnumMapหรือ a SortedMapผลลัพธ์Mapก็จะเป็นเช่นกัน ในกรณีที่SortedMapมี a พิเศษ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

ถ้าคุณใช้ฝรั่ง (ขั้นต่ำ v11) ในโครงการของคุณคุณสามารถใช้แผนที่ :: transformValues

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

หมายเหตุ: ค่าของแผนที่นี้ได้รับการประเมินอย่างขี้เกียจ หากการเปลี่ยนแปลงมีราคาแพงคุณสามารถคัดลอกผลลัพธ์ไปยังแผนที่ใหม่อย่างที่แนะนำใน Guava docs

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.
Andrea Bergonzo

จริง แต่อาจเป็นการเพิ่มเชิงอรรถในคำตอบสำหรับผู้ที่มักจะข้ามการอ่านเอกสาร
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

หากคุณไม่ทราบใช้ห้องสมุดของบุคคลที่ 3 ของฉันไซคลอปส์ทำปฏิกิริยา lib มีส่วนขยายสำหรับทุกJDK เก็บประเภทรวมทั้งแผนที่ คุณสามารถใช้แผนที่หรือวิธีการ 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<String, Column> y
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.