CodeToConcept
About

java

java Tutorials

Introduction
Java Variables
Java Data Types
Java Type Casting
Java Operators
Java Input/Output
Java Class & Object
java Method
Java Constructor
Java this keyword
Java super keyword
Java Method Overloading and Overriding
Java Interface
Java Abstract Class
Java Memory Management
Design Patterns
JDBC (Database Connectivity)
Java IDEs

java Tutorials

Introduction
Java Variables
Java Data Types
Java Type Casting
Java Operators
Java Input/Output
Java Class & Object
java Method
Java Constructor
Java this keyword
Java super keyword
Java Method Overloading and Overriding
Java Interface
Java Abstract Class
Java Memory Management
Design Patterns
JDBC (Database Connectivity)
Java IDEs

Sample Question

Think it through, then check the tutorial.

CodeToConcept

Quickly learn through simple, structured, and practical coding tutorials. Build your confidence from step zero to real-world experience.

© 2026 CodeToConcept. All rights reserved.

Quick Links

AboutTutorials

Tutorials

JavaHTMLCSS

Support

Privacy PolicyTerms of ServiceContact
GitHubTwitterYouTube

Java Collections Framework - Comparable & Comparator

When working with Java collections, we often need to sort objects.

For example, suppose we have a list of students:

Ashish - 25
Rahul  - 20
Priya  - 22

We may want to sort them:

  • By age
  • By name
  • By salary
  • By ID
  • By any other property

Java provides two important interfaces for defining sorting logic:

  • Comparable
  • Comparator

Both are available in the java.lang and java.util packages respectively.

import java.util.Comparator;

Comparable is part of:

java.lang.Comparable

and Comparator is part of:

java.util.Comparator

Why do we need Comparable and Comparator?

Java knows how to compare many standard data types.

For example:

List<Integer> numbers =
    Arrays.asList(30, 10, 20);
 
Collections.sort(numbers);

Output

[10, 20, 30]

Similarly, strings can be sorted:

List<String> names =
    Arrays.asList("Rahul", "Ashish", "Priya");
 
Collections.sort(names);

Output

[Ashish, Priya, Rahul]

But what happens when we create our own class?

For example:

class Student {
 
    int id;
    String name;
    int age;
}

Java does not automatically know whether students should be sorted by:

  • ID
  • Name
  • Age

This is where Comparable and Comparator are useful.

Comparable

What is Comparable?

Comparable is an interface used to define the natural ordering of objects of a class.

It is available in:

java.lang

The interface contains the method:

int compareTo(T o)

A class implements Comparable when it wants to define its own default or natural sorting order.

Basic Syntax

class Student implements Comparable<Student> {
 
    @Override
    public int compareTo(Student other) {
        return 0; // Placeholder: replace with your comparison rule.
    }
}

The important method is:

compareTo()

How compareTo() Works

The compareTo() method returns an integer.

The result has three possible meanings:

ResultMeaning
Negative valueCurrent object comes before the other object
0Objects are considered equal for ordering
Positive valueCurrent object comes after the other object

You don't need to return exactly -1, 0, or 1. Any negative, zero, or positive integer has the corresponding meaning.

For example:

return this.age - other.age;

conceptually means:

Negative -> this.age is smaller
Zero     -> ages are equal
Positive -> this.age is greater

However, for integer comparisons, methods such as Integer.compare() are generally safer because subtraction can overflow for extreme integer values.

Example of Comparable

Suppose we have a Student class:

class Student implements Comparable<Student> {
 
    private int age;
    private String name;
 
    public Student(int age, String name) {
        this.age = age;
        this.name = name;
    }
 
    public int getAge() {
        return age;
    }
 
    public String getName() {
        return name;
    }
 
    @Override
    public int compareTo(Student other) {
        return Integer.compare(this.age, other.age);
    }
}

Here, we have defined the natural ordering of Student based on age.

Now create students:

List<Student> students = new ArrayList<>();
 
students.add(new Student(25, "Ashish"));
students.add(new Student(20, "Rahul"));
students.add(new Student(22, "Priya"));

We can sort them:

Collections.sort(students);

The students will be sorted by age:

Rahul  - 20
Priya  - 22
Ashish - 25

Comparable and Collections.sort()

When a class implements Comparable, we can use:

Collections.sort(list);

Example

