Resume processing of a Java Stream after running findFirst()

568 Views Asked by At

Given a simple list of strings:

List<String> strings = Arrays.asList("Mary", "James", "Jim", "Camilla");

I'd like to process each item of the list with a anotherMethod() method, however the first item shall be processed additionally by someMethod().

I've tried something like this:

List<String> strings = Arrays.asList("Mary", "James", "Jim", "Camilla");

var result = strings.stream()
     .findFirst()
     .map(myString -> someMethod(myString))
     // "resume" stream
     .map(myString -> anotherMethod(myString))
     .toList();     

Is there a way to handle such a scenario using a Stream or something similar?


Update 1

Here is an example to help you better understand the motivation behind my question.

List<String> strings = Arrays.asList("Mary", "James", "Jim", "Camilla");

public String someMethod(String s) {
  return s.toUpperCase();
}

public String anotherMethod(String s) {
  return s.toLowerCase();
}

The final result shall be a List<String> with the following elements, that is, only the first element is converted to upper-case while all other elements are converted to lower-case.

"MARY", "james", "jim", "camilla"

5

There are 5 best solutions below

0
On BEST ANSWER

findFirst collapses the stream to an Optional<String>, and there is nothing to resume.

Optional<String> mary = Stream.of("Mary", "James", "Jim", "Camilla").findFirst();

Do you want to reduce the stream to only one element? Then the method limit(1) might be what you are looking for and then concat it with a copy where you skip(1) the element.

List<String> strings = Arrays.asList("Mary", "James", "Jim", "Camilla");
List<String> result = Stream.concat(
      strings.stream()
        .limit(1)
        .map(this::someMethod),
      strings.stream()
        .skip(1))
        .map(this::anotherMethod)
     ).toList();

Edit: After Update 1:

You wouldn't want to apply toLowerCase() to the first element after you applied toUpperCase().

7
On

Optional.stream()

Your idea of using findFirst() is perfectly viable.

We can apply map() on the Optional result and make use of the Java 9 Optional.stream() (to "resume" the stream).

flatMap() operation in conjunction with Stream.concat() would be handy for combining the very first element with the rest part of the stream.

Apply the final transformation and collect the result into a list.

List<String> strings = Arrays.asList("Mary", "James", "Jim", "Camilla");

List<String> result = strings.stream().findFirst()
    .map(MyClass::someMethod)
    .stream()
    .flatMap(s -> Stream.concat(
        Stream.of(s), 
        strings.stream().skip(1).map(MyClass::anotherMethod)
    ))
    .toList();

Iterator.forEachRemaining()

Another option would be to use Java 8 Iterator.forEachRemaining():

List<String> strings = Arrays.asList("Mary", "James", "Jim", "Camilla");
List<String> result = new ArrayList<>();

Iterator<String> iterator = strings.iterator();
result.add(someMethod(iterator.next()));
iterator.forEachRemaining(s -> result.add(anotherMethod(s));
0
On

You could create an IntStream over the indexes.

IntStream.range(0, strings.size())
    .mapToObj(i -> {
        var s = strings.get(i);
        if (i == 0) s = someMethod(s);
        return anotherMethod(s);
    }).toList();
0
On

Try StreamEx or abacus-common (I'm the developer of this library)

var result = StreamEx.of(strings)
           .mapFirstOrElse(String::toUpperCase, String::toLowerCase)
           .toList();

result.forEach(System.out::println); 
0
On
 final AtomicBoolean processFirstElement = new AtomicBoolean(true);
        strings.stream()
                .map(input->processFirstElement.getAndSet(false)?
                                someMethod(input)
                                :input)
                .map(YourClass::anotherMethod)
                .collect(Collectors.toList());

Ideally we should avoid using mutable objects,AtomicBoolean should be able to maintain happens-before and hence prevent racing condition.