3

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...
Eduardo
  • 6,900
  • 17
  • 77
  • 121
  • If `ContactNumber` is unique per owner then why have `Map` of owners per `ContactNumber` in `groupByContactNumberAndOwner`? – tsolakp Feb 22 '18 at 17:18
  • Possible duplicate of [Group by multiple field names in java 8](https://stackoverflow.com/questions/28342814/group-by-multiple-field-names-in-java-8) – sknt Feb 22 '18 at 18:13
  • @tsolakp You're right, If that restriction wasnt in place, i.e if a ContactNumber could be shared by multiple owners how would I do this? – Eduardo Feb 23 '18 at 10:58

2 Answers2

8

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.

fps
  • 33,623
  • 8
  • 55
  • 110
5

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)
);
Eduardo
  • 6,900
  • 17
  • 77
  • 121