Di Java 8 bagaimana cara mengubah Peta <K, V> ke Peta lain <K, V> menggunakan lambda?

145

Saya baru saja mulai melihat Java 8 dan mencoba lambda saya pikir saya akan mencoba menulis ulang hal yang sangat sederhana yang saya tulis baru-baru ini. Saya perlu mengubah Peta String ke Kolom menjadi Peta String ke Kolom lain di mana Kolom di Peta baru adalah salinan defensif Kolom di Peta pertama. Kolom memiliki konstruktor salinan. Hal terdekat yang saya dapatkan sejauh ini adalah:

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

tapi saya yakin pasti ada cara yang lebih baik untuk melakukannya dan saya akan berterima kasih atas beberapa nasihat.

annesadleir
sumber

Jawaban:

230

Anda bisa menggunakan Kolektor :

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) {}
  }
}
McDowell
sumber
7
Saya pikir hal penting (dan sedikit kontra-intuitif) yang perlu diperhatikan di atas adalah bahwa transformasi terjadi di kolektor, bukan di operasi aliran map ()
Brian Agnew
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}

Anda dapat menggunakan forEachmetode ini untuk melakukan apa yang Anda inginkan.

Apa yang Anda lakukan di sana adalah:

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

Yang bisa kita sederhanakan menjadi lambda:

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

Dan karena kita hanya memanggil metode yang sudah ada, kita bisa menggunakan referensi metode , yang memberi kita:

map.forEach(map2::put);
Arrem
sumber
8
Saya suka bagaimana Anda menjelaskan dengan tepat apa yang terjadi di balik layar di sini, ini membuatnya lebih jelas. Tapi saya pikir itu hanya memberi saya salinan langsung dari peta asli saya, bukan peta baru di mana nilainya telah diubah menjadi salinan defensif. Apakah saya memahami Anda dengan benar bahwa alasan kita hanya dapat menggunakan (map2 :: put) adalah karena argumen yang sama masuk ke lambda, misalnya (s, integer) seperti yang masuk ke metode put? Jadi untuk membuat salinan defensif, apakah perlu originalColumnMap.forEach((string, column) -> newColumnMap.put(string, new Column(column)));atau dapatkah saya mempersingkatnya?
annesadleir
Ya, kamu. Jika Anda hanya meneruskan argumen yang sama, Anda dapat menggunakan referensi metode, namun dalam kasus ini, karena Anda memiliki parameter new Column(column)sebagai paremeter, Anda harus menggunakannya.
Arrem
Saya lebih suka jawaban ini karena berfungsi jika kedua peta sudah disediakan untuk Anda, seperti dalam pembaruan sesi.
David Stanley
Tidak yakin untuk memahami maksud dari jawaban seperti itu. Ini menulis ulang konstruktor dari sebagian besar semua implementasi Peta dari Peta yang ada - seperti ini .
Kineolyan
13

Cara tanpa memasukkan kembali semua entri ke peta baru harus menjadi yang tercepat karena secara HashMap.cloneinternal melakukan pengulangan juga.

Map<String, Column> newColumnMap = originalColumnMap.clone();
newColumnMap.replaceAll((s, c) -> new Column(c));
leventov.dll
sumber
Itu cara yang sangat mudah dibaca untuk melakukannya dan cukup ringkas. Sepertinya HashMap.clone () juga ada Map<String,Column> newColumnMap = new HashMap<>(originalColumnMap);. Saya tidak tahu apakah ada bedanya di bawah selimut.
annesadleir
2
Pendekatan ini memiliki keunggulan dalam menjaga Mapperilaku implementasinya, yaitu jika Mapan EnumMapatau a SortedMap, hasilnya Mapjuga akan sama. Dalam kasus SortedMapdengan khusus Comparatoritu mungkin membuat perbedaan semantik besar. Oh well, hal yang sama berlaku untuk IdentityHashMap...
Holger
8

Buat tetap sederhana dan gunakan 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())
                                  );
Anant Khurana
sumber
dalam hal ini: map.forEach (map2 :: put); sederhana :)
ses
6

Jika Anda menggunakan Guava (minimum v11) dalam proyek Anda, Anda dapat menggunakan Maps :: transformValues .

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

Catatan: Nilai peta ini dievaluasi dengan malas. Jika transformasinya mahal, Anda dapat menyalin hasilnya ke peta baru seperti yang disarankan di dokumen 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.
Andrea Bergonzo
sumber
Catatan: Menurut dokumen, ini mengembalikan tampilan evaluasi malas pada originalColumnMap, sehingga fungsi Column :: new dievaluasi ulang setiap kali entri diakses (yang mungkin tidak diinginkan saat fungsi pemetaan mahal)
Erric
Benar. Jika transformasinya mahal, Anda mungkin baik-baik saja dengan overhead menyalinnya ke peta baru seperti yang disarankan di dokumen. 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
Benar, tetapi mungkin bermanfaat menambahkan sebagai catatan kaki dalam jawaban bagi mereka yang cenderung melewatkan membaca dokumen.
Erric
@Erric Masuk akal, baru saja ditambahkan.
Andrea Bergonzo
3

Berikut adalah cara lain yang memberi Anda akses ke kunci dan nilai secara bersamaan, jika Anda harus melakukan beberapa jenis transformasi.

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));
Lucas Ross
sumber
1

Jika Anda tidak keberatan menggunakan pustaka pihak ketiga, lib cyclops-react saya memiliki ekstensi untuk semua jenis Koleksi JDK , termasuk Peta . Anda dapat langsung menggunakan metode peta atau bimap untuk mengubah Peta Anda. Sebuah MapX dapat dibangun dari Peta yang ada misalnya.

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

Jika Anda juga ingin mengubah kunci, Anda dapat menulis

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

bimap dapat digunakan untuk mengubah kunci dan nilai secara bersamaan.

Saat MapX meluas, Peta yang dihasilkan juga dapat didefinisikan sebagai

  Map<String, Column> y
John McClean
sumber