Java stream, replace foreach by collect

Is there a better way to fill my zoo with Java 8 stream ?

I guess there may be a solution using map / flatMap / collect ?

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class Main {

    public static void main(String args[])
    {
        List<Animal> zoo = new ArrayList<>();

        List<String> animals = new ArrayList<>();
        animals.add("cat");
        animals.add("dog");
        animals.add("donkey");

        Map<String, List<String>> houses = new ConcurrentHashMap<>();
        houses.put("cat", Arrays.asList("white house", "black house"));
        houses.put("dog", Arrays.asList("blue house"));
        houses.put("donkey", Arrays.asList("black house"));

        Map<String, List<String>> planets = new ConcurrentHashMap<>();
        planets.put("white house", Arrays.asList("earth", "mars"));
        planets.put("green house", Arrays.asList("earth", "jupiter"));
        planets.put("blue house", Arrays.asList("jupiter", "mars"));
        planets.put("black house", Arrays.asList("mars"));

        animals.parallelStream().forEachOrdered(s ->
                {
                    houses.get(s).parallelStream().forEachOrdered(s1 ->
                            {
                                System.out.println(s1);
                                planets.get(s1).parallelStream().forEachOrdered(s2 ->
                                        {
                                            zoo.add(new Animal(s, s1, s2));
                                        }
                                );
                            }
                    );
                }
        );

        System.out.println(zoo);

    }

    static  class Animal {
        private String name;
        private String house;
        private String planet;

        Animal(String name, String house, String planet) {
            this.name = name;
            this.house = house;
            this.planet = planet;
        }

    }
}

Try following solution:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

public class Main {

    public static void main(String args[]) {
        final List<String> animals = new ArrayList<>();
        animals.add("cat");
        animals.add("dog");
        animals.add("donkey");

        final Map<String, List<String>> houses = new ConcurrentHashMap<>();
        houses.put("cat", Arrays.asList("white house", "black house"));
        houses.put("dog", Arrays.asList("blue house"));
        houses.put("donkey", Arrays.asList("black house"));

        final Map<String, List<String>> planets = new ConcurrentHashMap<>();
        planets.put("white house", Arrays.asList("earth", "mars"));
        planets.put("green house", Arrays.asList("earth", "jupiter"));
        planets.put("blue house", Arrays.asList("jupiter", "mars"));
        planets.put("black house", Arrays.asList("mars"));

        final List<Animal> zoo = animals.parallelStream()
                .map(animal -> houses.getOrDefault(animal, Collections.emptyList())
                        .parallelStream()
                        .map(house -> planets.getOrDefault(house, Collections.emptyList())
                                .parallelStream()
                                .map(planet -> new Animal(animal, house, planet))
                                .collect(Collectors.toList())
                        )
                        .flatMap(Collection::stream)
                        .collect(Collectors.toList())
                )
                .flatMap(Collection::stream)
                .collect(Collectors.toList());

        zoo.forEach(System.out::println);
    }

    static final class Animal {
        private final String name;
        private final String house;
        private final String planet;

        Animal(String name, String house, String planet) {
            this.name = name;
            this.house = house;
            this.planet = planet;
        }

        @Override
        public String toString() {
            return "Animal{" +
                    "name='" + name + '/'' +
                    ", house='" + house + '/'' +
                    ", planet='" + planet + '/'' +
                    '}';
        }
    }
}

It prints following output to console:

Animal{name='cat', house='white house', planet='earth'}
Animal{name='cat', house='white house', planet='mars'}
Animal{name='cat', house='black house', planet='mars'}
Animal{name='dog', house='blue house', planet='jupiter'}
Animal{name='dog', house='blue house', planet='mars'}
Animal{name='donkey', house='black house', planet='mars'}

EDIT: I’ve updated the example to use more compact stream example.

Maybe whatever you are trying to accomplish would be easier if you redesigned your program. I don’t know what exactly you want to do with your zoo, but as far as I can figure out your intention by your code, house and planet are not properties of an animal, but a planet is a container of houses and a house is a container of animals. So you could define a class Animal with a field String name, a class House with a field of whatever subtype of Collection<Animal> seems most appropriate (probably List<Animal>), and class Planet with a field of a subtype of Collection<House> (in this case, maybe Set<House> might be more appropriate, depends on what you want to do). Then additionally, a House would need to check if it can contain an animal or not. How this would be best implemented depends on your intention (for example, if the applicability of an animal for a house depends on the animal itself, e.g. whether the animal wants to be in a specific house, the implementation would have to be different from if the applicability depends solely on the house, e.g. if an animal can fit inside it). Maybe it would be even more convenient if House and/or Planet implemented Collection directly and were not merely wrappers, but again, this depends on your intention.

I understand that this is not a direct answer to your question, but maybe it solves the problem which caused the question to arise in the first place (but without knowing the context of your code, I can’t be sure of course).