I have an expensive method that I only want to call it when necessary in a stream. Here is an example:
public static Optional<MyObject> findTarget(String input, List<MyObject> myList) {
return Stream.concat(myList.stream(), expensive().stream()).filter(o -> o.hasName(input)).findFirst();
}
The goal is to find the target MyObject from myList based on the input value, but if its not in myList ONLY then it will call expensive() to return a bigger list and look from there.
The above example does not do that, as it seems Stream.concat will call expensive() already before consuming all of myList.
An ugly solution I can think of is to do it in two steps, e.g.:
return myList.stream().filter(o -> o.hasName(input)).findFirst().or(
() -> expensive().stream().filter(o -> o.hasName(input)).findFirst());
But then I will have to repeat the filter and the rest twice.
Is there any better solution or even a single liner of Stream that does that?
You can lazily evaluate by concatenating Supplier<List<MyObject>> instead of 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();
}
Test:
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));
}
Output:
case 1: Optional[MyObject[s=a]]
expensive() called
case 2: Optional.empty
Alternatively you can do this:
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();
}
Another alternative, which might be simpler to understand, is to extract the stream logic in a separate method:
private static Optional<MyObject> findInternal(String input, List<MyObject> myList) {
return myList.stream().filter(o -> o.hasName(input)).findFirst();
}
and then simply call it twice:
public static Optional<MyObject> findTarget(String input, List<MyObject> myList) {
return findInternal(input, myList).or(() -> findInternal(input, expensive()));
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With