Search code examples
javacollectionsjava-streamside-effectsjava-16

Java-Stream - Using filter and forEach for populating a Set


I have a Set<Person> and I need to filter the content and then populate another set out of the given set.

I am fairly new to streams.

The first solution that I can think of is chaining stream().filter().foreach()

Solution 1:

private Set<Person> mapPerson(final Set<Student> student) {
        final Set<Person> person = new HashSet<>();
        student.stream()
            .filter(student1 -> Objects.nonNull(student.getAge()))
            .forEach(student1 -> person.add(Person.builder()
                .id(student1.getId())
                .status(student1.getStatus()))
                .build()));

        return person;
    }

But I am not sure if this can somehow be done by chaining stream().filter().map() ?

Would appreciate any help and also if it is possible to do it both ways, which is the preferred way?


Solution

  • For that, you don't need to create a set manually and then mutate it. You can obtain a set as a result of the execution of the stream pipeline.

    can somehow be done by chaining stream().filter().map() ?

    First transform a stream of students Stream<Student> into a stream of person objects Stream<Person> by applying the map() operation, which expects a function that turns the element of the stream into another object of the desired type. So you need to place the logic for creation of a Person object that was used inside forEach() into map().

    And then apply collect passing as parameter Collectors.toSet().

    private Set<Person> mapPerson(final Set<Student> students) {
    
      return students.stream()
              .filter(student -> Objects.nonNull(student.getAge()))
              .map(student -> Person.builder()
                              .id(student.getId())
                              .status(student.getStatus())
                              .build())
              .collect(Collectors.toSet());
    }
    

    if it is possible to do it both ways, which is the preferred way?

    The approach you are using is discouraged by the documentation. Have a look at the quote below (emphasize is mine).

    As an example of how to transform a stream pipeline that inappropriately uses side-effects to one that does not, the following code searches a stream of strings for those matching a given regular expression, and puts the matches in a list.

     ArrayList<String> results = new ArrayList<>();
     stream.filter(s -> pattern.matcher(s).matches())
           .forEach(s -> results.add(s));  // Unnecessary use of side-effects!
    

    ... the forEach() can simply be replaced with a reduction operation that is safer, more efficient, and more amenable to parallelization:

     List<String> results =
         stream.filter(s -> pattern.matcher(s).matches())
               .toList();  // No side-effects!
    

    You shouldn't utilize forEach (as well as peek) which could operate only by causing side effects in situations when there's an alternative way to achieve the same result.

    In this case, you can do it with a collect() (as shown above) or by using a plain for loop.