2017-03-27 2 views
1

だが、私はこのようなクラスのアイテムを持っているとしましょう:Java 8 - どのように多くのフィールドをdtoに集計しますか?

class Item { 
    private long id; 
    private BigDecimal value1; 
    private BigDecimal value2; 
    private BigDecimal value3; 
} 

その後、私は、私は各値の合計を知りたい、多くのitensとのリストを持っている:

だから、私は私ができる知っていますしかし

BigDecimal v1 = list.stream().map(Item::value1).reduce(BigDecimal.ZERO, BigDecimal::add); 

ような何かを、私は各値のために同じことを行う必要がありますこのように、私は次のように一つだけDTOに各属性を合計するいくつかの方法がありますかどうかを知りたいのです:

class TotalItem { 
    private BigDecimal value1; 
    private BigDecimal value2; 
    private BigDecimal value3; 
} 

TotalItem t = list.stream().map(???).reduce(BigDecimal.ZERO, BigDecimal::add); 

これは可能ですか?

ありがとうございます。

+0

統計情報の新しいクラスを作成する必要があります。 –

+0

結果ごとに3つの合計値、または同じ合計に3つの異なる値を意味する、それぞれの値は個別にですか? – appl3r

答えて

3

私はそれをテストしていないが、私はあなたのようなItemに機能を追加実装することができると思います。

public Item add(Item other) { 
    Item newItem = new Item(this.value1 + other.value1, 
       this.value2 + other.value2, 
       this.value3 + other.value3); 

    return newItem; 
} 

してから実行します。

Item t = list.stream().reduce(BigDecimal.ZERO, Item::add); 
+0

それはコンパイルされません: 'Item'は' TotalItem'に割り当てられません。 –

+0

これは簡単に解決できます。 TotalItemが全く同じであるためにTotalItemを使用しないでください。あるいは、 "toTotalItem()"を実装してください。 –

+0

'Item'は、なぜそれを' TotalItem'に変換する可能性について気にしますか?これはSRPの違反です。 –

0

は私が仮定その項目を作ってるんです/ TotalItemは非常に大きなオブジェクトで、toTotalItemsummarise(TotalItem,TotalItem)を手作業で大きく面倒な仕事にするでしょう。完全に定型的で間違いやすいものです。

データ構造をリストまたはマップに変更する - コードの読みやすさとタイプの安全性を犠牲にして、集計を簡単にします。

fieldsで反復処理を使用します。

