【问题标题】:Group map values but keys are same分组映射值但键相同
【发布时间】:2022-01-26 20:57:16
【问题描述】:

我有一张这样的地图。 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 和代码字段。我希望两个地图的键相同。

我可以使用这些代码对地图进行分组,但 summingDouble 不适用于 BigDecimal。此外,我无法将 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` 
}

【问题讨论】:

  • 您能否澄清一下 StudentInfo 是否与 Student 基本相同,只是缺少姓名和城市?还是 StudentInfo 是居住在同一个城市的多个学生的汇总?如果不指定 StudentInfo 和 Student 之间的关系,您就不能在这里真正期待解决方案。
  • 我只想创建一个新的Object(StudentInfo),其中包含Student的一些字段。但是我想对addressNo和code字段进行分组
  • Java Map 意义上的“分组”意味着将它们用作映射键。相应的值将是该分组中的对象列表。 (想想您将事物分类到其中的标签桶——标签是关键,内容是事物的List)。如果您想要标签上的两个属性(在键中),您首先需要创建一个复合键对象,该对象实现equals()hashCode() 以符合Map 合同。如果这不是您想要的“分组”,请澄清。

标签: java hashmap


【解决方案1】:

对于从 Student 到 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;
    }
}

从一个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&lt;T&gt; public abstract &lt;R, A&gt; R collect(java.util.stream.Collector&lt;? super T, A, R&gt; collector) 的 JavaDoc:

下面会按城市对 Person 对象进行分类:

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

以下将按州和城市对 Person 对象进行分类,将两个 Collector 级联在一起:

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

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

现在,summingDouble 通过 StudentInfo::getTax 生成 BigDecimal,而不是 Map。替换为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&lt;MyKey,List&gt;),你需要一个复合键 MyKey

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

这意味着您现在有一个由复合键引用的嵌套映射。 Students 具有完全相同的地址编号、代码和税金将成为每个此类键引用的列表的一部分。

编辑:保留原始密钥

同样,如果您想保留原始密钥,您可以将它们添加到复合密钥中,或与上述类似:

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

【讨论】:

  • 非常感谢您。第一部分正在工作(将其转换为 studentInfoMap )但分组部分不起作用,因为 studentMap(1,2,3,4..) 的键不存在。跨度>
  • 我已经在“标准”和扁平化/复合键示例中添加了相应的部分。
  • 它给出了 Map.Entry 类型没有定义 getKey(StudentInfo) 在这里适用于编辑:保留 1,2,3,4 原始键部分。:(
  • 是的,抱歉,转换时需要使用 toMap 和流式传输列表。您实际上可以一步完成转换和映射,您知道怎么做吗? (提示:将转换代码中的 .collect(Collectors.toList()) 调用替换为“保留 1,2,3,4 键”中的最后一个调用,并修改生成的类型声明)
  • 非常感谢,我会试试的
【解决方案2】:
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 ...

【讨论】:

  • studentMap如何转换为studentInfoMap?
【解决方案3】:

要将地图从 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);
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-03-12
    • 2015-12-26
    • 2018-11-13
    • 2014-02-20
    • 1970-01-01
    • 2022-01-26
    相关资源
    最近更新 更多