Continue mapping after stream collect

Viewed 3234

How do I continue the stream mapping after collect:

public Bus createBus(List<String> passengers) {
   return passengers.stream()
             .map(name -> new Person(name))
             .collect(Collectors.toList())
             .map(personList -> new Bus(personList))
}

Above obviously does not work. How to make it work elegantly without using any variables in the method body?

Edit: Constructor is Bus(List persons).

5 Answers

You could use the Collector from Collectors.collectingAndThen to perform an additional finishing transformation :

public Bus createBus(List<String> passengers) {
    return passengers.stream()
            .map(name -> new Person(name))
            .collect(Collectors
                    .collectingAndThen(Collectors.toList(), Bus::new));
}

Since collect returns a collection, you need to open a new stream on the new collected list:

 list.stream().map(name -> new Person(name))
         .collect(Collectors.toList())
         .stream()....

or simply remove the collect: list.stream().map(name -> new Person(name)).map(...). That would make much more sense in your case

map will iterate over each Person in your list. Your Bus, instead, seems to need the whole list as a parameter. As it is, you try to create a new Bus for each name in the passengers list.

You can either pass the collected list directly to the constructor:

public Bus createBus(List<String> passengers) {
   return new Bus(passengers.stream()
             .map(name -> new Person(name))
             .collect(Collectors.toList()));
}

or store it in a variable (which I think is clearer):

public Bus createBus(List<String> passengers) {
   List<Person> persons = passengers.stream()
             .map(name -> new Person(name))
             .collect(Collectors.toList());
   return new Bus(persons);
}

Assuming the Bus has a List of Person objects:

public Bus createBus(List<String> passengers) {
   return new Bus(passengers.stream().map(name -> new Person(name)).collect(Collectors.toList()));
}


Another option:

public Bus createBus(List<String> passengers) {
    return new Bus(getPersons(passengers));
}

private List<Person> getPersons(List<String> passengers) {
    return passengers.stream().map(name -> new Person(name)).collect(Collectors.toList());
}

I believe this can be achieved in such a way if you don't want any extra variables being introduced:

    public Bus createBus(List<String> passengers) {
         return passengers.stream()
                .map(Person::new)
                .collect(Collectors.collectingAndThen(Collectors.toList(), Stream::of))
                .map(Bus::new)
                .reduce((first, second) -> second)
                .orElseThrow();
    }

Collecting creates one set, which is then converted to a stream again. Stream of 1 element in this case. However it is still a stream, so if you want to return just one Bus (not a list of Bus), it has to be reduced somehow. Here I just reduced it to one as the stream will have one element anyway.

reduce operation returns Optional<Bus>, so you have to check if it's there, when you get it. For simplicity, this code will just throw, when Optional is empty.

Related