Java 8 Streams可以对集合中的项目进行操作,然后将其删除吗?


71

像几乎每个人一样,我仍在学习新的Java 8 Streams API的复杂性(并喜欢它们)。我对流的使用有疑问。我将提供一个简化的示例。

Java Streams允许我们采用Collection,并stream()在其上使用方法来接收其所有元素的流。在这,有许多有用的方法,比如filter()map()forEach(),这让我们对内容的使用拉姆达操作。

我有看起来像这样的代码(简化):

set.stream().filter(item -> item.qualify())
    .map(item -> (Qualifier)item).forEach(item -> item.operate());
set.removeIf(item -> item.qualify());

想法是获取集合中与某个限定符匹配的所有项目的映射,然后对它们进行操作。手术后,它们不再具有任何用途,应从原始设备中取出。该代码运行良好,但是我无法动摇一种感觉,Stream即可以在一行中为我完成操作。

如果在Javadocs中,我可能会忽略它。

有谁更熟悉API,会看到类似的东西吗?

Answers:


123

您可以这样做:

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();
    }
}

12
+1用于使用for循环;不要仅仅使用流,这样就可以使代码适合一行。循环通常比流式解决方案更具可读性,即使它们更冗长。
dimo414 2015年

4
我有点犹豫是否要向其中插入更改状态的代码removeIf(),尽管我认为这在理论上是可行的。另外,我的情况很适合函数式编程,所以我对流的需求不仅限于将所有内容打包到一行中。但是谢谢你
Michael Macha'5

3
removeIf与lambda表达式一起使用是完全合法的,并且应该以这种方式使用。状态更改是不流API的一部分。
hussachai 2015年

7
for循环的好例子,但是为什么您认为它更清楚?对我来说,我实际上认为,在这种情况下,流解决方案更清晰
serup

我真的很喜欢迭代器代码段。
卡米尔

4

在一行中没有,但是也许您可以利用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答案在哪里?我在这里想念什么?
AlikElzin-kilaka '18

4

有很多方法。如果使用myList.remove(element),则必须重写equals()。我更喜欢的是:

allList.removeIf(item -> item.getId().equals(elementToDelete.getId()));

祝你好运,编码愉快:)


请注意,这只会删除与谓词匹配的第一个匹配项
Nikiforos

3

手术后,它们不再具有任何用途,应从原始设备中取出。该代码运行良好,但是我无法撼动Stream中有一项操作可以在一行中为我完成的感觉。

您无法使用流将元素从流的源中删除。从Javadoc

大多数流操作都接受描述用户指定行为的参数。....为了保留正确的行为,这些行为参数:

  • 必须是无干扰的(它们不会修改流源);和
  • 在大多数情况下,它们必须是无状态的(其结果不应取决于在执行流水线期间可能改变的任何状态)。

2

您真正想要做的就是对您的集合进行分区。不幸的是,在Java 8中,只能通过终端“ collect”方法进行分区。您最终得到这样的东西:

// 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在这几天中实现“ ”方法。如果这样做,我认为我的代码不会更清晰或更快速。感谢您的帮助!
Michael Macha'5

令人惊讶的是,您不需要像这样的特定方法。Scala中的相同代码更具意义(请参见上文)。
Dmitriy Yefremov

remove答案在哪里?我在这里想念什么?
AlikElzin-kilaka '18

List partitioned.get(false)中的@ AlikElzin-kilaka元素是removed个元素,partitioned.get(true)中的列表是removal之后原始列表的剩余部分。如果我做对了。
阿兰·贝克

1

不,您的实现可能是最简单的实现。您可能会通过修改removeIf谓词中的状态来做一些非常邪恶的事情,但是请不要这样做。另一方面,实际上切换到基于迭代器的命令式实现可能是合理的,对于该用例而言,这实际上可能更合适,更有效。


我一直在考虑,但是对于我实际的(未简化的)实现,数据流可能会很大。我通常在四到八台核心计算机上运行它;因此,尽管Java的Stream API很年轻,但我仍不相信迭代器会带来好处。parallel()可能是这里的救星。不过,谢谢您的及时答复!
Michael Macha'5

1

如果我正确理解您的问题:

set = set.stream().filter(item -> {
    if (item.qualify()) {
        ((Qualifier) item).operate();
        return false;
    }
    return true;
}).collect(Collectors.toSet());

0

我在最上面的答案中看到Paul在使用流时的清晰度问题。也许添加解释变量可以使意图更加清晰。

set.removeIf(item -> {
  boolean removeItem=item.qualify();
  if (removeItem){
    item.operate();
  }
  return removeItem;
});
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.