Java流和惰性集合

oknrviil  于 2023-05-05  发布在  Java
关注(0)|答案(2)|浏览(118)

我有一个方法,它通过执行一些代价高昂的操作(例如:使用数据库来通过ID解析对象)。
让我们使用这个方法作为一个例子:

public static Collection<Integer> getInts()
{
    return IntStream.range(1, 100)
            .peek((value) -> System.out.printf("getInts(): iterating over [%d]\n", value))
            .boxed()
            .collect(Collectors.toList());
}

然后我有另一个方法,它检查集合中是否存在与 predicate 匹配的对象:

public static boolean primeExists()
{
    return getInts().stream()
        .peek((value) -> System.out.printf("primeExists(): iterating over [%d]\n", value))
        .anyMatch(Main::isPrime); // checks if there is a prime number
}

通过运行这段代码,我注意到getInts()方法中的peek打印了10次,每个数字打印一次,尽管primeExists()实际上只需要前两个元素。
下面是一个完整的代码示例:

public class Main
{
    private static boolean isPrime(int n)
    {
        if (n < 2)
        {
            return false;
        }

        for (int i = 2; i * i <= n; i++)
        {
            if (n % i == 0)
            {
                return false;
            }
        }

        return true;
    }

    public static Collection<Integer> getInts()
    {
        return IntStream.range(1, 10)
                .peek((value) -> System.out.printf("getInts(): iterating over [%d]\n", value))
                .boxed()
                .collect(Collectors.toList());
    }

    public static boolean primeExists()
    {
        return getInts().stream()
                        .peek((value) -> System.out.printf("primeExists(): iterating over [%d]\n", value))
                        .anyMatch(Main::isPrime);
    }

    public static void main(String[] args) throws IOException
    {
        primeExists();
    }
}

这是输出:

getInts(): iterating over [1]
getInts(): iterating over [2]
getInts(): iterating over [3]
getInts(): iterating over [4]
getInts(): iterating over [5]
getInts(): iterating over [6]
getInts(): iterating over [7]
getInts(): iterating over [8]
getInts(): iterating over [9]
primeExists(): iterating over [1]
primeExists(): iterating over [2]

如何更改此代码以获得此输出:

getInts(): iterating over [1]
getInts(): iterating over [2]
primeExists(): iterating over [1]
primeExists(): iterating over [2]

有没有可能在不更改getInts()方法以返回中间Stream的情况下获得这种行为?
我认为理想的解决方案是从getInts()返回一个惰性集合(例如:其元素仅在实际迭代时创建的集合)。有没有一种方法可以在Java中创建这样的集合?

ibrsph3r

ibrsph3r1#

好吧,我已经回答了问题。但让我们提供一大堆上下文:
我认为理想的解决方案是从getInts()返回一个惰性集合(例如:其元素仅在实际迭代时创建的集合)。有没有一种方法可以在Java中创建这样的集合?
你对术语感到困惑。通常在官方定义的系统中(编程语言的定义肯定比通常的人与人之间的对话要具体得多),术语获得了更具体,更详细的定义含义。
在你的头脑中,“收藏”可能是懒惰的,为什么不呢,对吗?收藏是一个词,在字典里,字典里没有什么规定它必须是渴望的。
但是在java中,* j.u.Collection意味着eager*。要求一个懒惰的收集,“收集”在那里指的是英语单词,这是有道理的。要求'一个懒惰的集合',其中集合具体引用java.util.Collection没有意义。这就像要求:“一个圆,但有一个角。这是矛盾修饰法考虑到你是在java的土地上,使用单词'collection'并打算将其作为字典单词而不是j.u.Collection是一个错误:语言是用来交流思想的,如果语言不能很好地表达思想,那你就选择了不好的语言。很自然地,试图消除“collection - in the sense of英语dictionary”和“collection - in the sense of j.u. Collection”的歧义几乎是不可能的,所以找另一个词来表达这个概念。
那么,java的官方术语是什么呢?Stream
换句话说,你有一个方法,它的返回类型是“EagerCollection”(除了在java中我们只称之为Collection),并要求:有没有办法让你不那么急切?嗯,不。规格是字面上告诉你,它必须是渴望。
考虑一个假设的“懒惰集合”的API,并通过j.u.Collection的API来获得灵感,这是没有意义的。许多API需要:

  • 因为没有意义/有我们永远不想暴露的讨厌的副作用而被完全删除。
  • 记录它“消耗”集合,无法返回,和/或具有荒谬的副作用,例如花费一个小时并分配2GB的RAM。
  • 一般来说你不会感兴趣的。
  • 需要扔东西。
  • 需要返回具有奇怪含义的神奇值,例如size()被指定为返回-1表示无限集合,返回-2表示'I have no idea,I might even be infinite'。
  • 将概念束缚在消除这样一个概念应用于用例的能力上,即使它们感觉是正确的(例如添加一个束缚,即惰性集合仍然需要**以快速和非破坏性的方式准确地报告size(),这意味着您不能制作一个惰性集合来表示通过网络传输的数据)。

这就是为什么Stream存在:需要不同的API。这就是 * 为什么 * java.util.Collection(特别是java.util.List)本质上意味着“渴望”。即使有一些东西(实际上,只有iterator(),几乎所有其他方法都是无意义的,或者不能以快速和非破坏性的方式实现)也可以应用于“懒惰集合”。
作为一个简单的例子,.get(1000)通常涉及以下两种销毁:(我可以给予你第1000个元素,但这样做,元素0-999被消耗,永远不会再返回,所以如果你之后调用.get(500),将不得不发生异常),或缓存(我将分配一些内存并提取1000个元素,缓存0-999,这样.get(500)以后就可以工作了,尽管它是从缓存中抓取的)。
相反,在流中,你会写.skip(1000).findFirst(),它同样简单,而且它实际上做的事情要准确得多-而对于.get(1000),我不知道这是否会创建缓存或崩溃,或者它是如何工作的,我必须阅读文档,对于.skip(1000).findFirst(),从那一点点文本中就可以明显看出:这将破坏元素0-999(从源中取出它们并丢弃它们,我永远无法返回到这些)。

l2osamch

l2osamch2#

您需要返回Stream而不是Collection。因为在生成集合时,它将从流中读取所有值。所以pick被调用了10次。
更新程序。

public class Main {

    public static boolean isPrime(Integer n) {
        if (n < 2) {
            return false;
        }

        for (var i = 2; (i * i) <= n; i++) {
            if ((n % i) == 0) {
                return false;
            }
        }

        return true;
    }

    public static Stream<Integer> getInts() {
        return IntStream.range(1, 10).peek((value) -> System.out.printf("getInts(): iterating over [%d]\n", value))
                .boxed();
    }

    public static boolean primeExists() {
        return getInts().peek((value) -> System.out.printf("primeExists(): iterating over [%d]\n", value))
                .anyMatch(Main::isPrime);
    }

    public static void main(String[] args) throws IOException {
        primeExists();
    }

}

输出:

getInts(): iterating over [1]
primeExists(): iterating over [1]
getInts(): iterating over [2]
primeExists(): iterating over [2]

因为它将获取一个元素并处理它,所以它将为每个元素打印getInts()primeExists()

相关问题