javajava-streamcomparatortreemap

Duplicate entries when creating a TreeMap with a custom Comparator from a stream


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 breaking the contract between hashCode() and equals() and at the end I have duplicate keys:

enter image description here

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".


Solution

  • 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.