TotalItem toTotalItem(Item item) { 
    Field[] targetFields = TotalItem.class.getFields(); 
    Collection<Field> sourceFields = Item.class.getFields().stream() 
     .filter(x=>fieldNameIsIn(x, targetFields)   
     .collect(Collectors.toList()); 

    TotalItem returnItem = new TotalItem(); 
    for(Field field : sourceFields) { 
     toTargetField(field, targetFields).set(returnItem, (BigDecimal) field.get(item)); 
    } 
    return returnItem; 
} 
boolean fieldNameIsIn(Field sourceField, Field[] targetFields) // exercise for the reader 
Field toTargetField(Field sourceField, Field[] targetFields) // exercise for the reader 

上記のコードはきれいではありませんが、概念を示す必要があります。要約するのに同じ概念を使うことができます。

これにより、書き込みが必要なコードの量は削減されますが、実行時の速度は低下します。また、リフレクションは正しいと魔法を得るのが難しい(いくつかの開発者は好きではない)。

より高速なオプションは、要約で追加するカスタム注釈です。しかし、これは大きな仕事のチャンクです。これを必要とする多数のオブジェクトがある場合は、意味をなさないかもしれません。リフレクションのように、正しいと魔法を得るのは難しい(いくつかの開発者は好きではない)。 Javacがアノテーション処理をネイティブにサポートしているので、幸いにも構築ステップは必要ありません。

+0

(これは解決策について不平を言う削除されたコメントに返信しています) ItemとTotalItemが非常に大きい場合があります。だから2つの合計は、間違った定型コードを取得するために、大型で完全に鈍いものでした。これを自動化すると(Apache CommonsとtoString/equalsのように)意味があります。 これは上記のコードよりもはるかに優れている必要があります。これは概念を示すためのコード例です。 私はその時点でアノテーションソリューションで遊んでいましたが、プロトタイプを完成させることはありませんでした。 –

+0

私が言ったことは、時には反省がブードーであり、私はより明示的であることを好みます。私はこの解決法が機能しないと言っているわけではありません。 –

+0

です。絶対に。 しかし、十分にテストされたライブラリ(Apache Commons toString/Equalsのように)でラップされていれば、とてもうまく動作します。私は個人的にボイラープレートのコードが嫌いです。我々は私たちのためにボイラープレートを行うコンピュータを持っています。だから、なぜそれらを使用しないでください。 個人的に私はこの問題の正しい解決策であると考えています(Project Lombokなど)。 @Summarizableアノテーションとフィールドを要約する方法に関するいくつかのアノテーション。 –

1

次の方法はどうですか?

TotalItem t = new TotalItem(); 

list.stream().forEach(item -> { 
    t.value1+ = item.value1; 
    t.value2+ = item.value2; 
    t.value3+ = item.value3; 
}); 
+1

これは副作用があるので悪いです – maxpovver

+0

@maxpovverどのような副作用ですか? foreach内の – VHS

+0

は、foreach以外の変数を変更します。もしあなたが 'TotalItem'を変更し、それを中断するコードを追加したいのであれば、それは新しいコードが追加されると壊れる可能性があります。 – maxpovver

0

この回答は、同様の操作を行うJDKの方法に触発されています。つまり、私はDoubleSummaryStatisticsクラスを参照しています。

まず、我々は我々が収集されますのBigDecimalの詳細については、ホルダーを定義します。

public class BigDecimalSummaryStats { 
    private long count; 
    private MathContext mc; 
    private BigDecimal sum = BigDecimal.ZERO; 
    private BigDecimal max; 
    private BigDecimal min; 

    public BigDecimalSummaryStats(MathContext mathCtx) { 
    mc = requireNonNull(mathCtx); 
    } 

    public Supplier<BigDecimalSummaryStats> supplier(MathContext ctx) { 
    return() -> new BigDecimalSummaryStats(ctx); 
    } 

    public void accept(BigDecimal value) { 
    requireNonNull(value); 
    count++; 
    sum = sum.add(value, mc); 
    min = min.min(value); 
    max = max.max(value); 
    } 

    public void combine(BigDecimalSummaryStats other) { 
    requireNonNull(other); 
    count += other.count; 
    sum = sum.add(other.sum, mc); 
    min = min.min(other.min); 
    max = max.max(other.max); 
    } 

    public long getCount() { 
    return count; 
    } 

    public BigDecimal getSum() { 
    return sum; 
    } 
    public BigDecimal getMax() { 
    return max; 
    } 
    public BigDecimal getMin() { 
    return min; 
    } 
    public BigDecimal getAverage() { 
    long c = getCount(); 
    return c == 0 ? BigDecimal.ZERO : getSum().divide(BigDecimal.valueOf(c), mc); 
    } 
} 

これは、BigDecimalの値の任意のシーケンスからの要約を収集するのに適した素敵な一般的なユーティリティを提供します。

その後、我々はアイテムの概要クラスを定義することができます

public class ItemSummaryStats { 
    private BigDecimalSummaryStats value1; 
    private BigDecimalSummaryStats value2; 
    private BigDecimalSummaryStats value3; 
    // ... other fields as needed 
    public ItemSummaryStats(MathContext math) { 
    value1 = new BigDecimalSummaryStats(math); 
    value2 = new BigDecimalSummaryStats(math); 
    value3 = new BigDecimalSummaryStats(math); 
    } 

    public void accept(Item item) { 
    value1.accept(item.value1); 
    value2.accept(item.value2); 
    value3.accept(item.value3); 
    // ... other fields as needed 
    } 
    public void combine(ItemSummaryStats other) { 
    value1.combine(other.value1); 
    value2.combine(other.value2); 
    value3.combine(other.value3); 
    } 

    public TotalItem get(
    Function<BigDecimalSummaryStats, BigDecimal> v1Mapper, 
    Function<BigDecimalSummaryStats, BigDecimal> v2Mapper, 
    Function<BigDecimalSummaryStats, BigDecimal> v3Mapper) { 

    TotalItem t = new TotalItem(); 
    t.value1 = v1Mapper.get(value1); 
    t.value2 = v2Mapper.get(value2); 
    t.value3 = v3Mapper.get(value3); 
    return t; 
    } 

    public TotalItem getSum() { 
    return get(BigDecimalSummaryStats::getSum, 
       BigDecimalSummaryStats::getSum, 
       BigDecimalSummaryStats::getSum); 
    } 
    public TotalItem getAverage() { 
    return get(BigDecimalSummaryStats::getAverage, 
       BigDecimalSummaryStats::getAverage, 
       BigDecimalSummaryStats::getAverage); 
    } 
    public TotalItem getMin() { 
    return get(BigDecimalSummaryStats::getMin, 
       BigDecimalSummaryStats::getMin, 
       BigDecimalSummaryStats::getMin); 
    } 
    //.... other methods basically all the same. You get the idea. 
} 

そして最後に、我々はこのように、この良さを使用します。このアプローチの

TotalItem totals = list.stream().collect(
     Collector.of(() -> new ItemStatsSummary(MathContext.DECIMAL64), 
        ItemStatsSummary::accept, 
        ItemStatsSummary::combine, 
        ItemStatsSummary::getSum) 
    ) 

短所:

  1. をアドホックなソリューションよりも開発時間がやや長くなります。

はるかに賛否を上回る、または少なくとも私はそれを確信しています:

  1. は原則関心事の分離に従う:アイテムの統計は、特定のフィールドの要約を収集する方法実際に心配はありません。彼らBigDecimalSummaryが動作すると信じることができます
  2. テスト可能:各部分は、それぞれのスイートでテストすることができます。同じAPIを使用しているため、すべてのフィールドが同じように動作することを信頼できます。
  3. フレキシブル:get(Function...)メソッドは、可能性の大きなリストを公開しています。必要に応じて、最初のフィールドの合計、2番目の平均と3番目の最小値を収集できます。
関連する問題