1

I would like to move some method into java8 functional style.

public static String foo(List<String> arguments, List<String> conditions) {
    for (String s : conditions) {
        for (String argument : arguments) {
            if (argument.contains(s)) {
                return argument;
            }
        }
    }
    return "";
}

My current idea is:

public static String fooFunctional(List<String> arguments, List<String> conditions) {
    return conditions.stream()
            .flatMap(condition -> arguments.stream()
                    .filter(argument -> argument.contains(condition))
                    .findFirst()
                    .map(Stream::of)
                    .orElseGet(Stream::empty))
            .findFirst()
            .orElse("");
}

Is there any shorter version then proposed above?

kokodyn
  • 254
  • 2
  • 9

2 Answers2

1

UPDATE

As noticed by @Holger in the comments, a simpler and Java 8 compatible approach:

conditions.stream()
        .flatMap(condition -> arguments.stream()
                .filter(argument -> argument.contains(condition)))
        .findFirst().orElse("");

Java 8:

conditions.stream()
        .map(condition -> arguments.stream()
                .filter(argument -> argument.contains(condition))
                .findFirst())
        .filter(Optional::isPresent)
        .findFirst().map(Optional::get).orElse("");

Java 9+:

You can use Optional.stream():

conditions.stream()
        .flatMap(condition -> arguments.stream()
                .filter(argument -> argument.contains(condition))
                .findFirst().stream())
        .findFirst().orElse("");
Oboe
  • 2,643
  • 2
  • 9
  • 17
  • You should not use `orElseGet` when the parameter given to `orElse` is a constant like `""`. That just increases the overhead unnecessarily. – marstran Nov 10 '21 at 22:50
  • Optional.stream() is not present in java8 which is expected to be used. – kokodyn Nov 10 '21 at 22:56
  • 1
    Your first option is wrong. The `.orElse("")` in the `map` function ensures that it can never fail, as it will return an empty string when there is no match. So it will never process more than the first `condition`. • The “Java 9+” variant is unnecessarily complicated. The `.findFirst().stream()` has the same effect as `limit(1)` but even that is obsolete; the outer stream’s `findFirst` will take only the first match anyway. So you can simplify the inner stream to `stream().filter(…)`, which makes it a Java 8 compatible solution. – Holger Nov 11 '21 at 09:36
  • @Holger you are right, never thought about `.limit(1)` as an alternative to `.findFirst().stream()`. I updated the answer to incorporate your comment. BTW, do you know if there is any performance difference between `.findFirst().stream()` and `.limit(1)`? – Oboe Nov 11 '21 at 13:30
  • @Holger your approach is not full compatible because it iterating over all arguments, instead of finding first matching. Please look at the example when we add printout it in filter method: ' .filter(argument -> { System.out.println("filtering"); return argument.contains(condition); }))' given arguments 'Arrays.asList("x", "a", "c")' given conditions 'Arrays.asList("a", "b", "c")' We can see on console: filtering filtering filtering So filtering is done 3x instead of 2x. – kokodyn Nov 11 '21 at 22:37
  • 2
    @kokodyn you asked about *functional style* and functional style is precisely about *expressing the intent* and not discussing when which function will be evaluated. The approach is correct when the order of evaluation doesn’t matter for getting the correct result. What you have demonstrated, is just a well known issue of one particular Stream implementation, see “[Why filter() after flatMap() is "not completely" lazy in Java streams?](https://stackoverflow.com/q/29229373/2711488)”. But this is only a minor performance issue not affecting the correct result and even fixed in newer versions. – Holger Nov 12 '21 at 08:41
0

If it's not important which exact argument you return as long as at least one condition is contained in it, then you can get it a bit shorter by streaming over arguments before conditions. Like this:

return arguments.stream()
    .filter(argument -> conditions.stream()
        .anyMatch(condition -> argument.contains(condition)))
    .findFirst()
    .orElse("");
marstran
  • 26,413
  • 5
  • 61
  • 67
  • @marstran Original method behaviour should not be changed. I would like to have full backward compatibility. – kokodyn Nov 10 '21 at 22:53