2017-12-07 95 views
5

Genellikle kendimi bir Set veya List'dan Map nesnesi oluşturmam gereken bir durumda buluyorum. Anahtar genellikle bazı String veya Enum veya benzeridir ve değer birlikte toplanan verilere sahip yeni bir nesnedir. Bunu yapmak için alışılagelen bir yol, ilk olarak Map<String, SomeKeyValueObject>'u oluşturarak ve daha sonra oluşturduğum Set veya Listenin üzerinde yinelemek ve yeni oluşturulmuş haritanızda değişiklik yapmaktır. Aşağıdaki örnekte gibiJava akışları: Haritaya ekleyin ancak mutasyondan kaçının

:

class Example { 
    Map<String, GroupedDataObject> groupData(final List<SomeData> list){ 
     final Map<String, GroupedDataObject> map = new HashMap<>(); 
     for(final SomeData data : list){ 
     final String key = data.valueToGroupBy(); 
     map.put(key, GroupedDataObject.of(map.get(key), data.displayName(), data.data())); 
     } 
     return map; 
    } 

} 

class SomeData { 
    private final String valueToGroupBy; 
    private final Object data; 
    private final String displayName; 

    public SomeData(final String valueToGroupBy, final String displayName, final Object data) { 
     this.valueToGroupBy = valueToGroupBy; 
     this.data = data; 
     this.displayName = displayName; 
    } 

    public String valueToGroupBy() { 
     return valueToGroupBy; 
    } 

    public Object data() { 
     return data; 
    } 

    public String displayName() { 
     return displayName; 
    } 
} 

class GroupedDataObject{ 

    private final String key; 
    private final List<Object> datas; 

    private GroupedDataObject(final String key, final List<Object> list) { 
     this.key = key; 
     this.datas = list; 
    } 

    public static GroupedDataObject of(final GroupedDataObject groupedDataObject, final String key, final Object data) { 
     final List<Object> list = new ArrayList<>(); 
     if(groupedDataObject != null){ 
     list.addAll(groupedDataObject.datas()); 
     } 
     list.add(data); 
     return new GroupedDataObject(key, list); 
    } 

    public String key() { 
     return key; 
    } 

    public List<Object> datas() { 
     return datas; 
    } 
} 

Bu çok kirli hissediyor. Bir harita oluştururuz ve sonra tekrar tekrar değiştiririz.

Ben java 8e Stream s kullanımını ve mutasyona uğramayan veri yapıları oluşturmayı (veya daha ziyade mutasyonu görmüyorsun) aldım. Öyleyse, bu veri gruplandırmasını, zorunlu bir yoldan ziyade bir deklaratif yaklaşım kullanan bir şeye dönüştürmenin bir yolu var mı?

Öneriyi https://stackoverflow.com/a/34453814/3478016'da uygulamaya çalıştım ama tökezliyorum gibi görünüyor. Cevaptaki yaklaşımı kullanarak (Collectors.groupingBy ve Collectors.mapping kullanma önerisi) verileri bir haritaya göre sıralayabiliyorum. Ancak "verileri" bir ve aynı nesneye gruplayamıyorum.

Bunu bildirimsel bir şekilde yapmanın bir yolu var mı, yoksa zorunlulukla mı takılıyorum?

+0

Öyle mi * son * 'data.displayName()' ve her data.valueToGroupBy() 'için data.data()'? – Bohemian

+0

@Bohemian 'data.displayName()', her bir 'data.valueToGroupBy()' için aynıdır. Benim şimdiki sorunumda, 'valueToGroupBy' ingilizce’dir ve' displayName' İsveççe’dir. Ama aynılar. – Chewtoy

+1

Bu, soruma cevap vermiyor. Evet ya da hayır: Sadece çıktı haritasındaki her bir anahtar için görülen son verileri koymaya mı çalışıyorsunuz? – Bohemian

cevap

5

Collectors.groupingBy yerine birleştirme işleviyle kullanabilirsiniz.

Map<String, GroupedDataObject> map = 
    list.stream() 
     .collect(Collectors.toMap(SomeData::valueToGroupBy, 
            d -> { 
            List<Object> l = new ArrayList<>(); 
            l.add(d.data()); 
            return new GroupedDataObject(d.valueToGroupBy(), l); 
            }, 
            (g1,g2) -> { 
             g1.datas().addAll(g2.datas()); 
             return g1; 
            })); 

GroupedDataObject yapıcı bu çalışması için erişilebilir yapılmalıdır.

+1

Bunun bir derleyeceğini sanmıyorum. –

+0

Bu sorunumu mükemmel bir şekilde çözdü. Hatlarda daha uzun olmasına rağmen, lambdaları parçalamak bu kadar çok yeniden kullanılabilir hale getirir. Ayrıca kurucunun erişilebilir olması gerekmediğini de öğrendim. Statik fabrika yöntemlerini de kullanabilirsiniz. – Chewtoy

+0

@Eran olduğu gibi değil. Ama biraz yeniden yazarak işe yarıyor. Bir 'of' değer lambda kullanmak, bir' String' ve 'Nesnesi' almak için, ve bu 'String' almak ve iki' ayarlama of' bir 'birleştirme-lambda kullanımı. – Chewtoy

1

GroupedDataObject'dan kaçınırsanız ve bir anahtar ve bir liste içeren bir harita istiyorsanız, aradığınız Collectors.groupingBy numaralı telefonu kullanabilirsiniz.

Collectors.groupingBy bunu sağlayacak:

List<SomeObject> list = getSomeList(); 

Map<SomeKey, List<SomeObject>> = list.stream().collect(Collectors.groupingBy(SomeObject::getKeyMethod)); 

Bu equals ve

1

hashValue doğru uygulamaları için SomeKey gerektirecektir Bazen akışları gitmek için yol değildir. Sanırım bu zamanlardan biri.

merge() kullanarak Biraz üstlenmeden verir:

Map<String, MyTuple> groupData(final List<SomeData> list) { 
    Map<String, MyTuple> map = new HashMap<>(); 
    list.forEach(d -> map.merge(d.valueToGroupBy(), new MyTuple(data.displayName(), data.data()), 
     (a, b) -> {a.addAll(b.getDatas()); return a;}); 

eşyalarını tutmak için makul bir sınıf varsayarsak: Yalnızca istediğiniz

class MyTuple { 
    String displayName; 
    List<Object> datas = new ArrayList<>(); 
    // getters plus constructor that takes 1 data and adds it to list 
}