Skip to content
Advertisement

Group map values but keys are same

I have a map like this. Map<long,List<Student>> studentMap

Key is a number 1,2,3,4… Student object is :

public class Student {
 private long addressNo;
 private String code;
 private BigDecimal tax;
 private String name;
 private String city;

 // getter and setters` 
}

What i want to do is to convert it Map<long,List<StudentInfo>> studentInfoMap object and group id, addressNo and code fields.I want key are same for both maps.

I can group the map by using these codes but summingDouble is not working for BigDecimal.Also I cannot convert my studentMap to studentInfoMap.:(

 studentInfoMap.values().stream()
            .collect(
                     Collectors.groupingBy(StudentInfo::getCode, 
                     Collectors.groupingBy(StudentInfo::getAddressNo, 
                     Collectors.summingDouble(StudentInfo::getTax))));

My studentInfo object is :

public class StudentInfo {
  private long addressNo;
  private String code;
  private BigDecimal tax;

  // getter and setters` 
}

Advertisement

Answer

For a one-to-one conversion from Student to StudentInfo:

class StudentInfo {
    public static StudentInfo of(Student student) {
        StudentInfo si = new StudentInfo();
        si.setAddressNo(student.getAddressNo());
        si.setCode(student.getCode());
        si.setTax(student.getTax());
        return si;
    }
}

To convert from one Map to the other:

Map<Long,List<Student>> studentMap = ...
Map<Long,List<StudentInfo>> studentInfoMap = studentMap.entrySet().stream()
            .collect(Collectors.toMap(Map.Entry::getKey, //same key
                    entry -> entry.getValue().stream()
                            .map(StudentInfo::of)    //conversion of Student to StudentInfo
                            .collect(Collectors.toList()) //or simply `.toList()` as of Java 16
                    ));

Now your grouping….

From the JavaDoc for java.util.stream.Stream<T> public abstract <R, A> R collect(java.util.stream.Collector<? super T, A, R> collector):

The following will classify Person objects by city:

Map<String, List<Person>> peopleByCity
    = personStream.collect(Collectors.groupingBy(Person::getCity));

The following will classify Person objects by state and city, cascading two Collectors together:

Map<String, Map<String, List<Person>>> peopleByStateAndCity
    = personStream.collect(Collectors.groupingBy(Person::getState,
                                                 Collectors.groupingBy(Person::getCity)));

Note how the last example produces a Map with another Map as its values.

Now, summingDouble over StudentInfo::getTax produces a BigDecimal, not a Map. Replacing with groupingBy will work to classify Students that have the same amount for getTax:

Map<String, Map<Long, Map<BigDecimal, List<StudentInfo>>>> summary = 
      studentInfoMap.values().stream()
            .flatMap(List::stream)  //YOU ALSO NEED THIS
            .collect(
                    Collectors.groupingBy(StudentInfo::getCode,
                            Collectors.groupingBy(StudentInfo::getAddressNo,
                                    Collectors.groupingBy(StudentInfo::getTax)))
            );

Edit: Retaining the 1,2,3,4 original keys

To retain the original keys you can iterate or stream the original entrySet, which contains both key and value:

Map<Long,Map<String, Map<Long, Map<BigDecimal, List<StudentInfo>>>>> summaryWithKeys =
        studentInfoMap.entrySet().stream() //NOTE streaming the entrySet not just values
                .collect(
                        Collectors.toMap(Map.Entry::getKey, //Original Key with toMap
                                entry -> entry.getValue().stream() //group the value-List members
                                        .collect(Collectors.groupingBy(StudentInfo::getCode,
                                        Collectors.groupingBy(StudentInfo::getAddressNo,
                                                Collectors.groupingBy(StudentInfo::getTax))))
                    ));

Just as an exercise, if you want a flat map (Map<MyKey,List>) you need a composite key MyKey

As per my comment, if you are looking to have a single flat Map, you could design a composite key, which would need to implement both equals() and hashCode() to contract. For example, this is what Lombok would generate for StudentInfo (yes, its easier to depend on lombok and use @EqualsAndHashCode):

public boolean equals(final Object o) {
        if(o == this) return true;
        if(!(o instanceof StudentInfo)) return false;
        final StudentInfo other = (StudentInfo) o;
        if(!other.canEqual((Object) this)) return false;
        if(this.getAddressNo() != other.getAddressNo()) return false;
        final Object this$code = this.getCode();
        final Object other$code = other.getCode();
        if(this$code == null ? other$code != null : !this$code.equals(other$code)) return false;
        final Object this$tax = this.getTax();
        final Object other$tax = other.getTax();
        if(this$tax == null ? other$tax != null : !this$tax.equals(other$tax)) return false;
        return true;
    }
    
    protected boolean canEqual(final Object other) {return other instanceof StudentInfo;}
    
    public int hashCode() {
        final int PRIME = 59;
        int result = 1;
        final long $addressNo = this.getAddressNo();
        result = result * PRIME + (int) ($addressNo >>> 32 ^ $addressNo);
        final Object $code = this.getCode();
        result = result * PRIME + ($code == null ? 43 : $code.hashCode());
        final Object $tax = this.getTax();
        result = result * PRIME + ($tax == null ? 43 : $tax.hashCode());
        return result;
    }

You might then use StudentInfo as the composite key as follows:

Map<Long, List<Student>> studentMap = ...
Map<StudentInfo,List<Student>>> summaryMap = studentMap.values().stream()
            .collect(Collectors.groupingBy(StudentInfo::of))
));

This means that you now have a nested map referenced by the composite key. Students that have exactly the same addressNo, code and tax will be part of the List referenced by each such key.

Edit: Retaining original keys

Similarly, if you wanted to retain the original keys, you could either add them into the composite key, or similar as above:

Map<Long, List<Student>> studentMap = ...
Map<Long, Map<StudentInfo,List<Student>>>> summaryMap = studentMap.entrySet().stream()
            .collect(Collectors.groupingBy(Map.Entry::getKey,
                         Collectors.groupingBy(StudentInfo::of)))
));
Advertisement