Java: How to add objects from List<T> to Map (Key: Enum, value List<T>)

Hans Meier :

I'm trying to add objects from the List<Farm> to Map<Animal, List<Farm>>

public class Farm {
    private String farmName;
    private EnumSet<Animal> animals = EnumSet.noneOf(Animal.class);
    /* ... */
}

Farm f1 = new Farm("Farm A", EnumSet.of(Animal.CAT, Animal.DOG, Animal.DUCK));
Farm f2 = new Farm("Farm B", EnumSet.of(Animal.PIG, Animal.CAT, Animal.HORSE));
Farm f3 = new Farm("Farm C", EnumSet.of(Animal.DUCK));

Task 1: add Objects to List<Farm>

List<Farm> list = new ArrayList<>();
list.add(f1);
list.add(f2);
list.add(f3);

Task 2: Add the objects from list to a map (Key: Animal, Value: List <Farm>) I did this task in this way:

Map<Animal, List<Farm>> map = new HashMap<>();

for(Farm farm: list) {
    for(Animal an: farm.getAnimals()) {
        if(!map.containsKey(an)) {
            List<Farm> new_list = new ArrayList<>();
            new_list.add(farm);
            map.put(an, new_list);
        }else {     
            List<Farm> old_list = map.get(an);
            if(!old_list.contains(farm)) {
                old_list.add(farm);
                    }
            }
        }
    }

Is there a second / more efficient solution? Something like this:

Map<Animal, List<Farm>> map = list.stream().collect(Collectors.groupingBy(Farm::getAnimals)));

This does not work because getAnimals returns an EnumSet<Animal>.

Holger :

You probably want to stay with the loop, but modernize it:

Map<Animal, List<Farm>> map = new EnumMap<>(Animal.class);
for(Farm farm: list)
    for(Animal an: farm.getAnimals())
        map.computeIfAbsent(an, x -> new ArrayList<>()).add(farm);

In your loop, add(farm) redundantly appeared in both branches, as you always add it to the List. Then, computeIfAbsent allows to eliminate the conditional, as it will return an existing value or construct a new value, put and return it. The groupingBy collector also uses this method internally.

Using a Stream operation for the same has the disadvantage that you need a temporary holder for two values, e.g.

Map<Animal, List<Farm>> map = list.stream()
    .flatMap(farm -> farm.getAnimals().stream()
        .map(animal -> new AbstractMap.SimpleImmutableEntry<>(animal, farm)))
    .collect(Collectors.groupingBy(Map.Entry::getKey,
        () -> new EnumMap<>(Animal.class),
        Collectors.mapping(Map.Entry::getValue, Collectors.toList())));

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=146452&siteId=1