分组映射值但键相同

Group map values but keys are same

我有一张这样的地图。 Map<long,List<Student>> studentMap

键是数字 1,2,3,4... 学生对象是:

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

 // getter and setters` 
}

我想做的是转换它 Map<long,List<StudentInfo>> studentInfoMap 对象和组 ID、addressNo 和代码 fields.I 两个地图的密钥相同。

我可以使用这些代码对地图进行分组,但是 summingDouble 不适用于 BigDecimal.Also 我无法将我的 studentMap 转换为 studentInfoMap。:(

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

我的 studentInfo 对象是:

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

  // getter and setters` 
}
Map<Integer, Object> map = new HashMap<>();
map.put(1, studentInfoMap.values().stream().map(
    student -> student.getAddressNo()
  ).collect(Collectors.toList()));
map.put(2, studentInfoMap.values().stream().map(
  student -> student.getCode()
).collect(Collectors.toList()));
// and so ...

要将映射从 Student 转换为 StudentInfo,同时保留相同的键,您可以这样做:

Set<Long> keys = studentMap.keySet();
List<Long> keylist = new ArrayList<>(keys);
Map<Long, List<StudentInfo>> studentInfoMap = new HashMap<>();
for(int i = 0; i < keys.size(); i++){
    long key = keylist.get(i);
    List<Student> list = studentMap.get(key);
    List<StudentInfo> result = new ArrayList<>();
    // Create the new StudentInfo object with your values
    list.forEach(s -> result.add(new StudentInfo(s.name())));
    // Put them into the new Map with the same keys
   studentInfoMap.put(key, result);
}

对于从 Student 到 StudentInfo 的 one-to-one 转换:

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;
    }
}

要从一个 Map 转换为另一个:

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
                    ));

现在分组....

来自 java.util.stream.Stream<T> public abstract <R, A> R collect(java.util.stream.Collector<? super T, A, R> collector) 的 JavaDoc:

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)));

请注意最后一个示例如何生成一个 Map 并将另一个 Map 作为其值。

现在,summingDouble 超过 StudentInfo::getTax 生成 BigDecimal,而不是地图。替换为 groupingBy 将用于对 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)))
            );

编辑:保留原来的1,2,3,4键

要保留原始键,您可以迭代或流式传输包含键和值的原始 entrySet:

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))))
                    ));

作为练习,如果你想要一个平面图(Map<MyKey,List>)你需要一个组合键MyKey

根据我的评论,如果您希望拥有一个平面地图,您可以设计一个复合键,它需要同时实现 equals()hashCode() 才能收缩。例如,这是 LombokStudentInfo 生成的内容(是的,它更容易依赖 lombok 并使用 @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;
    }

然后您可以使用 StudentInfo 作为组合键,如下所示:

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

这意味着您现在有一个由复合键引用的嵌套地图。 Student具有完全相同的 addressNo、code 和 tax 的 s 将成为每个此类键引用的列表的一部分。

编辑:保留原始密钥

同样的,如果你想保留原来的键,你可以将它们添加到复合键中,或者类似于上面:

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)))
));