DEV Community

Cover image for Sorting Smarts in Java: Comparable and Comparator
Arshi Saxena
Arshi Saxena

Posted on

Sorting Smarts in Java: Comparable and Comparator

Sorting is a fundamental operation in Java, especially when dealing with collections. Java offers two main strategies for sorting: natural ordering using Comparable and custom ordering using Comparator.

For advanced use cases, collections like TreeSet and TreeMap leverage these strategies to maintain sorted order dynamically. This will be covered in depth in the next post.


Natural Ordering: The Comparable Interface

Comparable provides the default or natural ordering of objects. A class implements this interface and overrides the compareTo() method to define how objects of that class should be compared.

Key Characteristics of Comparable:

  • Interface Type: Not a functional interface (cannot be used with lambdas).
  • Sorting Criterion: A single, consistent sorting logic.
  • Usage: For objects with a natural comparison, such as integers, strings, or IDs.

Example: Sorting by ID

// Implementing Comparable Interface
class Product implements Comparable<Product> {
    private int id;
    private double price;
    private String category;

    public Product(int id, double price, String category) {
        this.id = id;
        this.price = price;
        this.category = category;
    }

    // Overriding compareTo() method
    @Override
    public int compareTo(Product o) {
        return Integer.compare(this.id, o.id); // Natural ordering by id
    }

    @Override
    public String toString() {
        return "Product [id=" + id + ", price=" + price + ", category=" +
        category + "]";
    }

    // Getters
    public int getId() {
        return id;
    }

    public double getPrice() {
        return price;
    }

    public String getCategory() {
        return category;
    }
}

public class ProductSortingExample {
    public static void main(String[] args) {
        List<Product> productList = new ArrayList<>();
        productList.add(new Product(3, 200.0, "Non-Essentials"));
        productList.add(new Product(1, 100.0, "Essentials"));
        productList.add(new Product(2, 150.0, "Essentials"));

        // Sorting using Comparable
        Collections.sort(productList);

        System.out.println("List Sorted with Comparable -> Natural Ordering");
        list.forEach(System.out::println);

    }
}
Enter fullscreen mode Exit fullscreen mode

Output

List Sorted with Comparable -> Natural Ordering
Product [id=1, price=100.0, category=Essentials]
Product [id=2, price=150.0, category=Essentials]
Product [id=3, price=200.0, category=Non-Essentials]
Enter fullscreen mode Exit fullscreen mode

Code Explanation

  1. Defining Natural Order:
    The compareTo method compares the id fields of Product objects. This provides the natural ordering for sorting.

  2. Natural Sorting:
    The Collections.sort(productList) method sorts the productList based on the natural ordering provided by the compareTo method. After sorting, the Product objects are arranged in ascending order of their id.

  3. Tree-Based Collections:
    This sorting behavior is crucial when working with collections such as TreeSet,TreeMap and others. These collections automatically maintain elements in sorted order based on the Comparable implementation, and we’ll demonstrate this in the next post.


Custom Ordering: The Comparator Interface

Comparator allows you to define custom sorting criteria, independent of the natural ordering. It’s a functional interface, so it can be implemented using lambdas or method references.

Key Characteristics of Comparator:

  • Interface Type: Functional interface (can be used with lambdas).
  • Sorting Criterion: Flexible, supports multiple custom criteria.
  • Usage: Ideal for sorting by attributes other than the natural order or for multi-level sorting.

Example: Sorting by category and price

public class NaturalVsCustomOrdering {

    public static void main(String[] args) {
        // Initializing List of Products
        List<Product> list = new ArrayList<>(Arrays.asList(
                new Product(1, 100.0, "Essentials"),
                new Product(2, 500.0, "Essentials"),
                new Product(3, 200.0, "Non-Essentials"),
                new Product(4, 400.0, "Non-Essentials"),
                new Product(5, 300.0, "Essentials")
        ));

        // Defining the Custom Comparator
        Comparator<Product> customComparator = (p1, p2) -> {
            // Comparing on the basis of category
            int categoryComparison = p1.getCategory()
                           .compareTo(p2.getCategory());

            // Comparing on the basis of price when categories are same
            return categoryComparison != 0 
                   ? categoryComparison 
                   : Double.compare(p1.getPrice(), p2.getPrice());
        };

        // Sorting the List using the Comparator
        Collections.sort(list, customComparator);

        System.out.println("List Sorted with Comparator -> Custom Ordering");
        list.forEach(System.out::println);
    }
}
Enter fullscreen mode Exit fullscreen mode

Output

List Sorted with Comparator -> Custom Ordering
Product [id=1, price=100.0, category=Essentials]
Product [id=5, price=300.0, category=Essentials]
Product [id=2, price=500.0, category=Essentials]
Product [id=3, price=200.0, category=Non-Essentials]
Product [id=4, price=400.0, category=Non-Essentials]
Enter fullscreen mode Exit fullscreen mode

Code Explanation

  1. Defining Custom Order:
    The custom comparator compares the category fields of Product objects. If two products have the same category, the comparator then compares the price fields to sort by price within each category. This approach allows us to define a sorting order that is not strictly natural but based on specific criteria.

  2. Sorting with Collections.sort:
    The Collections.sort(list, comparator) method uses the custom comparator to sort the list. This method will first order the products by their category. If products belong to the same category, they will be sorted by their price in ascending order.

  3. Tree-Based Collections:
    This custom ordering approach also applies to collections like TreeSet and TreeMap, which use a comparator to maintain a sorted order of their elements. We'll demonstrate how these collections work with custom sorting in the next post.


Comparing Comparable and Comparator

Feature Comparable Comparator
Purpose Defines natural ordering. Allows custom ordering.
Implementation Implement the Comparable interface. Use the Comparator interface.
Functional Interface No, cannot use lambdas. Yes, can use lambdas.
Flexibility Single sorting criterion. Multiple sorting criteria.
In-Class Definition Implemented within the class itself. Implemented as an external class or lambda.

Key Takeaways

  1. Use Comparable for Natural Ordering:
    Best for objects with a single, consistent sorting logic.

  2. Use Comparator for Custom Ordering:
    Ideal for flexible or multi-level sorting criteria.

  3. Choose the Right Tool:

  • Use compareTo for intrinsic ordering logic.

  • Use Comparator for scenarios requiring more flexibility or dynamic sorting.

By understanding the difference between these interfaces, you can choose the right approach to sort your objects effectively in Java!


Related Posts

Happy Coding!

Top comments (0)