Collections.sort(students);

The sorting algorithm uses the class's compareTo() method to determine the order.

Comparable with List.sort()

Modern Java also provides:

students.sort(null);

Passing null tells the list to use the elements' natural ordering.

For a Student class implementing Comparable<Student>:

students.sort(null);

will use:

compareTo()

Comparable Example - Sort Employees by Salary

Suppose we want the natural ordering of an Employee to be based on salary.

class Employee implements Comparable<Employee> {
 
    private int id;
    private String name;
    private double salary;
    private String department = "General";
 
    public int getId() { return id; }
    public String getName() { return name; }
    public double getSalary() { return salary; }
    public String getDepartment() { return department; }
 
    public Employee(int id, String name, double salary) {
        this.id = id;
        this.name = name;
        this.salary = salary;
    }
 
    @Override
    public int compareTo(Employee other) {
        return Double.compare(this.salary, other.salary);
    }
 
    @Override
    public String toString() {
        return id + " " + name + " " + salary;
    }
}

Now:

Collections.sort(employees);

will sort employees according to salary.

Comparator

What is Comparator?

Comparator is an interface used to define a custom ordering for objects.

It is available in:

java.util

The main method is:

int compare(T o1, T o2)

Unlike Comparable, the comparison logic is normally defined outside the class being sorted.

Why do we need Comparator?

Suppose a Student class has:

  • id
  • name
  • age

Maybe we want to sort students by age today.

Tomorrow, we may want to sort them by name.

Another time, we may want to sort them by ID.

If we put every possible sorting rule inside compareTo(), the class becomes difficult to manage.

Comparator allows us to define different sorting strategies separately.

Basic Comparator Syntax

Comparator<Student> comparator =
    (s1, s2) -> Integer.compare(s1.getAge(), s2.getAge());

Then:

students.sort(comparator);

Comparator Example

Consider:

class Student {
 
    private int id;
    private String name;
    private int age;
 
    public Student(int id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }
 
    public int getId() {
        return id;
    }
 
    public String getName() {
        return name;
    }
 
    public int getAge() {
        return age;
    }
}

We can sort by age:

students.sort(
    (s1, s2) ->
        Integer.compare(s1.getAge(), s2.getAge())
);

Sorting by Name

We can define another Comparator:

students.sort(
    Comparator.comparing(Student::getName)
);

This sorts students alphabetically by name.

Sorting by ID

students.sort(
    Comparator.comparingInt(Student::getId)
);

This sorts students according to their ID.

Sorting in Descending Order

By default, many comparison methods produce ascending order.

We can reverse the Comparator:

students.sort(
    Comparator.comparingInt(Student::getAge)
              .reversed()
);

This sorts students from highest age to lowest age.

Comparator.comparing()

Java provides convenient methods for creating Comparators.

For example:

Comparator<Student> byName =
    Comparator.comparing(Student::getName);

Then:

students.sort(byName);

Comparator.comparingInt()

For integer properties:

Comparator<Student> byAge =
    Comparator.comparingInt(Student::getAge);

Then:

students.sort(byAge);

Comparator.comparingDouble()

For double properties:

Comparator<Employee> bySalary =
    Comparator.comparingDouble(Employee::getSalary);

Sorting by Multiple Fields

One of the most useful features of Comparator is the ability to combine multiple sorting conditions.

Suppose we want:

  • Sort by department.
  • If department is the same, sort by salary.

We can write:

employees.sort(
    Comparator.comparing(Employee::getDepartment)
              .thenComparingDouble(Employee::getSalary)
);

The first comparison is performed using department.

If two employees have the same department, salary is used as the next comparison.

thenComparing()

The thenComparing() method allows us to add another sorting condition.

Example

students.sort(
    Comparator.comparing(Student::getName)
              .thenComparingInt(Student::getAge)
);

This means:

First sort by name
        ↓
If names are equal
        ↓
Sort by age

Comparator Using Lambda Expression

A Comparator can be written using a lambda expression.

students.sort(
    (s1, s2) ->
        Integer.compare(s1.getAge(), s2.getAge())
);

Here:

s1 -> first student
s2 -> second student

The comparison result determines their ordering.

Comparator Using Anonymous Class

Before lambda expressions became available, Comparator was commonly written using an anonymous class.

