Home > OS >  Perform actions with streaming api before/after foreach
Perform actions with streaming api before/after foreach

Time:03-24

Here's the non-stream code that I want to reproduce using only the streaming api if possible:

List<String> list = Arrays.asList("one", "two", "three", "four");
list.removeIf(s -> s.length() <= 3);

if (!list.isEmpty()) {
    System.out.println("before");
    for (String elem : list) {
        System.out.println(elem);
    }
    System.out.println("after");
}

Of course simple iteration is easy with a stream:

List<String> list = Arrays.asList("one", "two", "three", "four");
list.stream()
        .filter(s -> s.length() > 3)
        .forEach(System.out::println);

But I need to add the "before" and "after" lines only if the stream is not empty after filtering. Is this possible in one statement or do I need to collect?

This hardly seems like a better solution...

List<String> list = Arrays.asList("one", "two", "three", "four");
List<String> filtered = list.stream()
        .filter(s -> s.length() > 3)
        .collect(Collectors.toList());
if (!filtered.isEmpty()) {
    System.out.println("before");
    filtered.stream().forEach(System.out::println);
    System.out.println("after");
}

Update: The examples above with Strings are too simple. I actually need to write a json array if the filtered list is not empty. I cannot write an empty array.

private void writeJsonArray(JsonGenerator g, List<String> list) {
    list.removeIf(s -> s.length() <= 3);
    
    if (!list.isEmpty()) {
        g.writeArrayFieldStart("arr");
        for (String elem : list) {
            g.writeString(elem);
        }
        g.writeEndArray();
    }
}

CodePudding user response:

You can use anyMatch() and Collectors.joining() with prefix and suffix:

private static String NEW_LINE = System.lineSeparator();

public static void print(List<String> list) {
    String str = list.stream().anyMatch(s -> s.length() > 3) 
        ? list.stream().filter(s -> s.length() > 3)
                .collect(Collectors.joining(NEW_LINE, 
                        "before"   NEW_LINE, NEW_LINE   "after"))
        : "";
    System.out.println(str);
}

As an alternative approach, you can reduce the stream using StringBuilder as identity. Then, if the StringBuilder isn't empty, you can insert "before" and append "after". This approach performs only one terminal operation.

public static void print(List<String> list) {
    StringBuilder sb = list.stream()
            .filter(str -> str.length() > 3)
            .reduce(new StringBuilder(), 
                    (s, str) -> s.append(str).append(NEW_LINE), 
                    StringBuilder::append);
    if (!sb.isEmpty()) {
        sb.insert(0, NEW_LINE).insert(0, "before")
            .append("after");
    }
    
    System.out.println(sb.toString());
}

Test:

List<String> list = Arrays.asList("one", "two", "three", "four");

print(list);

Output:

before
three
four
after

CodePudding user response:

I would use Stream.concat for this:

Stream.concat(
    Stream.of("before"),
    Stream.concat(
        list.stream().filter(s -> s.length() > 3),
        Stream.of("after")))
            .forEach(System.out::println);

You might find it more readable split the logic into separate statements:

Stream<String> stream = list.stream().filter(s -> s.length() > 3);
stream = Stream.concat(Stream.of("before"), stream);
stream = Stream.concat(stream, Stream.of("after"));
stream.forEach(System.out::println);
  • Related