Java 8 Sort HashMap where map key is an object of <String, Integer>

vscoder :

I have a simple Customer class like so

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

I populate a list of these objects using this

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

Now I want to group and count the names and discounts, using a collector like this

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

I can review my output using this

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

Which outputs the following

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

The question is how do I sort the Map by name and discount so it looks like this

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

I keep bumping up against the Object type that is returned by the collector?

Can I cast the collector so that it returns a class, maybe something like

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

Is it possible to convert the Map<**Object**, Long>() to something like Map<DiscountCounts, Long>(), would this allow access to the fields of the Map key using lambda or Comparator constructs?

I tried something like this, iterate over the map and manually convert to the Map I want but I can't get to the original collection's keys?

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

One way you can do it without using DiscountCounts class is, first sort the list and then perform the groping by operation, and use LinkedHashMap to save the sorted order

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

The another way using DiscountCounts class is, by override the equals and hashcode of DiscountCounts class and do a groupingBy creating DiscountCounts object for every Customer object as key in Map and use TreeMap with Comparator to sort the result

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 suggest in the comment enlighten me another way of doing it, and i feel this is one of the best approach you can implement Comparable on DiscountCounts and provide the sorting logic so that you don't need to provide Comparator to 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()));

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=300132&siteId=1