How to Group a List of Objects by Field in Java


Suppose we have a Dog class in Java with breed and name attributes.

class Dog {
  int id;
  String breed;

  Dog(int id, String breed) {
    this.id = id;
    this.breed = breed;
  }
  int getId() { return this.id; }
  String getBreed() { return this.breed; }
}

And let’s suppose we have a list of Dog objects.

List<Dog> lst = new ArrayList<Dog>();
lst.add(new Dog(1, "corgi"));
lst.add(new Dog(2, "shih tzu"));
lst.add(new Dog(3, "corgi"));
lst.add(new Dog(4, "corgi"));
lst.add(new Dog(5, "husky"));
lst.add(new Dog(6, "shih tzu"));

How can we group these Dog objects by the breed field?

Using a for loop

We can do this quite easily using a simple for loop and conditional.

Map<String, List<Dog>> grouped = new HashMap<String, List<Dog>>();
for (Dog dog : lst) {
  String key = dog.breed;
  if (!grouped.containsKey(key)) {
    grouped.put(key, new ArrayList<Dog>());
  }
  grouped.get(key).add(student);
}

Using the Stream API’s groupingBy()

In Java 8, we can use stream() and collect() to achieve the same functionality.

Map<String, List<Dog>> grouped =
  lst.stream().collect(Collectors.groupingBy(o -> o.breed));

We can generalize this function using this same Collectors.groupingBy() method.

public static <E, K> Map<K, List<E>> groupBy(List<E> list, Function<E, K> keyFunction) {
  return Optional.ofNullable(list)
                 .orElseGet(ArrayList::new)
                 .stream()
                 .collect(Collectors.groupingBy(keyFunction));
}
Map<String, List<Dog>> grouped = groupBy(lst, Dog::getBreed);