Comparator<Student> comparator =
    new Comparator<Student>() {
 
        @Override
        public int compare(
                Student s1,
                Student s2) {
 
            return Integer.compare(
                s1.getAge(),
                s2.getAge()
            );
        }
    };

Then:

students.sort(comparator);

This is still valid Java, although lambda expressions are usually more concise when applicable.

Comparable vs Comparator

The most important difference is where the comparison logic is defined.

ComparableComparator
Defines natural orderingDefines custom ordering
Implemented by the class being comparedUsually defined separately from the class
Uses compareTo()Uses compare()
Located in java.langLocated in java.util
Usually provides one natural orderingCan provide multiple different orderings
Modifies the class definitionDoes not require modifying the class

Comparable Example

class Student implements Comparable<Student> {
 
    private int age;
 
    @Override
    public int compareTo(Student other) {
        return Integer.compare(this.age, other.age);
    }
}

The class itself defines its natural ordering.

Comparator Example

Comparator<Student> byAge =
    Comparator.comparingInt(Student::getAge);

The sorting rule is defined separately.

When Should We Use Comparable?

Use Comparable when:

  • A class has a clear natural ordering.
  • You want the class itself to define that ordering.
  • There is one primary/default way objects should normally be sorted.

For example, a Product class might naturally be ordered by product ID.

When Should We Use Comparator?

Use Comparator when:

  • Multiple sorting orders are required.
  • You cannot or do not want to modify the class.
  • Sorting rules depend on the particular operation.
  • You need flexible and reusable sorting strategies.

For example, employees can be sorted by:

  • Salary
  • Name
  • Department
  • Experience
  • ID

Each can be represented by a different Comparator.

Comparable with TreeSet

Comparable is also important when working with sorted collections such as TreeSet.

For example:

class Student implements Comparable<Student> {
 
    private int age;
 
    @Override
    public int compareTo(Student other) {
        return Integer.compare(this.age, other.age);
    }
}

Now a TreeSet<Student> can use the natural ordering:

TreeSet<Student> students =
    new TreeSet<>();
 
students.add(student1);
students.add(student2);
students.add(student3);

The elements are maintained according to the ordering defined by compareTo().

Comparator with TreeSet

A TreeSet can also receive a Comparator.

TreeSet<Student> students =
    new TreeSet<>(
        Comparator.comparingInt(Student::getAge)
    );

Now the TreeSet uses the supplied Comparator to determine ordering.

Comparator with TreeMap

Comparator is also useful with TreeMap.

For example:

TreeMap<String, Integer> map =
    new TreeMap<>(Comparator.reverseOrder());

The keys are maintained in reverse order.

Comparing Strings

Strings already implement Comparable.

For example:

String a = "Apple";
String b = "Banana";
 
System.out.println(a.compareTo(b));

The result is negative because "Apple" comes before "Banana" according to their natural ordering.

For case-insensitive comparison:

Comparator<String> comparator =
    String.CASE_INSENSITIVE_ORDER;

Comparable Contract

When implementing Comparable, the comparison should follow a consistent contract.

The important relationship is:

compareTo() < 0

means the current object comes before the other object.

compareTo() == 0

means the objects are equivalent for ordering.

compareTo() > 0

means the current object comes after the other object.

A comparison should also generally be:

Reflexive in ordering

An object compared with itself should return:

object.compareTo(object) == 0

Antisymmetric in sign

If:

a.compareTo(b)

is positive, then:

b.compareTo(a)

should be negative, and vice versa.

Transitive

If:

A < B
B < C

then the ordering should also indicate:

A < C

Following these rules helps prevent unpredictable behavior in sorted collections and sorting operations.

Comparator Contract

A Comparator should follow similar consistency rules.

For:

compare(a, b)

the result means:

Negative -> a comes before b
Zero     -> a and b are equivalent for this ordering
Positive -> a comes after b

The comparison should be consistent and transitive.

Important: compareTo() == 0 and equals()

One important concept is that:

compareTo() == 0

does not automatically mean:

equals() == true

The two concepts are related but not identical.

For example, a Comparator may compare two employees only by salary:

Comparator<Employee> bySalary =
    Comparator.comparingDouble(Employee::getSalary);

Two different employees with the same salary will compare as:

0

even though they may not be equal according to equals().

This becomes especially important with sorted collections such as TreeSet and TreeMap.

