1

I have a list of Foo objects, where each Foo contains a name and a List<Strings>.

I am trying to

  1. find if the fooList contains a Foo object with a target name, and if such element exists,
  2. find if a target set of strings is a subset of the List stringList in the found Foo instance.

This is the code I have yet.

import java.util.List;

public class Foo {
    String fooName;
    List<String> bars;

    public Foo(String fooName, List<String> bars) {
        this.fooName = fooName;
        this.bars = bars;
    }

    public String getFooName() {
        return fooName;
    }

    public List<String> getBars() {
        return bars;
    }
}

    @Test
    public boolean demo() {

        List<Foo> fooList = new ArrayList<>();
        // add foos and bars

        String fooNameToFind = "some-target-name";

        Set<String> barsToFind = new HashSet<>();
        // add to list of target bar names

        Optional<Foo> optionalFoo = fooList.stream()
                .filter(foo -> foo.getFooName().equals(fooNameToFind))
                .findFirst();

        if (optionalFoo.isEmpty()) {
            // have to log something here
            Logger.getAnonymousLogger().info("target foo not found");
            return false;
        }

        boolean success = optionalFoo.get().getBars().stream()
                .filter(barsToFind::remove)
                .anyMatch(__ -> barsToFind.isEmpty());

        if (!success) {
            Logger.getAnonymousLogger().info("no match for list of bar names for given foo name");
        }

        return success;
    }

I wish to log whenever I don't find either the target name or the list subset. As you can see, I am currently using .isEmpty() and .get(), which I want to avoid. Please let me know if there is a better way to chain these.

TIA

EDIT: How about using peek?

de5tro
  • 11
  • 1
  • 4

2 Answers2

1

You are looking for Optional.map()

If a value is present, apply the provided mapping function to it, and if the result is non-null, return an Optional describing the result. Otherwise return an empty Optional.

and Optional.orElseGet()

Return the value if present, otherwise invoke other and return the result of that invocation.

public boolean demo() {
  //get optional foo
  return optionalFoo
          .map(foo -> {
            boolean result = foo.getBars()
                    .stream()
                    .filter(barsToFind::remove)
                    .anyMatch(__ -> barsToFind.isEmpty());
            if (!result) {
              //log stuff
            }
            return result;
          })
          .orElseGet(() -> {
            //log stuff
            return false;
          });
}
Chaosfire
  • 4,818
  • 4
  • 8
  • 23
  • Thank you for prompt response. Although this works perfectly, is there a way to avoid the big inner function? I was imagining a world where I could use flatmap to convert the optional list to stream. But was losing the result of the first optional. – de5tro Aug 11 '23 at 09:56
  • @de5tro There is no way, unless you get rid of the logging. It requires those intermediate results, without it the code will be more concise. – Chaosfire Aug 11 '23 at 10:32
  • 1
    @Chaosfire there is a way, but the only way I could think of is using `Optional.or`, added in Java 9. See my answer for an example. – Rob Spoor Aug 11 '23 at 12:09
  • @RobSpoor Good point, i didn't think of using `or()`. But if i were op i would just stick with his original approach, IMO it's the most readable option right now. – Chaosfire Aug 11 '23 at 12:21
  • @Chaosfire I agree that the individual pieces of logic are more readable. One small thing that might be confusing to the readers is what check is done first. This solution gives the illusion that the first //log stuff is checked first. – de5tro Aug 11 '23 at 21:18
1

Using Optional.stream() you can probably do this easier, also without having to remove anything from the set:

boolean success = fooList.stream() // Stream<Foo>
        .filter(foo -> foo.getFooName().equals(fooNameToFind)) // Stream<Foo>
        .findFirst() // Optional<Foo>
        .stream() // Stream<Foo> - either 0 or 1 element
        .filter(foo -> foo.getBars().containsAll(barsToFind)) // Stream<Foo>
        .findAny() // either the Foo from findFirst(), or empty if bars don't match
        .isPresent();

I've tested this with 2 Foo instances. One didn't have a matching name, the other had a matching name and contained all of the bars (plus some more). That one was found. I then added a bar that wasn't in the list, and I got no results.

With logging added:

boolean success = fooList.stream() // Stream<Foo>
        .filter(foo -> foo.getFooName().equals(fooNameToFind)) // Stream<Foo>
        .findFirst() // Optional<Foo>
        .or(() -> {
            Logger.getAnonymousLogger().info("target foo not found");
            return Optional.empty();
        })
        .stream() // Stream<Foo> - either 0 or 1 element
        .filter(foo -> foo.getBars().containsAll(barsToFind)) // Stream<Foo>
        .findAny() // either the Foo from findFirst(), or empty if bars don't match
        .or(() -> {
            Logger.getAnonymousLogger().info("no match for list of bar names for given foo name");
            return Optional.empty();
        })
        .isPresent();

These calls to or will only evaluate the supplier if the current Optional is empty, and replace the current empty Optional with the result of the supplier. By returning Optional.empty() this is effectively doing nothing but logging.

Rob Spoor
  • 6,186
  • 1
  • 19
  • 20
  • this is what i was looking for! A small performance boost in case the barList is too big can be achieved using the anyMatch() logic. https://stackoverflow.com/questions/58269632/is-there-a-way-to-check-if-a-stream-contains-all-collection-elements – de5tro Aug 11 '23 at 21:16
  • One small problem I found that both .or() statements get called even if the Optional is empty. It would result in logs that are not essentially true. – de5tro Aug 11 '23 at 21:48
  • @de5tro not true. The argument to `or` is a `Supplier` that will only be evaluated if the original `Optional` is empty. – Rob Spoor Aug 12 '23 at 10:49
  • yes i understand that. I mean that when the original `Optional` is empty, both of the `Supplier`s in the `.or()` will get evaluated. This will result in two log statements. Ideally, the second one shouldn't be evaluated. It should only be evaluated if the original `Optional` is non empty and the result of the `.filter((foo) -> {foo.getBars.containAll()})` is false. – de5tro Aug 12 '23 at 21:21