【问题标题】:Java 8 Sort HashMap where map key is an object of <String, Integer>Java 8 Sort HashMap,其中映射键是 <String, Integer> 的对象
【发布时间】:2020-06-29 04:24:21
【问题描述】:

我有一个像这样的简单客户类

public class Customer {
    public int age;
    public int discount;
    public String name;

    public Customer(String name) {
        this.name = name;
    }
    public Customer(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public Customer(String name, int age, int discount) {
        this.name = name;
        this.age = age;
        this.discount = discount;
    }

    @Override
    public String toString() {
        return "Customer [age=" + age + ", discount=" + discount + ", name=" + name + "]";
    }

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public Integer getDiscount() {
        return discount;
    }
    public void setDiscount(int discount) {
        this.discount = discount;
    }
}

我使用这个填充这些对象的列表

List<Customer> customerList = new ArrayList<>(Arrays.asList(
        new Customer("John",   2, 15),
        new Customer("John",   4, 15),
        new Customer("John",   6, 25),
        new Customer("Joe",    3, 15),
        new Customer("Joe",    3, 15),
        new Customer("Joe",    3, 15),
        new Customer("Goerge", 6, 25),
        new Customer("Goerge", 6, 25),
        new Customer("Mary",   7, 25),
        new Customer("Jane",   1, 15),
        new Customer("Jane",   2, 15),
        new Customer("Jane",   8, 25),
        new Customer("Jane",   8, 25)
        ));

现在我想使用这样的收集器对名称和折扣进行分组和计数

Map<Object, Long> collected = customerList
    .stream()
    .collect(Collectors.groupingBy(x -> Arrays.asList(x.name, x.discount), Collectors.counting()));

我可以使用这个来查看我的输出

collected.entrySet().forEach(c -> {
    System.out.println(c);
});

输出如下

[Jane, 15]=2
[Joe, 15]=3
[John, 15]=2
[Mary, 25]=1
[John, 25]=1
[Jane, 25]=2
[Goerge, 25]=2

问题是我如何按名称和折扣排序地图,使其看起来像这样

[Goerge, 25]=2
[Jane, 15]=2
[Jane, 25]=2
[Joe, 15]=3
[John, 15]=2
[John, 25]=1
[Mary, 25]=1

我一直碰到收集器返回的 Object 类型?

我可以转换收集器以便它返回一个类,也许像

private class DiscountCounts
{
    public String name;
    public Integer discount;
}

是否可以将Map&lt;**Object**, Long&gt;() 转换为Map&lt;DiscountCounts, Long&gt;() 之类的东西,这是否允许使用 lambda 或 Comparator 构造访问 Map 键的字段?

我尝试了类似的方法,遍历地图并手动转换为我想要的地图,但我无法获取原始集合的键?

    Map<DiscountCounts, Long> collected2 = new HashMap<>();
    collected.entrySet().forEach(o -> {
        DiscountCounts key1 = (DiscountCounts)o.getKey();  //--> Fails here
        collected2.put((DiscountCounts)o.getKey(), o.getValue());
    });

【问题讨论】:

  • 您是否考虑过使用TreeMap 而不是HashMap?它会自动对其键进行排序。
  • Dawood,从下面的回复中,我可以看到 TreeMap 在这里会有什么帮助,但我永远无法很好地理解 Collector 对象来自己实现它,感谢您的建议。跨度>

标签: java collections hashmap type-conversion


【解决方案1】:

不使用DiscountCounts类的一种方法是,先对列表进行排序,然后按操作进行摸索,并使用LinkedHashMap保存排序顺序

Map<List<Object>, Long> map = customerList.stream()
                .sorted(Comparator.comparing(Customer::getName).thenComparing(Customer::getDiscount))
                .collect(Collectors.groupingBy(x -> Arrays.asList(x.name, x.discount),LinkedHashMap::new, Collectors.counting()));

使用DiscountCounts 类的另一种方法是,通过覆盖DiscountCounts 类的equalshashcode 并进行分组通过为每个Customer 对象创建DiscountCounts 对象作为Map 中的键和使用TreeMapComparator 对结果进行排序

Map<DiscountCounts, Long> result = customerList.stream().collect(Collectors.groupingBy(
            c -> new DiscountCounts(c.getName(), c.getDiscount()),
            () -> new TreeMap<DiscountCounts, Long>(
                    Comparator.comparing(DiscountCounts::getName).thenComparing(DiscountCounts::getDiscount)),
            Collectors.counting()));

@Andreas 在评论中建议启发我另一种方法,我觉得这是您可以在 DiscountCounts 上实现 Comparable 并提供排序逻辑的最佳方法之一,这样您就不需要提供比较器到TreeMap

@Override
public int compareTo(DiscountCounts cust) {

      int last = this.getName().compareTo(cust.getName());

     return last == 0 ? this.getDiscount().compareTo(cust.getDiscount()) : last;
}

Map<DiscountCounts, Long> result1 = customerList.stream().collect(Collectors.groupingBy(
            c -> new DiscountCounts(c.getName(), c.getDiscount()), TreeMap::new, Collectors.counting()));

【讨论】:

  • 如果使用DiscountCounts,最好让DiscountCounts实现Comparable,这样就不必为TreeMap构建Comparator
  • 谢谢,第二个解决方案有效,并为我提供了最强大的功能来完成我需要做的事情。完整和连贯的解决方案的全部功劳。
【解决方案2】:

通过为DiscountCounts 正确实现equalshashcode,您可能正在寻找以下内容:

Map<DiscountCounts, Long> collectSortedEntries = customerList
        .stream()
        .collect(Collectors.groupingBy(x -> new DiscountCounts(x.name, x.discount),
                Collectors.counting()))
        .entrySet()
        .stream()
        .sorted(Comparator.comparing((Map.Entry<DiscountCounts, Long> e) -> e.getKey().getName())
                .thenComparing(e -> e.getKey().getDiscount()))
        .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue,
                (a, b) -> a, LinkedHashMap::new));

【讨论】:

  • Naman,我喜欢你的做法;你的代码会在 e -> e.getKey().getName() 上引发编译器错误,但这对我来说是一个机会了解有关比较器的更多信息。
  • @vscoder 已经更新了修复编译的答案,但实际上 Andreas 对另一个答案的评论是将DiscountCounts 更新为implement Comparable&lt;DiscountCounts&gt;,然后进一步简化收集到TreeMap
猜你喜欢
  • 1970-01-01
  • 2012-05-26
  • 1970-01-01
  • 1970-01-01
  • 2023-03-15
  • 1970-01-01
  • 2015-02-19
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多