使用Java Stream在循环中的应用。

3

我对Java Streams还比较陌生。在这个循环中,我需要每次重新创建流吗?还是有更好的方法来做到这一点?只创建一次流并两次使用.noneMatch会导致“流已关闭”异常。

for ( ItemSetNode itemSetNode : itemSetNodeList )
{
  Stream<Id> allUserNodesStream = allUserNodes.stream().map( n -> n.getNodeId() );

  Id nodeId = itemSetNode.getNodeId();
  //if none of the user node ids match the node id, the user is missing the node
  if ( allUserNodesStream.noneMatch( userNode -> userNode.compareTo( nodeId ) == 0 ) )
  {
    isUserMissingNode = true;
    break;
  }
}

谢谢!

这似乎不是使用流的最佳情况。我会使用TreeSet(因为Id似乎实现了Comparable)或者HashSet - Kayaman
你能举一个使用TreeSet的例子吗?你可以遍历集合并将其转换为TreeSet,然后使用set.contains()方法。 - Fresh Codemonger
5个回答

2

我建议您在循环外部制作所有用户ID的列表。只需确保类Id覆盖了equals()函数。

List<Id> allUsersIds = allUserNodes.stream().map(n -> n.getNodeId()).collect(Collectors.toList());

for (ItemSetNode itemSetNode : itemSetNodeList)
{
    Id nodeId = itemSetNode.getNodeId();

    if (!allUsersIds.contains(nodeId))
    {
        isUserMissingNode = true;
        break;
    }
}

1
以下代码应该是等价的,除了布尔值的值被反转,所以如果有缺失节点,则为false
首先,所有用户节点ID都会被收集到一个TreeSet中(如果Id实现了hashCode()equals(),则应该使用HashSet)。然后,我们对itemSetNodeList进行流操作,查看是否所有这些节点ID都包含在集合中。
TreeSet<Id> all = allUserNodes
                        .stream()
                        .map(n -> n.getNodeId())
                        .collect(Collectors.toCollection(TreeSet::new));

boolean isAllNodes = itemSetNodeList
                        .stream()
                        .allMatch(n -> all.contains(n.getNodeId()));

有很多种编写等效(至少对外部来说)代码的方式,这里使用了一个Set来提高查找效率,因此我们不需要不断迭代allUserNodes集合。

您要避免在循环中使用流,因为这会将您的算法转化为O(n²),当您在线性循环和线性流操作内部时。这种方法是O(n log n),用于线性流操作和O(log n) TreeSet查找。使用HashSet只需O(n),尽管这并不重要,除非您处理大量元素。


根据他的代码(有真实的中断),他不需要使用allMatch。 - egorlitvinenko
1
@egorlitvinenko 这是因为我翻转了(代码)逻辑。逻辑是“每个itemSetNode是否都包含在所有UserNodes中?”,这在反转逻辑版本中实际上更清晰。 - Kayaman

1

流的终端操作,如noneMatch()会关闭该流,并使其不可重复使用。
如果需要重复使用此流:

Stream<Id> allUserNodesStream = allUserNodes.stream().map( n -> n.getNodeId() );

把它放进一个方法里:
public Stream<Id> getAllUserNodesStream(){
   return allUserNodes.stream().map( n -> n.getNodeId());
}

并根据需要调用它来创建它:
if (getAllUserNodesStream().noneMatch( userNode -> userNode.compareTo( nodeId ) == 0 ))  

现在请记住,流在编译后的字节码中变成循环。
多次执行相同的循环可能并不是理想的。因此,在实例化相同的流之前,您应该考虑这一点。
作为检测与节点ID匹配的多个流的替代方案:
if (allUserNodesStream.noneMatch( userNode -> userNode.compareTo( nodeId ) == 0 ) ) {
  isUserMissingNode = true;
  break;
}

使用类型为Set的结构,其中包含所有allUserNodesid

if (idsFromUserNodes.contains(nodeId)){
  isUserMissingNode = true;
  break;
}

它会使逻辑更简单,性能更好。
当然,这假设compareTo()equals()一致,但强烈建议(尽管不是必需的)。

对于每个元素再次进行流式处理可能不太适合使用方法,更适合使用Supplier。同时,在回答这个问题时,您还可以提供一种非流式方式,以避免过多的计算。 - Eugene
除非在循环时allUserNodes会发生变化,否则没有必要多次执行lambda函数。 - Simon Arsenault
1
@Eugene,我更新了一个更合理的Set。关于“supplier”,为什么它更适合?我不太确定。 - davidxxx

1
您还可以像这样做:

Set<Id> allUserNodeIds = allUserNodes.stream()
    .map(ItemSetNode::getNodeId)
    .collect(Collectors.toCollection(TreeSet::new));
return itemSetNodeList.stream()
    .anyMatch(n -> !allUserNodeIds.contains(n.getNodeId())); // or firstMatch

甚至是:

或者:

Collectors.toCollection(() -> new TreeSet<>(new YourComparator()));

这里的逻辑有问题。如果itemSetNodeList中存在任何一个节点在allUserNodes中,你就返回true,但实际上即使是itemSetNodeList中的一个节点不存在allUserNodes中也会返回true。 - Kayaman
我是作者的反向条件。逻辑上,“如果A中没有任何元素匹配”,等同于“如果不是A,则至少有一个元素匹配”。如果你仔细看,即使itemSetNodeList中的一个节点在allUserNodes中不存在,我也会返回true。 - egorlitvinenko
啊,你说得对,我漏掉了否定。当你开始反转逻辑时,这就变得棘手了。 - Kayaman
是的,我同意你的观点。在Java流中实现可读的否定操作确实相当复杂。 - egorlitvinenko
在生产代码中,我更喜欢使用具有可读名称的方法引用或常量来处理这种情况。 - egorlitvinenko

0

它将从itemSetNodeList中获取每个项目,并使用noneMatch()检查其是否存在。如果不存在,则返回true。如果anyMatch至少找不到一个项目,将停止搜索并返回false。如果所有项目都被找到,我们将返回true。

Stream<Id> allUserNodesStream = allUserNodes.stream().map( n -> n.getNodeId() );
            boolean isUserMissing=itemSetNodeList.stream()
                            .anyMatch(n-> allUserNodes.stream().noneMatch(n));

1
请解释您的解决方案。更多信息可以在如何撰写一个好答案?找到。 - Matthijs

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接