2016-04-21 36 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添加到其他目标集合很容易,比在流处理期间编写用于生成两个列表的自定义收集器要容易得多。但请注意,上面所写的流操作过于勉强以至于不能从并行处理中获益。您需要大量的元素来补偿最初的多线程开销。甚至有可能没有任何数字能够得到回报。

相关问题