Implementing Custom Sorting Strategies in Java Collections

Implementing the Comparable interface establishes a natural ordering for a class. When a domain object overrides compareTo, standard collection utilities can sort instances without external configuration.

import java.util.ArrayList;
import java.util.List;

public class NaturalOrderingDemo {
    public static void main(String[] args) {
        List<StaffMember> team = new ArrayList<>();
        team.add(new StaffMember("Alice"));
        team.add(new StaffMember("Charlie"));
        team.add(new StaffMember("Bob"));

        team.forEach(m -> System.out.println(m.identifier));
        System.out.println("--- After Sorting ---");
        
        // Utilizes the natural ordering defined in StaffMember
        team.sort(null);
        team.forEach(m -> System.out.println(m.identifier));
    }
}

class StaffMember implements Comparable<StaffMember> {
    String identifier;

    StaffMember(String identifier) {
        this.identifier = identifier;
    }

    @Override
    public int compareTo(StaffMember other) {
        // Descending alphabetical order
        return other.identifier.compareTo(this.identifier);
    }
}

Output:

Alice
Charlie
Bob
--- After Sorting ---
Charlie
Bob
Alice

When modifying the source class is impossible or multiple sorting criteria are needed, Comparator decouples the ordering logic from the data model. This approach allows dynamic strategy selection at runtime.

import java.util.Arrays;
import java.util.Comparator;
import java.util.List;

public class ExternalOrderingDemo {
    public static void main(String[] args) {
        List<InventoryItem> stock = Arrays.asList(
            new InventoryItem("Laptop", 1200),
            new InventoryItem("Mouse", 25),
            new InventoryItem("Monitor", 300)
        );

        stock.forEach(i -> System.out.println(i.label + ": $" + i.cost));
        System.out.println("--- Sorted by Cost ---");

        // Applies an external comparison strategy
        stock.sort(new CostAscendingComparator());
        stock.forEach(i -> System.out.println(i.label + ": $" + i.cost));
    }
}

class InventoryItem {
    String label;
    int cost;

    InventoryItem(String label, int cost) {
        this.label = label;
        this.cost = cost;
    }
}

class CostAscendingComparator implements Comparator<InventoryItem> {
    @Override
    public int compare(InventoryItem first, InventoryItem second) {
        return Integer.compare(first.cost, second.cost);
    }
}

Output:

Laptop: $1200
Mouse: $25
Monitor: $300
--- Sorted by Cost ---
Mouse: $25
Monitor: $300
Laptop: $1200

Standard HashMap instances do not guarantee iteration order. To reorder map entries based on keys or values, the entry set can be streamed, sorted with a comparator, and collected into a LinkedHashMap to preserve the new sequence.

import java.util.*;
import java.util.stream.Collectors;

public class MapSortingUtil {

    // Orders entries by their values in ascending order
    public static <K, V extends Comparable<? super V>> Map<K, V> orderByValue(Map<K, V> source) {
        return source.entrySet().stream()
                .sorted(Map.Entry.comparingByValue())
                .collect(Collectors.toMap(
                        Map.Entry::getKey,
                        Map.Entry::getValue,
                        (e1, e2) -> e1,
                        LinkedHashMap::new
                ));
    }

    // Orders entries by their keys in descending order
    public static <K extends Comparable<? super K>, V> Map<K, V> orderByKeyDesc(Map<K, V> source) {
        return source.entrySet().stream()
                .sorted((entryA, entryB) -> entryB.getKey().compareTo(entryA.getKey()))
                .peek(e -> System.out.println("Processing -> Key: " + e.getKey() + " | Val: " + e.getValue()))
                .collect(Collectors.toMap(
                        Map.Entry::getKey,
                        Map.Entry::getValue,
                        (e1, e2) -> e1,
                        LinkedHashMap::new
                ));
    }

    public static void main(String[] args) {
        Map<Integer, String> priorityTasks = new HashMap<>();
        priorityTasks.put(3, "Deploy");
        priorityTasks.put(1, "Design");
        priorityTasks.put(2, "Develop");

        Map<Integer, String> sortedByKey = orderByKeyDesc(priorityTasks);
        System.out.println("Final Map: " + sortedByKey);
    }
}

LinkedHashMap maintains insertion order through an internal doubly-linked list. While standard API traversal requires O(N) time to reach the end, the most recently inserted element can be retrieved in O(1) by accessing the private tail node via reflection. This technique bypasses public API limitaitons but depends on internal JDK structure.

import java.lang.reflect.Field;
import java.util.LinkedHashMap;
import java.util.Map;

public class LinkedHashMapInternals {
    public static void main(String[] args) throws Exception {
        Map<String, Integer> cache = new LinkedHashMap<>();
        cache.put("alpha", 10);
        cache.put("beta", 20);
        cache.put("gamma", 30);

        // Access internal tail field directly
        Field tailField = LinkedHashMap.class.getDeclaredField("tail");
        tailField.setAccessible(true);
        
        @SuppressWarnings("unchecked")
        Map.Entry<String, Integer> lastNode = (Map.Entry<String, Integer>) tailField.get(cache);
        
        if (lastNode != null) {
            System.out.println("Last Key: " + lastNode.getKey());
            System.out.println("Last Value: " + lastNode.getValue());
        }
    }
}

Tags: java Collections Sorting Comparable comparator

Posted on Thu, 17 Sep 2026 16:32:48 +0000 by Eiolon