2016-04-21 41 views
3

如何從一個流同時在兩個不同的列表從流對象添加到兩份不同名單同時

添加對象目前我做

body.getSurroundings().parallelStream() 
       .filter(o -> o.getClass().equals(ResourcePoint.class)) 
       .map(o -> (ResourcePoint)o) 
       .filter(o -> !resourceMemory.contains(o)) 
       .forEach(resourceMemory::add); 

從我流對象添加到鏈表「 resourceMemory」,但我也想在同一對象同時添加到另一個列表中,但我無法找到它的語法。是否可能或者我需要爲每個列表有兩個這樣的代碼副本?取而代之的

.forEach(resourceMemory::add) 

+2

您可以使用'peek()'或自定義收集器。 –

+2

你的意思是'instanceof ResourcePoint'或'o.getClass()== ResourcePoint.class'嗎?決定一個,但不要使用混淆實際意圖的'o.getClass()。equals(ResourcePoint.class)'。此外,使用並行流時,您的代碼會以多種方式被破解。請仔細閱讀https://docs.oracle.com/javase/8/docs/api/?java/util/stream/package-summary.html。 – Holger

+1

問題的嚴格答案是'.forEach(o - > {resourceMemory.add(o); myOtherList.add(o);})'。但請注意Holger的評論。您需要更多地瞭解您想要解決此問題的真正目標。 – Tunaki

回答

1

你可以調用

.forEach(o -> { 
    resourceMemory.add(o); 
    otherResource.add(o); 
}) 

或將添加操作在一個單獨的方法,所以你可以提供一個方法參考

.forEach(this::add) 

void add(ResourcePoint p) { 
    resourceMemory.add(o); 
    otherResource.add(o); 
} 

但是記住,因爲在使用並行流時,插入順序可能與每次運行不同。

+0

@Holger您的評論可能以錯誤的地方結束。我不認爲它是指上面的答案。 – Markus

+0

@Markus,不,Holger的評論是正確的,我編輯了我的答案,被破壞的部分被刪除,如果代碼被破壞修復/刪除它:) –

+1

應該強調的是,傳遞給'forEach'的動作不是隻能以任意順序調用,而不是*併發*,因此,這隻適用於線程安全集合。 – Holger

3

有幾個基本的錯誤,你應該先了解,努力擴大你的代碼之前。

首先,forEach並不保證元素有特定的處理順序,所以但它很可能添加到List錯誤的工具,即使是連續流,這是完全錯誤的用平行流使用要添加到集合像LinkedList這是不是線程安全的,因爲動作將同時進行

但即使resourceMemory是一個線程安全集合,您的代碼仍然被破壞,因爲您的filter條件與終端操作之間存在干擾。 .filter(o -> !resourceMemory.contains(o))查詢您在終端操作中修改的相同列表,並且不應該很難理解即使使用線程安全集合,這也能如何制動:

兩個或多個線程可能會處理過濾器,並發現該元素不包含在列表中,那麼所有的人都會添加的元素,矛盾沒有重複你的意圖明顯。

你可以訴諸forEachOrdered這將執行的操作順序和非併發:

body.getSurroundings().parallelStream() 
    .filter(o -> o instanceof ResourcePoint) 
    .map(o -> (ResourcePoint)o) 
    .forEachOrdered(o -> {// not recommended, just for explanation 
     if(!resourceMemory.contains(o)) 
      resourceMemory.add(o); 
    }); 

這將工作,很明顯你怎麼可以加入到這一行動中的另一個列表,但它遠是遠推薦的編碼風格。此外,這個終端動作與所有處理線程同步的事實會破壞並行處理的任何潛在的好處,特別是作爲該流管道的最昂貴的操作是在一個LinkedList將(必須)發生單線程調用contains

收集流元素到列表中正確的方法是通過,顧名思義,collect

List<ResourcePoint> resourceMemory 
    =body.getSurroundings().parallelStream() 
     .filter(o -> o instanceof ResourcePoint) 
     .map(o -> (ResourcePoint)o) 
     .distinct()     // no duplicates 
     .collect(Collectors.toList()); // collect into a list 

這不返回LinkedList,但是你應該認真重新考慮你是否真的需要一個LinkedList 。在所有病例的99%中,你沒有。如果您真的需要需要LinkedList,您可以用Collectors.toCollection(LinkedList::new)替換Collectors.toList()

現在,如果您確實必須添加到您的控件之外創建的現有列表(可能已包含元素),則應考慮上述事實,您必須確保單線程訪問非線程安全無論如何,所以從並行流中完全沒有任何好處。在大多數情況下,它更有效地從該名單獨立讓流工作,並在單線程一步之後添加的結果:

Set<ResourcePoint> newElements= 
    body.getSurroundings().parallelStream() 
     .filter(o -> o instanceof ResourcePoint) 
     .map(o -> (ResourcePoint)o) 
     .collect(Collectors.toCollection(LinkedHashSet::new)); 
newElements.removeAll(resourceMemory); 
resourceMemory.addAll(newElements); 

在這裏,我們收集到LinkedHashSet這意味着遭遇秩序的維護和排序刪除新元素中的重複項,然後在新元素上使用removeAll刪除目標列表中的現有元素(這裏我們從臨時集合的哈希集性質中受益),最後,將新元素添加到目標列表中,如上所述,無論如何,對於不是線程安全的目標集合,必須發生單線程。

使用此解決方案將newElements添加到其他目標集合很容易,比在流處理期間編寫用於生成兩個列表的自定義收集器要容易得多。但請注意,上面所寫的流操作過於勉強以至於不能從並行處理中獲益。您需要大量的元素來補償最初的多線程開銷。甚至有可能沒有任何數字能夠得到回報。

相關問題