Java 8 Streams 可以对集合中的项目进行操作,然后将其删除吗?
Posted
技术标签:
【中文标题】Java 8 Streams 可以对集合中的项目进行操作,然后将其删除吗?【英文标题】:Can Java 8 Streams operate on an item in a collection, and then remove it? 【发布时间】:2015-07-14 11:49:18 【问题描述】:与几乎所有人一样,我仍在学习新的 Java 8 Streams API 的复杂性(并喜欢它们)。我有一个关于流使用的问题。我将提供一个简化的示例。
Java Streams 允许我们获取Collection
,并在其上使用stream()
方法来接收其所有元素的流。其中有很多有用的方法,例如filter()
、map()
和forEach()
,它们允许我们对内容使用lambda操作。
我的代码看起来像这样(简化):
set.stream().filter(item -> item.qualify())
.map(item -> (Qualifier)item).forEach(item -> item.operate());
set.removeIf(item -> item.qualify());
这个想法是获取集合中所有匹配某个限定符的项的映射,然后通过它们进行操作。手术后,它们不再有任何用途,应从原始集合中删除。代码运行良好,但我无法摆脱Stream
中有一个操作可以为我完成此操作的感觉。
如果它在 Javadocs 中,我可能会忽略它。
有没有更熟悉 API 的人看到类似的东西?
【问题讨论】:
【参考方案1】:你可以这样做:
set.removeIf(item ->
if (!item.qualify())
return false;
item.operate();
return true;
);
如果item.operate()
总是返回true
,你可以非常简洁地做到这一点。
set.removeIf(item -> item.qualify() && item.operate());
但是,我不喜欢这些方法,因为目前尚不清楚发生了什么。就个人而言,我会继续为此使用for
循环和Iterator
。
for (Iterator<Item> i = set.iterator(); i.hasNext();)
Item item = i.next();
if (item.qualify())
item.operate();
i.remove();
【讨论】:
+1 用于使用 for 循环;不要只使用流,以便您可以将代码放在一行中。循环通常比流解决方案更具可读性,即使它们更冗长。 我有点犹豫是否将状态更改代码插入removeIf()
,尽管我相信这在理论上可行。此外,我的情况适合函数式编程,所以我对流的需求不仅仅是将所有内容打包到一行中;但谢谢。
将removeIf
与 lambda 表达式一起使用是完全合法的,并且应该以这种方式使用。状态更改是集合 API 的一部分,而不是流。
for 循环的好例子,但是为什么你认为它更清楚?对我来说,我实际上认为,在这种情况下,流解决方案更清晰
我真的很喜欢那个迭代器 sn-p。【参考方案2】:
在一行中没有,但也许你可以使用partitioningBy
收集器:
Map<Boolean, Set<Item>> map =
set.stream()
.collect(partitioningBy(Item::qualify, toSet()));
map.get(true).forEach(i -> ((Qualifier)i).operate());
set = map.get(false);
这可能更有效,因为它避免了两次迭代集合,一次用于过滤流,然后一次用于删除相应的元素。
否则我认为你的方法比较好。
【讨论】:
答案中的remove
在哪里?我在这里错过了什么?【参考方案3】:
有很多方法。如果使用 myList.remove(element),则必须覆盖 equals()。我更喜欢的是:
allList.removeIf(item -> item.getId().equals(elementToDelete.getId()));
祝你好运,编码愉快:)
【讨论】:
请注意,这将仅删除与谓词匹配的第一个匹配项【参考方案4】:操作后,它们不再有任何用途,应从原始集合中删除。代码运行良好,但我无法摆脱这样的感觉,即 Stream 中有一个操作可以在一行中为我完成此操作。
您不能使用流从流的源中删除元素。来自Javadoc:
大多数流操作接受描述用户指定行为的参数.....为了保持正确的行为,这些行为参数:
必须不干扰(它们不会修改流源);和 在大多数情况下必须是无状态的(它们的结果不应依赖于在流管道执行期间可能更改的任何状态)。
【讨论】:
【参考方案5】:你真正想要做的是对你的集合进行分区。不幸的是,在 Java 8 中,只能通过终端“收集”方法进行分区。你最终会得到这样的结果:
// test data set
Set<Integer> set = ImmutableSet.of(1, 2, 3, 4, 5);
// predicate separating even and odd numbers
Predicate<Integer> evenNumber = n -> n % 2 == 0;
// initial set partitioned by the predicate
Map<Boolean, List<Integer>> partitioned = set.stream().collect(Collectors.partitioningBy(evenNumber));
// print even numbers
partitioned.get(true).forEach(System.out::println);
// do something else with the rest of the set (odd numbers)
doSomethingElse(partitioned.get(false))
更新:
上面代码的Scala版本
val set = Set(1, 2, 3, 4, 5)
val partitioned = set.partition(_ % 2 == 0)
partitioned._1.foreach(println)
doSomethingElse(partitioned._2)`
【讨论】:
啊,希望他们在这些日子里实现“forEachAndRemove
”方法。如果我这样做,我认为我的代码不会更清晰或更快。感谢您的帮助!
令人惊讶的是,您不需要这样的特定方法。 Scala 中的相同代码更有意义(见上文)。
答案中的remove
在哪里?我在这里错过了什么?
@AlikElzin-kilaka List partitioned.get(false) 中的元素是remove
d 元素,partitioned.get(true) 中的列表是@987654326 之后的原始List 的剩余部分@al。如果我做对了。【参考方案6】:
不,您的实现可能是最简单的。您可能会通过修改 removeIf
谓词中的状态来做一些非常邪恶的事情,但请不要这样做。另一方面,实际切换到基于迭代器的命令式实现可能是合理的,这实际上可能更适合此用例且更有效。
【讨论】:
我一直在考虑这个问题,但是对于我的实际(未简化)实现,流可能会变得非常庞大。我通常在四到八核机器上运行它;因此,尽管 Java 的 Stream API 很年轻,但我还不相信迭代器会带来好处。parallel()
可能是这里的救星。不过,感谢您的及时回复!【参考方案7】:
如果我正确理解您的问题:
set = set.stream().filter(item ->
if (item.qualify())
((Qualifier) item).operate();
return false;
return true;
).collect(Collectors.toSet());
【讨论】:
【参考方案8】:我看到保罗在使用流时的清晰度问题,在最佳答案中说明了这一点。也许添加解释变量可以稍微阐明意图。
set.removeIf(item ->
boolean removeItem=item.qualify();
if (removeItem)
item.operate();
return removeItem;
);
【讨论】:
以上是关于Java 8 Streams 可以对集合中的项目进行操作,然后将其删除吗?的主要内容,如果未能解决你的问题,请参考以下文章