Lazily调用java流中的方法

ccgok5k5  于 2023-01-19  发布在  Java
关注(0)|答案(3)|浏览(140)

我有一个开销很大的方法,我只想在流中必要的时候调用它。下面是一个例子:

public static Optional<MyObject> findTarget(String input, List<MyObject> myList) {
    return Stream.concat(myList.stream(), expensive().stream()).filter(o -> o.hasName(input)).findFirst();
}

目标是基于input值从myList中找到目标MyObject,但如果它不在myList ONLY中,则它将调用expensive()以返回更大的列表并从那里查找。
上面的示例没有这样做,因为Stream.concat似乎在使用所有myList之前就已经调用了expensive()
我能想到的一个丑陋的解决方案是分两步来做,例如:

return myList.stream().filter(o -> o.hasName(input)).findFirst().or(
    () -> expensive().stream().filter(o -> o.hasName(input)).findFirst());

但之后我将不得不重复过滤器和其余的两次。
有没有更好的解决方案,甚至是一个单一的流行这样做?

u2nhd7ah

u2nhd7ah1#

您可以通过连接Supplier<List<MyObject>>而不是List<MyObject>来延迟求值。

public static Optional<MyObject> findTarget(String input, List<MyObject> myList) {
    List<Supplier<List<MyObject>>> concat = List.of(() -> myList, () -> expensive());
    return concat.stream()
        .flatMap(supplier -> supplier.get().stream())
        .filter(o -> o.hasName(input))
        .findFirst();
}

试验:

record MyObject(String s) {
    public boolean hasName(String in) {
        return s.equals(in);
    }
}

static List<MyObject> expensive() {
    System.out.println("expensive() called");
    return List.of(new MyObject("z"));
}

public static void main(String[] args) {
    List<MyObject> myList = List.of(new MyObject("a"));
    System.out.println("case 1: " + findTarget("a", myList));
    System.out.println("case 2: " + findTarget("x", myList));
}

输出:

case 1: Optional[MyObject[s=a]]
expensive() called
case 2: Optional.empty

或者,您可以执行以下操作:

public static Optional<MyObject> findTarget(String input, List<MyObject> myList) {
    return Stream.of(
            (Supplier<List<MyObject>>) () -> myList,
            (Supplier<List<MyObject>>) () -> expensive())
        .flatMap(supplier -> supplier.get().stream())
        .filter(o -> o.hasName(input))
        .findFirst();
}
uxhixvfz

uxhixvfz2#

是的,您提供的示例是正确的。Stream.concat()方法将在使用所有myList之前调用expensive()方法,因此它不会像您希望的那样高效。
另一种解决方案是使用带有supplier函数的Stream.concat()方法,而不是使用流。supplier函数只有在第一个流耗尽时才会被调用,因此它只会在必要时调用开销大的方法。下面是一个示例:

public static Optional<MyObject> findTarget(String input, List<MyObject> myList) {
    return Stream.concat(
                myList.stream().filter(o -> o.hasName(input)),
                () -> expensive().stream().filter(o -> o.hasName(input))
            ).findFirst();
}

这样,filter方法在将myList流与昂贵流连接之前应用于myList流,这意味着仅当在myList流中未找到目标时才调用昂贵方法。
另一种方法是使用Stream.concat()和一个标志来检查是否调用了开销大的方法。

public static Optional<MyObject> findTarget(String input, List<MyObject> myList) {
    AtomicBoolean flag = new AtomicBoolean(false);
    return Stream.concat(
                myList.stream().filter(o -> o.hasName(input)),
                flag.get() ? Stream.empty() : expensive().stream().peek(e->flag.set(true)).filter(o -> o.hasName(input))
            ).findFirst();
}

它与第一个类似,但更明确,可读性更强。
这两种解决方案都应该比您提供的初始示例更有效。

mi7gmzs6

mi7gmzs63#

另一种可能更容易理解的替代方案是在单独的方法中提取流逻辑:

private static Optional<MyObject> findInternal(String input, List<MyObject> myList) {
    return myList.stream().filter(o -> o.hasName(input)).findFirst();
}

然后简单地调用两次

public static Optional<MyObject> findTarget(String input, List<MyObject> myList) {
    return findInternal(input, myList).or(() -> findInternal(input, expensive()));
}

相关问题