我有一个昂贵的方法,我只想在流中必要时调用它。下面是一个示例:
public static Optional<MyObject> findTarget(String input, List<MyObject> myList) {
return Stream.concat(myList.stream(), expensive().stream()).filter(o -> o.hasName(input)).findFirst();
}
目标是根据输入
值从myList
中找到目标MyObject
,但如果它不在myList
ONLY中,那么它将调用贵()
返回一个更大的列表并从那里查看。
上面的示例并没有做到这一点,因为似乎在使用所有myList
之前,Stream.contat
已经调用了expensive()
。
我能想到的一个丑陋的解决方案是分两步进行,例如:
return myList.stream().filter(o -> o.hasName(input)).findFirst().or(
() -> expensive().stream().filter(o -> o.hasName(input)).findFirst());
但是,我将不得不重复过滤器和其余的两次。
有没有更好的解决方案,甚至是单个 Stream 班轮可以做到这一点?
您可以通过连接< code >供应商来进行延迟评估
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();
}
另一种可能更容易理解的方法是用一个单独的方法提取流逻辑:
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()));
}
是的,你提供的例子是正确的。< code>Stream.concat()方法将在使用所有< code>myList之前调用< code>expensive()方法,因此它不会像您希望的那样高效。
另一种解决方案是将 Stream.concat()
方法与供应商函数而不是 stream 一起使用。供应商函数仅在第一个流耗尽时调用,因此它只会在必要时调用昂贵的方法。下面是一个示例:
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();
}
这样,在将<code>myList</code>流与昂贵的流连接之前,将过滤器方法应用于该流,这意味着只有在<code>myList</code>流中找不到目标时才会调用昂贵的方法。
另一种方法是使用< code>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();
}
它与第一个类似,但它更明确,更具可读性。
这两种解决方案都应该比您提供的初始示例更有效。