2017-12-07 19 views
5

からオブジェクトのMapを作成する必要がある場合がよくあります。 キーは通常、いくつかのStringやEnumなどであり、値は一緒にまとめられた新しいオブジェクトです。 これを行う一般的な方法は、私のために、最初にMap<String, SomeKeyValueObject>を作成してから、SetまたはListを反復して、新しく作成したマップを入れ替えます。次の例のようにJavaストリーム:マップに追加するが、突然変異を避ける

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; 
    } 
} 

これは非常に汚れた感じ。マップを作成し、それを何度も繰り返します。

私はjava 8sの使用をStreamで行い、非突然変異型データ構造を作成しています(むしろ突然変異は見られません)。このようなデータのグループ化を、命令的な方法ではなく宣言的なアプローチを使用するものに変える方法はありますか?

私はhttps://stackoverflow.com/a/34453814/3478016で提案を実装しようとしましたが、私はつまずくようです。答えのアプローチ(Collectors.groupingByCollectors.mappingの使用を提案)を使用して、データをマップにソートすることができました。しかし、私は "データ"を1つの同じオブジェクトにグループ化することはできません。

宣言的な方法でそれを行う方法はありますか、それとも命令に固執していますか?

+0

は、それはあなたが唯一の 'data.displayName(*最後に*をしたいです)' 'data.valueToGroupBy()'ごとに 'data.data()'がありますか? – Bohemian

+0

@Bohemian 'data.displayName()'は各 'data.valueToGroupBy()'で同じです。私の現在の問題では、 'valueToGroupBy'は英語で、' displayName'はスウェーデン語です。しかし、彼らは同じです。 – Chewtoy

+1

それは私の質問に答えません。はいまたはいいえ:各キーに表示されている最後のデータのみを出力マップに入れようとしていますか? – Bohemian

答えて

5

ではなく、Collectors.toMapのマージ関数を使用できます。

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コンストラクタは、これを動作させるためにアクセス可能にする必要があります。

+1

私はこれがコンパイルされるとは思わない。 –

+0

これは私の問題を完全に解決しました。ラインが長くなっても、ラムダを壊すと、これはずっと再利用可能になります。私はまた、コンストラクタがアクセス可能である必要がないことも発見しました。静的ファクトリメソッドも同様に使用できます。 – Chewtoy

+0

@エランはそうではありませんでした。しかし、それを少し書き直すだけで動作します。 value-lambdaで使用する 'String'と' Object'を取り込む 'of'と' String'を取る 'of'と' merge-lambdaで使う 'Set 'です。 – Chewtoy

1

GroupedDataObjectを避け、単に地図とキーとリストが必要な場合は、あなたが探しているCollectors.groupingByを使うことができます。

Collectors.groupingByは、あなたがこれを行うことができます:

List<SomeObject> list = getSomeList(); 

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

これは時々、流れが進むべき道ではありませんequalshashValue

1

の適切な実装を持っているSomeKeyが必要になります。私はこれがその時代の一つだと信じています。

merge()を使用して少しリファクタリングは、あなたが得られます。

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;}); 

を自分のものを保持するために合理的なクラスを仮定:

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