在不破坏流管道的情况下引用前面的 Java 流步骤?

Posted

技术标签:

【中文标题】在不破坏流管道的情况下引用前面的 Java 流步骤?【英文标题】:Reference antecedent Java stream step without breaking the stream pipeline? 【发布时间】:2021-10-22 09:24:46 【问题描述】:

我是函数式编程的新手,我正在努力变得更好。

目前,我正在试验一些采用以下基本形式的代码:

private static int myMethod(List<Integer> input)
      Map<Integer,Long> freq = input
        .stream()
        .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
      
      return (int) freq
        .keySet()
        .stream()
        .filter(key-> freq.containsKey(freq.get(key)))
        .count();
    

首先使用哈希图来获取列表中每个数字的频率。接下来,我们总结了在地图中也作为键存在的键的数量。

我不喜欢两个流需要彼此分开存在的方式,其中 HashMap 是由一个流制成的,只是为了立即被另一个流专门使用。

有没有办法将它组合成一个流?我在想这样的事情:

private static int myMethod(List<Integer> input)
      return (int) input
        .stream()
        .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))
        .keySet()
        .stream()
        .filter(key-> freq.containsKey(freq.get(key)))
        .count();
    

但这里的问题是没有可参考的频率映射,因为它被用作管道的一部分,因此过滤器无法完成它需要做的事情。

总之,我不喜欢这样收集到一个哈希图然后再转换回一个键集。有没有办法“简化”(双关语)这个操作来

    不会在流和 hashmap 之间来回切换 以某种方式引用自身,而无需在管道之前声明单独的映射。

谢谢!

【问题讨论】:

您的建议应该有效。但是,我在您的建议中看到了一个问题。你在哪里获取频率来检索密钥? Freq 不存在,这是代码示例 2 中的问题。我不知道是否有自我引用的方法,如果没有 - 那么 freq 必须像代码示例中一样单独声明1. 你可以使用collectingAndThen(),但不会更漂亮。 只是为了确保我得到你是这个 .filter(key-> freq.containsKey(freq.get(key)) 来检查值是否等于其中一个键或者我错过了了解一下 这永远不会是真的:freq.containsKey(freq.get(key))freq.get(key) 返回一个Long,映射的键是IntegerLong 对象永远不会等于 Integer 对象(原始 long 可能等于原始 int,但这是另一回事)。 【参考方案1】:

您的密钥集实际上是由您的input 组成的HashSet。所以,你应该使用临时存储,这样:

Set<Integer> freq = new HashSet<>(input);

并进一步计数,根据单个流管道中的值进行过滤

return (int) input
        .stream()
        .collect(Collectors.groupingBy(Function.identity(),
                Collectors.counting()))
        .values() // just using the frequencies evaluated
        .stream()
        .filter(count -> freq.contains(count.intValue()))
        .count();

【讨论】:

花费 CPU 时间和堆内存来创建冗余 HashSet,只是为了让两个操作显示为单个语句,这是一个值得怀疑的权衡。当结果甚至不是一个语句,而是两个语句时,它已经到了离奇的程度,就像以前一样。 @Holger 你能详细说明一下,为什么HashSet 是多余的?您是否试图暗示最初的List 可以用于执行contains 检查?或者containsKey + get 的性能相同,但内存消耗更低? 当然,HashMap 上的containsKeygetHashSet 上的contains 具有相同的性能(实际上,在OpenJDK 中,HashSet 是一个包装器围绕HashMap,所以contains 在引擎盖下调用containsKey)。通过流式传输values() 而不是keySet(),您获得了小幅改进,就像您在答案中所做的那样,无需为每个键调用get,但这不需要额外的freq 集。剩余的contains 调用与containsKey 调用相当。

以上是关于在不破坏流管道的情况下引用前面的 Java 流步骤?的主要内容,如果未能解决你的问题,请参考以下文章

MessagePack Java Jackson 在不关闭输出流(output stream)的情况下序列化多变量

MessagePack Java Jackson 在不关闭输入流(input stream)的情况下反序列化多变量

Java IO7:管道流对象流

Java使用PipedStream管道流通信

如何在不破坏封装的情况下返回对 RefCell 内某些内容的引用?

JAVA的IO编程:管道流