Eduardo
Eduardo

Reputation: 7141

Java Streams: Grouping a List by two fields

I have a list of cars

List<Car> cars = ...;

A car has an Owner and and Owner has a ContactNumber

Car

public class Car {
    private Owner owner;
    public Owner getOwner(){
       return owner;
    }
}

Owner

public class Owner {
    private ContactNumber contactNumber;
    public ContactNumber getContactNumber() {
        return contactNumber;
    }
}

I know I can group cars by Owners using

Map<Owner, List<Car>> groupByOwner = cars.stream().collect(groupingBy(Car::getOwner));

Is there a way I can then use streams to group by Owner and ContactNumber knowing that one ContactNumber can only ever be associated with one Owner?

How would I also do this if a ContactNumber could be shared by multiple Owners?

i.e to create the below map:

Map<Owner, List<Car>> groupByOwner = cars.stream().collect(groupingBy(Car::getOwner))
Map<ContactNumber, Map<Owner, List<Car>>> groupByContactNumberAndOwner = groupByOwner...

Upvotes: 3

Views: 17172

Answers (2)

fps
fps

Reputation: 34460

If you know that

one ContactNumber can only ever be associated with one Owner

Then, you don't need inner maps. Just group directly by ContactNumber:

Map<ContactNumber, List<Car>> groupByOwnerContactNumber = cars.stream()
    .collect(Collectors.groupingBy(c -> c.getOwner().getContactNumber()));

Don't be afraid to use lambda expressions ;)


EDIT:

As per the second part of your question:

if a ContactNumber could be shared by multiple Owners

You could do a nested grouping by using the overloaded version of Collectors.groupingBy that accepts a downstream collector:

Map<ContactNumber, Map<Owner, List<Car>>> groupByOwnerAndContactNumber = 
    cars.stream().collect(Collectors.groupingBy(
            c -> c.getOwner().getContactNumber(),
            Collectors.groupingBy(Car::getOwner)));

Another way would be to group by a compound key (ContactNumber, Owner). You can achieve this by letting the key be a List<Object>:

Map<List<Object>, List<Car>> groupByCompundOwnerContactNumber = 
    cars.stream().collect(Collectors.groupingBy(
        c -> Arrays.asList(c.getOwner().getContactNumber(), c.getOwner())));

This doesn't require a nested grouping operation.

Note: if you're using Java 9 you can use List.of instead of Arrays.asList.

Upvotes: 8

Eduardo
Eduardo

Reputation: 7141

To get the Map<ContactNumber, Map<Owner, List<Car>>> collection I was looking for I had to do:

Map<ContactNumber, Map<Owner, List<Car>>> response = cars.stream()
                    .collect(groupingBy(c -> c.getOwner().getContactNumber(), groupingBy(Car::getOwner)));

Using the overloaded collector

groupingBy(Function<? super T, ? extends K> classifier, Collector<? super T, A, D> downstream)

Where you would use:

groupingBy(
    list element -> root key of the map, 
    groupingBy(list element -> second level key of the map)
);

Upvotes: 5

Related Questions