Java 8 Сортировать HashMap, где ключ карты является объектом - PullRequest
3 голосов
/ 18 марта 2020

У меня есть простой класс Customer, такой как

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

Я продолжаю сталкиваться с типом объекта, возвращаемого сборщиком?

Могу ли я привести коллектор так, чтобы он возвращал класс, может быть, что-то вроде

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

Можно ли преобразовать Map<**Object**, Long>() во что-то вроде Map<DiscountCounts, Long>(), если бы это позволило получить доступ в поля ключа Map, используя лямбда-выражения или конструкции Comparator?

Я пытался сделать что-то подобное, перебрать карту и вручную преобразовать ее в карту, которую я хочу, но не могу получить ключи исходной коллекции?

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

Ответы [ 2 ]

5 голосов
/ 18 марта 2020

Один из способов сделать это без использования класса 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 путем переопределения equals и hashcode класса DiscountCounts и создания groupingBy, создающего DiscountCounts объект для каждого Customer объекта в качестве ключа в Map и использования TreeMap с Comparator для сортировки результата

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

@ Андреас предлагает в комментарии просветить меня другим способом сделать это, и я чувствую, что это один из лучших подходов, которые вы можете реализовать Comparable на DiscountCounts и предоставьте логи сортировки c, чтобы вам не нужно было предоставлять компаратор для 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()));
1 голос
/ 18 марта 2020

При правильной реализации equals и hashcode для DiscountCounts вы можете искать что-то в строках:

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