ZetCode

Java Comparable Interface

Last modified: April 13, 2025

The java.lang.Comparable interface defines the natural ordering of objects in Java. It provides a single method, compareTo, which must be implemented by any class that requires sorting. Implementing this interface allows objects to be compared against one another based on a defined sorting logic.

By implementing Comparable, instances of a class gain automatic sorting capabilities, meaning they can be arranged in order using methods like Collections.sort or stored in sorted collections such as TreeSet and TreeMap. This ensures consistent behavior across different sorting mechanisms.

Comparable Interface Definition

The Comparable interface is simple, containing only one method that determines the relative order of objects:

public interface Comparable<T> {
    int compareTo(T o);

The generic type parameter T specifies the type of objects that the implementing class can compare itself against. The compareTo method compares the current object with another object of the same type and returns:

Usage in Sorting

Classes that implement Comparable must define their sorting logic inside compareTo. For example, sorting a list of custom objects like Person based on age would require overriding compareTo as follows:

class Person implements Comparable<Person> {
    private int age;

    public Person(int age) {
        this.age = age;
    }

    @Override
    public int compareTo(Person other) {
        return Integer.compare(this.age, other.age);
    }

This ensures that sorting a collection of Person objects will arrange them in ascending order based on age. The Comparable interface is widely used in scenarios requiring default sorting logic for objects, such as sorting lists, maintaining ordered data structures, and simplifying comparisons.

Basic Comparable Implementation

Let's start with a simple example of implementing Comparable for a Person class. We'll compare Person objects based on their age.

Main.java
class Person implements Comparable<Person> {

    private String name;
    private int age;
    
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    @Override
    public int compareTo(Person other) {
        return this.age - other.age;
    }
    
    @Override
    public String toString() {
        return name + " (" + age + ")";
    }
}


void main() {

    List<Person> people = Arrays.asList(
        new Person("Alice", 30),
        new Person("Bob", 25),
        new Person("Charlie", 35)
    );
    
    Collections.sort(people);
    System.out.println("Sorted by age: " + people);
}

In this example, Person implements Comparable<Person> and defines natural ordering by age. The compareTo method subtracts the other person's age from this person's age. Collections.sort uses this to sort the list.

String Comparison with Comparable

Strings in Java already implement Comparable, allowing them to be sorted alphabetically. Here's how String's natural ordering works.

Main.java
void main() {

    List<String> names = Arrays.asList(
        "Zoe", "Alice", "Bob", "Charlie", "David"
    );
    
    Collections.sort(names);
    System.out.println("Sorted names: " + names);
    
    // Demonstrating compareTo directly
    System.out.println("\"Alice\".compareTo(\"Bob\"): " + 
                        "Alice".compareTo("Bob"));
}

This example shows String's natural ordering. The list is sorted alphabetically. The direct compareTo call shows that "Alice" comes before "Bob" lexicographically, returning a negative number.

Comparing Multiple Fields

When comparing objects, we often need to consider multiple fields. Here's how to implement more complex comparison logic.

Main.java
class Student implements Comparable<Student> {

    private String name;
    private int grade;
    private double gpa;
    
    public Student(String name, int grade, double gpa) {
        this.name = name;
        this.grade = grade;
        this.gpa = gpa;
    }
    
    @Override
    public int compareTo(Student other) {

        // First compare by grade
        int gradeCompare = Integer.compare(this.grade, other.grade);
        if (gradeCompare != 0) {
            return gradeCompare;
        }
        
        // If grades are equal, compare by GPA (descending)
        int gpaCompare = Double.compare(other.gpa, this.gpa);
        if (gpaCompare != 0) {
            return gpaCompare;
        }
        
        // If GPA also equal, compare by name
        return this.name.compareTo(other.name);
    }
    
    @Override
    public String toString() {
        return name + " (Grade " + grade + ", GPA " + gpa + ")";
    }
}

void main() {

    List<Student> students = Arrays.asList(
        new Student("Alice", 10, 3.8),
        new Student("Bob", 10, 3.9),
        new Student("Charlie", 9, 4.0),
        new Student("David", 10, 3.9)
    );
    
    Collections.sort(students);
    System.out.println("Sorted students:");
    students.forEach(System.out::println);
}

This Student class compares first by grade (ascending), then by GPA (descending), and finally by name (ascending). The example shows how to implement multi-field comparisons in a clear, maintainable way.

Comparable with Custom Objects

Here's another example with a Product class that implements Comparable based on price and then name.

Main.java
class Product implements Comparable<Product> {

    private String name;
    private double price;
    
    public Product(String name, double price) {
        this.name = name;
        this.price = price;
    }
    
