2017-03-27 52 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個結果的值或三個不同的值到相同的總和? – 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,因爲它完全相同,或者實現「toTotalItem()」 –

+0

爲什麼'Item'會關心將其轉換爲'TotalItem'的可能性?這違反了SRP。 –

0

我正在做的假設,項目/ TotalItem是非常大的對象,這將使手工編寫一個toTotalItem和一個summarise(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非常大的情況。所以他們兩個的總和是一個大的,完全無趣的,容易出錯的樣板代碼。自動化(如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有什麼副作用? – VHS

+0

在foreach中它改變該foreach之外的變量。如果在項目創建時添加一些新代碼,它可能會被破壞 – maxpovver

0

此答案受JDK開展類似操作的方式的啓發。也就是說,我會參考DoubleSummaryStatistics課程。

首先,我們定義一個保持器的信息,關於BigDecimals的,我們將收集:

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

最後,我們使用這個善良像這樣:

  1. 這種方法的
    TotalItem totals = list.stream().collect(
         Collector.of(() -> new ItemStatsSummary(MathContext.DECIMAL64), 
            ItemStatsSummary::accept, 
            ItemStatsSummary::combine, 
            ItemStatsSummary::getSum) 
        ) 
    

    缺點比adhoc解決方案稍長的開發時間。

遠遠由利弊抵銷,或至少我確信這一點:

  1. 遵循的原則,關注點分離:物品狀態實際上並不關心如何收集特定領域的地總結:可以相信BigDecimalSummary的工作原理
  2. 可測試:每個部分都可以在自己的套件中測試。你可以相信,每個領域都會因爲他們使用相同的API而工作。
  3. 靈活:get(Function...)方法公開了一個大的可能性列表:您可以收集第一場的總和,第二和第三的總和,如果需要的話。