I have a stream where I am aggregating some data like this:
//start comparator -> I take a string in format month-year (2-2022) and I want to order all the //dates asc (day is not important here so I add by default first day from the month) Comparator<String> c = (String s1, String s2)->{ String[] s1Data = s1.split("-"); String[] s2Data = s2.split("-"); LocalDate first = LocalDate.of(Integer.parseInt(s1Data[1]), Integer.parseInt(s1Data[0]),1); LocalDate second = LocalDate.of(Integer.parseInt(s2Data[1]), Integer.parseInt(s2Data[0]),1); System.out.println(second + " is after " + first); if (first.isBefore(second)) return -1; else return 1; }; //end comparator //start stream Map<String, Map<String, Map<EventType, Long>>> outputStream = timeSheetDTOS.stream(). collect(Collectors.groupingBy( t -> t.getFirstName() + " " + t.getLastName(), TreeMap ::new, Collectors.groupingBy(t -> t.getDayOfWork().get(ChronoField.MONTH_OF_YEAR) + "-" + t.getDayOfWork().get(ChronoField.YEAR), **()-> new TreeMap<>(c)**, Collectors.groupingBy(TimeSheetDTO::getTaskType, TreeMap ::new , Collectors.counting()) ) ));
The problem is that by adding this comparator I am braking the contract between hashCode() and equals() and at the end I have duplicate keys:
Has anyone some ideas how can I fix this? Or is there a way to sort the final object ( Map<String, Map<String, Map<EventType, Long>>>) that is returned by the stream after the key of the second map?
My entire map looks like this:
<"Some String",<"2-2022",<"HOLIDAY",2>>>
And I want to sort in asc order after “2-2022”.
Thanks
Advertisement
Answer
Your comparator is broken. Comparators have rules they need to adhere to; one of them is commutativity; if your comparator claims that a
is below b
, then b
must be above a
. This isn’t true with your implementation; if a and b are equal, your comparator says that ‘a’ is above ‘b’, and also that ‘b’ is above ‘a’.
Return 0 for equal stuff. Easiest way is to use java’s built in stuff:
Comparator<String> c = Comparator.comparing((String s) -> { String[] sData = s.split("-"); return LocalDate.of(Integer.parseInt(sData[1]), Integer.parseInt(sData[0]), 1); });
Much shorter, much easier to read, and.. correct, which is nice.
NB: equals and hashCode are completely irrelevant here. TreeMap does not invoke or use them in any way; it uses the comparator for everything. The problem was your broken comparator implementation.