3

I have two HashSets with same type of objects. My search criteria is like, search in first set and if not present then search in another set. I had tried with Stream layer with following steps as given below

Set<MyObject> firstSet = new HashSet<>();
Set<MyObject> secondSet = new HashSet<>();

and these two sets are having some values.

Predicate<MyObject> match = myObject -> StringUtils.equals(myValue, myObject.getMyValue());

firstSet.values().stream().filter(match).findFirst()
.orElse(secondSet.values().stream().filter(match)
.findFirst().orElseThrow(()-> new MyException()));

My matching object is in First Set and i have tried to get it by manually and i got it... but using the above iteration, i always get the exception even when the first set has the matched object. Kindly correct me.. thanks is advance.

Holger
  • 285,553
  • 42
  • 434
  • 765
Jijesh Kumar
  • 299
  • 2
  • 13
  • 1
    When you pass a parameter to a method, the parameter gets evaluated first. In the outer (i.e. first) orElse, the parameter to that orElse is `secondSet.stream().values().filter(match) .findFirst().orElseThrow(()-> new MyException())`, which gets evaluated before anything else on the last three lines. – Albert Hendriks Oct 07 '20 at 19:42

1 Answers1

3

Your problem is that you are not using Optional.orElse as expected.

When you use Optional.orElse, its parameter is evaluated eagerly. This means that your second set is being searched first, to resolve the parameter of your first set's Optional.orElse.

Instead, use Optional.orElseGet, which receives a Supplier that is evaluated lazily:

firstSet.stream()
    .filter(match)
    .findFirst()
    .orElseGet(() -> secondSet.stream()
        .filter(match)
        .findFirst()
        .orElseThrow(()-> new MyException()));

EDIT: As suggested by Holger in the comments, there is an easier way:

Stream.of(firstSet, secondSet)
    .flatMap(Set::stream)
    .filter(match)
    .findFirst()
    .orElseThrow(MyException::new);

Streaming the sets first and then calling flatMap ensures that elements of the first set will all appear before elements of the second set.

fps
  • 33,623
  • 8
  • 55
  • 110
  • 1
    thank you for your answer... i will try this now – Jijesh Kumar Oct 07 '20 at 20:30
  • 1
    this is working now.... thanks alot.. – Jijesh Kumar Oct 07 '20 at 20:33
  • 3
    There is no `values()` method in a `Set`. Besides that, just `Stream.concat(firstSet.stream(), secondSet.stream()) .filter(match) .findFirst() .orElseThrow(MyException::new)` will do the job. – Holger Oct 08 '20 at 07:28
  • @Holger Fixed `.values()` thing, thanks. I thought in a code similar to yours, and if they were lists I would have done it that way. But they are sets. Are we 100% sure by the spec that, after calling `Stream.concat` on two streams whose source are sets, elements from the second set will appear after all the elements of the first set? – fps Oct 08 '20 at 12:14
  • 3
    Taking the spec as written, you can’t be sure. So, if you want to be on the safe side, you’d rather use `Stream.of(firstSet, secondSet) .flatMap(Set::stream) .filter(match) .findFirst() .orElseThrow(MyException::new)` which is guaranteed to be ordered as intended. – Holger Oct 08 '20 at 12:17