Why Comparator is More Flexible

Suppose we have:

class Employee {
 
    int id;
    String name;
    double salary;
 
    public int getId() { return id; }
    public String getName() { return name; }
    public double getSalary() { return salary; }
}

We can create multiple sorting rules:

Sort by ID

Comparator<Employee> byId =
    Comparator.comparingInt(Employee::getId);

Sort by Name

Comparator<Employee> byName =
    Comparator.comparing(Employee::getName);

Sort by Salary

Comparator<Employee> bySalary =
    Comparator.comparingDouble(Employee::getSalary);

Now we can choose the required sorting strategy:

employees.sort(byId);

or:

employees.sort(byName);

or:

employees.sort(bySalary);

The Employee class does not need to change.

Complete Comparable Example

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
 
class Student implements Comparable<Student> {
 
    private int id;
    private String name;
    private int age;
 
    public Student(int id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }
 
    public int getAge() {
        return age;
    }
 
    public String getName() {
        return name;
    }
 
    @Override
    public int compareTo(Student other) {
        return Integer.compare(this.age, other.age);
    }
 
    @Override
    public String toString() {
        return name + " - " + age;
    }
}
 
public class HelloWorld {
 
    public static void main(String[] args) {
 
        List<Student> students = new ArrayList<>();
 
        students.add(
            new Student(101, "Ashish", 25)
        );
 
        students.add(
            new Student(102, "Rahul", 20)
        );
 
        students.add(
            new Student(103, "Priya", 22)
        );
 
        Collections.sort(students);
 
        System.out.println(students);
    }
}

Output

[Rahul - 20, Priya - 22, Ashish - 25]

The ordering is defined by:

compareTo()

Complete Comparator Example

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
 
class Student {
 
    private int id;
    private String name;
    private int age;
 
    public Student(int id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }
 
    public int getId() {
        return id;
    }
 
    public String getName() {
        return name;
    }
 
    public int getAge() {
        return age;
    }
 
    @Override
    public String toString() {
        return name + " - " + age;
    }
}
 
public class HelloWorld {
 
    public static void main(String[] args) {
 
        List<Student> students = new ArrayList<>();
 
        students.add(
            new Student(101, "Ashish", 25)
        );
 
        students.add(
            new Student(102, "Rahul", 20)
        );
 
        students.add(
            new Student(103, "Priya", 22)
        );
 
        students.sort(
            Comparator.comparingInt(Student::getAge)
        );
 
        System.out.println(students);
    }
}

Output

[Rahul - 20, Priya - 22, Ashish - 25]

Here, the Student class does not implement Comparable.

The sorting rule is supplied separately using Comparator.

Simple Real-World Example

Imagine an employee management system.

An employee has:

  • ID
  • Name
  • Salary
  • Experience

Different screens may need different sorting:

Employee List
    ↓
Sort by Name
 
Payroll
    ↓
Sort by Salary
 
Employee Search
    ↓
Sort by ID
 
Experience Report
    ↓
Sort by Experience

Using Comparator, each sorting rule can be created independently.

This makes the code more flexible and easier to maintain.

Quick Comparison

FeatureComparableComparator
Packagejava.langjava.util
Main methodcompareTo()compare()
Sorting typeNatural orderingCustom ordering
DefinedInside the classUsually outside the class
Number of orderingsUsually one natural orderingMultiple possible orderings
Modify original class?YesNo
Lambda-friendlyNot directly as a sorting strategyYes
Useful with TreeSetYesYes
Useful with TreeMapYesYes

Key Takeaway

Comparable and Comparator are used to define how objects should be ordered.

Comparable

Use Comparable when a class has a clear natural ordering.

class Student implements Comparable<Student> {
 
    private int age;
 
    @Override
    public int compareTo(Student other) {
        return Integer.compare(this.age, other.age);
    }
}

Comparator

Use Comparator when you need custom or multiple sorting rules.

students.sort(
    Comparator.comparingInt(Student::getAge)
);

A simple way to remember the difference is: Comparable ↓ "I know my natural order."

Comparator ↓ "Tell me how you want to order these objects."

Understanding these two interfaces is essential when working with Collections.sort(), List.sort(), TreeSet, TreeMap, and custom Java objects.

See you in next page....🙂

← IteratorOverview →
← IteratorOverview →