javatuplesjava-streamtreemap

Sorting a Map in Java by Key


I checked out some related questions but none answered my question. I'm trying to sort a Map ascending alphabetically, but am having issues. Here is the code currently:

    // provinceName is key
    public Map<String, String> regionMap() {
        return 
            em
            .createQuery(
                """
                SELECT DISTINCT p.provinceName AS prov_id ,p.provinceAbbreviation AS prov_value
                FROM CanadianPersonalIncomeTaxRate p         
                ORDER BY p.provinceName ASC
                """,
                Tuple.class
            )
            .getResultStream()
                //    .filter(prov_value -> prov_value.get())
                //    .sorted(Comparator.comparing(prov_value -> prov_value))
            .collect(
                Collectors.toMap(
                    tuple -> tuple.get(0, String.class),
                    tuple -> tuple.get(1, String.class)
                )
            );
    }

I was thinking about using .sorted or TreeMap, but am open to suggestions on how I can do it the cleanest/simply. Any help is greatly appreciated.


Solution

  • Pass TreeMap :: new

    Change the declaration of your result to indicate you want a map sorted by key. Use NavigableMap.

    This:

    public Map<String, String> regionMap() { 
    

    … should be:

    public NavigableMap<String, String> regionMap() {
    

    Specify a concrete implementation of NavigableMap by passing another argument, a map factory such as TreeMap :: new, to your Collectors.toMap call. We must also pass a merge function to decide what to do in case of duplicate elements. See the Javadoc for this overload of Collectors.toMap.

              Collectors.toMap(
                    tuple -> tuple.get( 0 , String.class ) ,
                    tuple -> tuple.get( 1 , String.class ) ,
                    ( oldValue , newValue ) -> oldValue ) ,
                    TreeMap :: new
                )
    

    Your database sorting becomes superfluous in this approach. Since you are sorting on the Java side, you could delete the line of SQL:

    ORDER BY p.provinceName ASC
    

    But then again, adding elements to the TreeMap in pre-sorted order might help performance of the map’s sorting (just a guess on my part). So I would consider keeping the ORDER BY.

    If you want to return an unmodifiable map, use Collectors.toUnmodifiableMap with same arguments.

    Example code

    Here is some example code.

    First, some input data, to simulate your database query.

    String[][] inputs =
            {
                    { "Ontario" , "ON" } ,
                    { "Québec" , "QA" } ,
                    { "Nova Scotia" , "NS" } ,
                    { "New Brunswick" , "NB" } ,
                    { "Manatob" , "MB" } ,
                    { "British Columbia" , "BC" } ,
                    { "Prince Edward Island" , "PE" } ,
                    { "Saskatchewan" , "SK" } ,
                    { "Alberta" , "AB" } ,
                    { "Newfoundland and Labrador" , "NL" }
            };
    

    And the logic. We retrieve each row from the array of arrays. We extract the first and second element from each row/array by zero-based index counting (0 & 1). And we collect those into a NavigableMap, specifically a TreeMap.

    NavigableMap < String, String > map =
            Arrays
                    .stream( inputs )
                    .collect(
                            Collectors.toMap(
                                    ( String[] input ) -> input[ 0 ] ,
                                    ( String[] input ) -> input[ 1 ] ,
                                    ( String oldValue , String newValue ) -> oldValue ,
                                    TreeMap :: new
                            )
                    );
    

    Or, if you don't need to see the type declarations, use the following. Your situation is complicated enough that I would personally retain the type declarations for clarity. Doing so would have prevented the error you made in naming oldTuple , newTuple when really it is the String value objects that are being merged, oldValue, newValue.

    NavigableMap < String, String > map =
            Arrays
                    .stream( inputs )
                    .collect(
                            Collectors.toMap(
                                    input -> input[ 0 ] ,
                                    input -> input[ 1 ] ,
                                    ( oldValue , newValue ) -> oldValue ,
                                    TreeMap :: new
                            )
                    );
    

    Result:

    map.toString() = {Alberta=AB, British Columbia=BC, Manatob=MB, New Brunswick=NB, Newfoundland and Labrador=NL, Nova Scotia=NS, Ontario=ON, Prince Edward Island=PE, Québec=QA, Saskatchewan=SK}

    We can skip over unwanted rows.

    String[][] inputs =
            {
                    { "Ontario" , "ON" } ,
                    { "Federal" , "CAN" } ,  // <-- Unwanted row.
                    { "Québec" , "QA" } ,
                    { "Nova Scotia" , "NS" } ,
                    { "New Brunswick" , "NB" } ,
                    { "Manatob" , "MB" } ,
                    { "British Columbia" , "BC" } ,
                    { "Prince Edward Island" , "PE" } ,
                    { "Saskatchewan" , "SK" } ,
                    { "Alberta" , "AB" } ,
                    { "Newfoundland and Labrador" , "NL" }
            };
    
    NavigableMap < String, String > map =
            Arrays
                    .stream( inputs )
                    .filter( ( String[] input ) -> ! input[ 1 ].equalsIgnoreCase( "CAN" ) )
                    .collect(
                            Collectors.toMap(
                                    input -> input[ 0 ] ,
                                    input -> input[ 1 ] ,
                                    ( oldValue , newValue ) -> oldValue ,
                                    TreeMap :: new
                            )
                    );