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

Can Java 8 Streams operate on an item in a collection, and then remove it?

和几乎所有人一样,我仍在学习新的 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 中有一个操作可以在一行中为我完成此操作。

如果它在 Java 文档中,我可能会忽略它。

有没有更熟悉 API 的人看到类似的东西?

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

在一行中没有,但也许你可以利用 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);

它可能更有效,因为它避免了迭代集合两次,一次用于过滤流,另一次用于删除相应的元素。

另外我觉得你的做法比较好

你可以这样做:

set.removeIf(item -> {
    if (!item.qualify())
        return false;
    item.operate();
    return true;
});

如果item.operate()总是returns 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();
    }
}

你真正想做的是对你的集合进行分区。不幸的是,在 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)`

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

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

After the operation, they serve no further purpose, and should be removed from the original set. The code works well, but I can't shake the feeling that there's an operation in Stream that could do this for me, in a single line.

您不能使用流从流的源中删除元素。来自 Javadoc:

Most stream operations accept parameters that describe user-specified behavior..... To preserve correct behavior, these behavioral parameters:

  • must be non-interfering (they do not modify the stream source); and
  • in most cases must be stateless (their result should not depend on any state that might change during execution of the stream pipeline).

我看到 Paul 在使用流时对清晰度的担忧,这在最上面的答案中有说明。也许添加解释变量可以稍微阐明意图。

set.removeIf(item -> {
  boolean removeItem=item.qualify();
  if (removeItem){
    item.operate();
  }
  return removeItem;
});

方法有很多种。如果您使用 myList.remove(element),则必须覆盖 equals()。我比较喜欢的是:

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

祝你好运,编码愉快:)