Java 8'de lambda kullanarak bir Haritası <K, V> başka bir Haritaya <K, V> nasıl dönüştürebilirim?


140

Java 8'e bakmaya başladım ve son zamanlarda yazdığım çok basit bir şeyi yeniden yazmaya çalışacağımı düşündüğüm lambdasları denemek için. Yeni harita sütun ilk harita sütunun savunma bir kopyası olduğu sütun dizeye başka bir harita dize bir dize Haritası açmak gerekiyor. Sütun bir kopya oluşturucuya sahiptir. Şimdiye kadar aldığım en yakın şey:

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

ama eminim bunu yapmanın daha iyi bir yolu olmalı ve bazı tavsiyeler için minnettar olurum.

Yanıtlar:


222

Bir Koleksiyoncu kullanabilirsiniz :

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
Yukarıda belirtilmesi gereken önemli (ve hafif karşı-sezgisel) şey, dönüşümün bir harita () akışı işleminden ziyade toplayıcıda gerçekleştiğini düşünüyorum
Brian 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}

İstediğinizi forEachyapmak için yöntemi kullanabilirsiniz .

Orada ne yapıyorsun:

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

Hangi bir lambdaya basitleştirebiliriz:

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

Ve sadece mevcut bir yöntemi çağırdığımız için , bize aşağıdakileri sağlayan bir yöntem referansı kullanabiliriz :

map.forEach(map2::put);

8
Buradaki sahnelerin arkasında neler olduğunu tam olarak nasıl açıkladığınızı seviyorum, bu çok daha net hale geliyor. Ama bence bu sadece orijinal haritamın düz bir kopyasını veriyor, değerlerin defansif kopyalara dönüştürüldüğü yeni bir harita değil. Sizi doğru şekilde anlayabiliyorum (map2 :: put) nedeninin aynı argümanların lambda'ya, örneğin (s, integer) put metoduna giriyor olması? Bu yüzden defansif bir kopya yapmak için mi olmalı originalColumnMap.forEach((string, column) -> newColumnMap.put(string, new Column(column)));yoksa kısaltabilir miyim?
annesadleir

Evet öylesin. Sadece aynı argümanları new Column(column)geçiyorsanız, bir yöntem referansı kullanabilirsiniz, ancak bu durumda, bir paremeter olarak sahip olduğunuzdan, bununla gitmek zorunda kalacaksınız.
Arrem

Bu yanıtı daha çok seviyorum çünkü oturumun yenilenmesinde olduğu gibi her iki harita da zaten sağlanmışsa çalışır.
David Stanley

Böyle bir cevabın amacını anladığınızdan emin değilim. Gibi - Varolan Haritası çoğunlukla tüm Harita uygulamalarının yapıcısı yeniden yazıyor bu .
Kineolyan

13

Tüm girişleri yeni haritaya yeniden eklemeden yol, en hızlı şekilde olmalıdır çünkü HashMap.clonedahili olarak yeniden canlandırma gerçekleştirir.

Map<String, Column> newColumnMap = originalColumnMap.clone();
newColumnMap.replaceAll((s, c) -> new Column(c));

Bunu yapmanın çok okunaklı bir yolu ve oldukça özlü. HashMap.clone () gibi görünüyor Map<String,Column> newColumnMap = new HashMap<>(originalColumnMap);. Kapakların altında farklı olup olmadığını bilmiyorum.
annesadleir

2
Bu yaklaşım, Mapuygulamanın davranışını koruma avantajına sahiptir , yani Mapbir EnumMapveya a SortedMapise, sonuç da Mapolacaktır. SortedMapÖzel bir a durumunda, Comparatorbüyük bir anlamsal fark yaratabilir. Ah, aynı şey bir IdentityHashMap
Holger

8

Basit tutun ve Java 8 kullanın: -

 Map<String, AccountGroupMappingModel> mapAccountGroup=CustomerDAO.getAccountGroupMapping();
 Map<String, AccountGroupMappingModel> mapH2ToBydAccountGroups = 
              mapAccountGroup.entrySet().stream()
                         .collect(Collectors.toMap(e->e.getValue().getH2AccountGroup(),
                                                   e ->e.getValue())
                                  );

bu durumda: map.forEach (map2 :: put); is simple :)
ses

5

Projenizde Guava (minimum v11) kullanıyorsanız Maps :: transformValues kullanabilirsiniz .

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

Not: Bu haritanın değerleri tembel olarak değerlendirilir. Dönüştürme pahalıysa, sonucu Guava dokümanlarında önerilen gibi yeni bir haritaya kopyalayabilirsiniz.

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.

Not: Dokümanlara göre, bu, originalColumnMap üzerinde tembel bir değerlendirilmiş görünüm döndürür, bu nedenle girişe her erişildiğinde (eşleme işlevi pahalı olduğunda istenmeyebilir) Column :: new işlevi yeniden değerlendirilir
Erric

Doğru. Dönüştürme pahalıysa, bunu dokümanlarda önerildiği gibi yeni bir haritaya kopyalamanın yükü ile muhtemelen iyi olursunuz. 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

Doğru, ancak belgeleri okumayı atlama eğiliminde olanlar için cevaba bir dipnot olarak eklemeye değer olabilir.
Erric

@Erric Mantıklı, ekledi.
Andrea Bergonzo

3

Burada, bir tür dönüşüm yapmanız gerektiğinde, anahtar ve değere aynı anda erişmenizi sağlayan başka bir yol var.

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

Üçüncü taraf kitaplıklarını kullanmanın bir sakıncası yoksa, cyclops-tepki tepkisi, Harita da dahil olmak üzere tüm JDK Koleksiyonu türleri için uzantılara sahiptir . Haritanızı dönüştürmek için doğrudan harita veya bimap yöntemlerini kullanabilirsiniz. Bir MapX mevcut bir Haritadan yapılabilir.

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

Anahtarı değiştirmek isterseniz, yazabilirsiniz

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

bimap, anahtarları ve değerleri aynı anda dönüştürmek için kullanılabilir.

MapX Harita'yı genişlettikçe, oluşturulan harita da şu şekilde tanımlanabilir:

  Map<String, Column> y
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.