    @Override
    public int compareTo(Product other) {
        int priceCompare = Double.compare(this.price, other.price);
        if (priceCompare != 0) {
            return priceCompare;
        }
        return this.name.compareTo(other.name);
    }
    
    @Override
    public String toString() {
        return name + " ($" + price + ")";
    }
}

void main() {

    List<Product> products = Arrays.asList(

        new Product("Laptop", 999.99),
        new Product("Phone", 699.99),
        new Product("Tablet", 399.99),
        new Product("Headphones", 99.99),
        new Product("Mouse", 19.99),
        new Product("Keyboard", 99.99),
        new Product("Smartwatch", 399.99),
        new Product("Monitor", 699.99)
    );
    
    Collections.sort(products);
    System.out.println("Products sorted by price:");
    products.forEach(System.out::println);
}

The Product class compares first by price (ascending) and then by name (ascending). This ensures consistent ordering even when products have the same price. The example demonstrates sorting a list of products by their natural order.

Reverse Natural Ordering

Sometimes we need to sort in reverse of the natural order. Here's how to do it using Collections.reverseOrder().

Main.java
void main() {

    List<Integer> numbers = Arrays.asList(5, 2, 8, 1, 3, 9, 4);
    
    // Natural order (ascending)
    Collections.sort(numbers);
    System.out.println("Natural order: " + numbers);
    
    // Reverse natural order (descending)
    Collections.sort(numbers, Collections.reverseOrder());
    System.out.println("Reverse order: " + numbers);
    
    // Strings in reverse alphabetical order
    List<String> words = Arrays.asList("apple", "banana", "cherry", "date");
    Collections.sort(words, Collections.reverseOrder());
    System.out.println("Reverse alphabetical: " + words);
}

This example shows how to sort in reverse of the natural order using Collections.reverseOrder. It works with any Comparable type, including Integer and String. The comparator returned by reverseOrder simply inverts the result of the natural comparison.

Comparable with LocalDate Objects

Java's LocalDate class implements Comparable, enabling natural chronological ordering for date values. Here's an example demonstrating how to sort a list of dates using LocalDate.

Main.java
void main() {

    List<LocalDate> dates = Arrays.asList(
        LocalDate.parse("2023-05-15"),
        LocalDate.parse("2023-01-10"),
        LocalDate.parse("2023-03-20"),
        LocalDate.parse("2023-11-05")
    );

    System.out.println("Original dates:");
    dates.forEach(System.out::println);

    Collections.sort(dates);

    System.out.println("\nSorted dates:");
    dates.forEach(System.out::println);
}

This example demonstrates LocalDate's natural ordering, which follows chronological order. Dates are parsed from strings, stored in a list, and sorted using Collections.sort. The output displays dates in ascending order.

Comparable vs Comparator

While Comparable defines natural ordering, Comparator provides external comparison logic. Here's an example showing both approaches.

Main.java
class Employee implements Comparable<Employee> {

    private String name;
    private int id;
    private String department;
    
    public Employee(String name, int id, String department) {
        this.name = name;
        this.id = id;
        this.department = department;
    }
    
    // Natural ordering by ID
    @Override
    public int compareTo(Employee other) {
        return Integer.compare(this.id, other.id);
    }
    
    public String getName() { return name; }
    public int getId() { return id; }
    public String getDepartment() { return department; }
    
    @Override
    public String toString() {
        return name + " (ID: " + id + ", Dept: " + department + ")";
    }
}

void main() {

    List<Employee> employees = Arrays.asList(
        new Employee("Alice", 103, "HR"),
        new Employee("Bob", 101, "IT"),
        new Employee("Charlie", 102, "Finance")
    );
    
    // Sort using natural order (by ID)
    Collections.sort(employees);
    System.out.println("Sorted by ID (natural order):");
    employees.forEach(System.out::println);
    
    // Sort using Comparator (by name)
    Collections.sort(employees, Comparator.comparing(Employee::getName));
    System.out.println("\nSorted by name (using Comparator):");
    employees.forEach(System.out::println);
}

This example shows both approaches. Employee implements Comparable for natural ordering by ID. We also demonstrate sorting by name using a Comparator lambda. Comparable is for primary ordering, while Comparator provides alternative ordering options.

Source

Java Comparable Interface Documentation

This tutorial covered the Comparable interface with practical examples. Implementing Comparable enables natural ordering, making objects sortable with Collections.sort and usable in sorted collections. Remember to maintain the compareTo contract for consistent behavior.

Author

My name is Jan Bodnar, and I am a dedicated programmer with many years of experience in the field. I began writing programming articles in 2007 and have since authored over 1,400 articles and eight e-books. With more than eight years of teaching experience, I am committed to sharing my knowledge and helping others master programming concepts.

List all Java tutorials.