Comparable And Comparator in Java: Key Differences & Examples

Updated on August 28, 2024

Article Outline

Java is a popular programming language used by large enterprises and companies to build their software. Sorting is a common task for any software we are building, like sorting a list, numbers, etc. Java is an object-oriented programming language that uses the concepts of classes and objects. Java has robust ordering and sorting features that simplify data management and manipulation. Comparable and Comparator, Java’s two main interfaces, are crucial resources for creating unique sorting logic.

 

In this article, we will learn the difference between the comparable and comparator interfaces in Java, like how they differ in sorting, their methods, and how to apply these concepts in real-world examples.

What is a Comparable Interface?

Comparable Interface is a member of the Java Collections framework which defines the natural ordering of the objects of each class that implements it. It is a part of java.lang package of Java. Comparables are used to sort things by default or natural ordering, which occurs when an object knows how it should be arranged.

 

The compareTo() method, which specifies how instances of that class should be compared, must be overridden by a class that implements the Comparable interface. The Comparable interface allows you to compare one object to another of the same type. Several Java built-in classes, such as Double, String, and Integer, implement the Comparable interface.

Syntax of Comparable Interface

The syntax of a comparable interface consists of a parameter T. T refers to the type parameter and contains the type of objects that this object may be compared to.

 

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

How to Implement the Comparable Interface

A class must override the compareTo method to implement the Comparable interface. The objects’ natural order will be determined using this interface. Let’s consider an example, where we have a Car class and we want to sort the car according to their model year. To do this sorting, we will implement the Comparable<Car> and override the compareTo() method of this interface.

 

Example Code:

import java.util.*; //Creating a Car class and implementing Comparable class Car implements Comparable<Car> { private int year; private String name; private String color; public Car(int year, String name, String color) { this.year = year; this.name = name; this.color = color; } public int getYear() { return year; } public String getName() { return name; } public String getColor() { return color; } // Override the Comparable method to compare @Override public int compareTo(Car other) { return this.year - other.year; } @Override public String toString() { return "Car info- [Year: " + year + ", name='" + name + "', color='" + color + "']"; } } // main method public class Main { public static void main(String[] args) { List<Car> cars = new ArrayList<>(); cars.add(new Car(2022, "BMW", "Blue")); cars.add(new Car(2021, "Audi", "Red")); cars.add(new Car(2023, "Mercedes", "Black")); cars.add(new Car(1996, "Bugatti", "Blue")); cars.add(new Car(2003, "Ferrari", "Red")); // Sorting using Comparable Collections.sort(cars); // Printing sorted list of cars according to year for (Car car : cars) { System.out.println(car); } } }

Output:

Car info- [Year: 1996, name='Bugatti', color='Blue'] Car info- [Year: 2003, name='Ferrari', color='Red'] Car info- [Year: 2021, name='Audi', color='Red'] Car info- [Year: 2022, name='BMW', color='Blue'] Car info- [Year: 2023, name='Mercedes', color='Black']

Explanation:

 

In this example, the compareTo method compares cars according to their manufacturing year. This means that when a list of Car objects is sorted, they will be ordered by their years in ascending order, and then will be printed accordingly. By using the expression this.year – other.year, we’re effectively implementing the logic as follows:

 

  • If this.year is less than other.year, the result will be negative.
  • If this.year is equal to other.year, the result will be zero.
  • If this.year is greater than other.year, the result will be positive.

Methods of Comparable Interface

A comparable interface only has one method i.e., compareTo(T o). Let’s see the method details:

 

  • int compareTo(T o): This method compares the current (or this) object with the specified object for order. This method will define the natural order of the objects which is a convenient way to sort a list of items.

 

The compareTo method returns:

 

  1. A negative integer is returned, if the provided object is less than the current object.
  2. A zero is returned, if the supplied object and the current object are the same, then zero.
  3. A positive integer is returned, if the current object exceeds the given object.

 

Parameters:

 

  • o: The object to be compared.

 

Throws:

 

  • NullPointerException: If the specified object is null.
  • ClassCastException: If the specified object’s type prevents it from being compared to this object.
*Image
Get curriculum highlights, career paths, industry insights and accelerate your technology journey.
Download brochure

Limitations of Comparable Interface

The Comparable interface defines and helps maintain the natural order of objects which was not possible using the Arrays.sort() or Collections.sort() in Java. There are some limitations of the Comparable interface also, which need to be understood to have a better idea:

 

  1. Single Natural ordering:
    You can designate a single natural order for your class using the Comparable interface. Comparable by itself won’t be adequate if you need to sort the objects according to other criteria. For instance, you cannot use Comparable to make a Car class comparable by name if it is comparable by manufactureYear.
  2. Lack of flexibility:
    You will need to change the class or make new Comparable implementations if you need to sort objects differently, for as by different attributes or in various orders. This rigidity may result in an abundance of comparison techniques and increase the difficulty of maintaining the code. For instance, you must alter the class implementation to change the default ordering of a class (e.g., sort cars by their name rather than manufactureYear).
  3. Boilerplate Code:
    Writing and maintaining the compareTo method is necessary to implement the Comparable interface, and this might result in boilerplate code, particularly for complex comparisons.
  4. Inconsistency:
    When utilising objects in sorted data structures like TreeSet or TreeMap, the compareTo function needs to be consistent with the equals method to prevent unexpected behaviour. Maintaining this consistency can be difficult. For example, when compareTo reports that two items are equal but equals indicates otherwise, it can lead to problems in collections such as TreeSet and produce inaccurate results.

 

Also Check: Java Tutorial for Beginners

What is a Comparator Interface?

A comparison function that places an overall order on a group of things. To have exact control over the sort order, comparators can be provided to a sort method (like Collections.sort or Arrays.sort). Comparators can also be used to give an ordering for collections of items that lack a natural ordering or to regulate the order of specific data structures (like sorted sets or sorted maps).

 

Multiple sorting sequences, or sorting objects based on multiple data members, are provided by comparators. The java.util package contains the Comparator functional interface, which is used to sort objects in Java. An interface with just one abstract method is called a functional interface and this is known as a Single Abstract Method (SAM) Interface.

Syntax of Comparator Interface

The syntax of a Comparator interface consists of a parameter T. T refers to the type parameter and contains the type of objects that may be compared by this comparator.

public interface Comparator<T> { int compare(T o1, T o2); }

The compare method returns:

 

  • A negative integer if the first argument is less than the second.
  • Zero if the first argument is equal to the second.
  • A positive integer if the first argument is greater than the second.

How to Implement the Comparator Interface for Sorting

There are two ways to sort objects using the comparator interface in Java.

 

  1. Using a Separate Comparator Class

To implement the comparator interface for sorting we can use a separate comparator class which is ideal for situations when you need to apply standard, reusable sorting logic in several different locations.

 

Syntax:

// Define a class that implements Comparator for a specific type class ClassNameComparator implements Comparator<ClassName> {   // Override the compare method to define custom sorting logic @Override public int compare(ClassName obj1, ClassName obj2) { // Your comparison logic (returns -1, 0, or 1) } } public static void main(String[] args) { // Create a list of objects List<ClassName> list = // Initialize the list  // Sorting the list using the custom comparator Collections.sort(list, new ClassNameComparator()); }

Example Code 1:

import java.util.*;   //Creating a Car class class Car { private int year; private String name; private String color;   public Car(int year, String name, String color) { this.year = year; this.name = name; this.color = color; }   public int getYear() { return year; }   public String getName() { return name; }   public String getColor() { return color; }   @Override public String toString() { return "Car info- [Year: " + year + ", name='" + name + "', color='" + color + "']"; } }   // Define a Comparator for sorting cars by name class CarNameComparator implements Comparator<Car> { @Override public int compare(Car c1, Car c2) { return c1.getName().compareTo(c2.getName()); } }   // main method public class Main { public static void main(String[] args) { List<Car> cars = new ArrayList<>(); cars.add(new Car(2022, "BMW", "Blue")); cars.add(new Car(2021, "Audi", "Red")); cars.add(new Car(2023, "Mercedes", "Black")); cars.add(new Car(1996, "Bugatti", "Blue")); cars.add(new Car(2003, "Ferrari", "Red"));   // Sort cars by name using the separate comparator class Collections.sort(cars, new CarNameComparator());   // Printing sorted list of cars according to year for (Car car : cars) { System.out.println(car); } } }

Output:

Car info- [Year: 2021, name='Audi', color='Red'] Car info- [Year: 2022, name='BMW', color='Blue'] Car info- [Year: 1996, name='Bugatti', color='Blue'] Car info- [Year: 2003, name='Ferrari', color='Red'] Car info- [Year: 2023, name='Mercedes', color='Black']

Explanation:

In this example, we have used a separate comparator class to compare and sort the Car class objects according to their name. To compare two Car objects by name, the CarNameComparator class overrides the compare method and implements the Comparator<Car> interface.

 

This method neatly isolates the sorting logic from the rest of the code, which makes it very helpful when you require a reusable comparator across several application components. Because the comparison logic is contained in a single class, it is also easy to test and alter apart from the application code, which improves maintainability.

 

Example Code 2:

import java.util.*; //Creating a Car class class Car { private int year; private String name; private String color; public Car(int year, String name, String color) { this.year = year; this.name = name; this.color = color; } public int getYear() { return year; } public String getName() { return name; } public String getColor() { return color; } @Override public String toString() { return "Car info- [Year: " + year + ", name='" + name + "', color='" + color + "']"; } } // Define a Comparator for sorting cars by year class CarYearComparator implements Comparator<Car> { @Override public int compare(Car c1, Car c2) { return Integer.compare(c1.getYear(), c2.getYear()); } } // main method public class Main { public static void main(String[] args) { List<Car> cars = new ArrayList<>(); cars.add(new Car(2022, "BMW", "Blue")); cars.add(new Car(2021, "Audi", "Red")); cars.add(new Car(2023, "Mercedes", "Black")); cars.add(new Car(1996, "Bugatti", "Blue")); cars.add(new Car(2003, "Ferrari", "Red")); // Sort cars by year using the separate comparator class Collections.sort(cars, new CarYearComparator()); // Printing sorted list of cars according to year for (Car car : cars) { System.out.println(car); } } }

Output:

Car info- [Year: 1996, name='Bugatti', color='Blue'] Car info- [Year: 2003, name='Ferrari', color='Red'] Car info- [Year: 2021, name='Audi', color='Red'] Car info- [Year: 2022, name='BMW', color='Blue'] Car info- [Year: 2023, name='Mercedes', color='Black']

Explanation:

 

In this example, we have used a separate comparator class to compare and sort the Car class objects according to their year. To compare two Car objects by year, the CarYearComparator class overrides the compare method and implements the Comparator<Car> interface.

This method neatly isolates the sorting logic from the rest of the code, which makes it very helpful when you require a reusable comparator across several application components. Because the comparison logic is contained in a single class, it is also easy to test and alter apart from the application code, which improves maintainability.

 

2. Using an Inline Comparator with a Lambda Expression

 

To implement the comparator interface for sorting we can also use a lambda expression which is Ideal for context-specific, inline sorting, and particularly when you need quick & straightforward sorting logic.  

 

Syntax:

 

public static void main(String[] args) { // Create a list of objects List<ClassName> list = // Initialize the list   // Define a comparator using a lambda expression Comparator<ClassName> comparator = (ClassName obj1, ClassName obj2) -> { // Your comparison logic (returns -1, 0, or 1)   };   // Sorting the list using the inline comparator list.sort(comparator); }

Example Code 1:

 

import java.util.*;  //Creating a Car class class Car { private int year; private String name; private String color;  public Car(int year, String name, String color) { this.year = year; this.name = name; this.color = color; }  public int getYear() { return year; }  public String getName() { return name; }  public String getColor() { return color; }  @Override public String toString() { return "Car info- [Year: " + year + ", name='" + name + "', color='" + color + "']"; } }   // main method public class Main { public static void main(String[] args) { List<Car> cars = new ArrayList<>(); cars.add(new Car(2022, "BMW", "Blue")); cars.add(new Car(2021, "Audi", "Red")); cars.add(new Car(2023, "Mercedes", "Black")); cars.add(new Car(1996, "Bugatti", "Blue")); cars.add(new Car(2003, "Ferrari", "Red"));   // Sort Cars by name using an inline lambda expression Comparator<Car> nameComparator = (Car c1, Car c2) -> { return c1.getName().compareTo(c2.getName()); }; cars.sort(nameComparator);  // Printing sorted list of cars according to name for (Car car : cars) { System.out.println(car); } } }

Output:

Car info- [Year: 2021, name='Audi', color='Red'] Car info- [Year: 2022, name='BMW', color='Blue'] Car info- [Year: 1996, name='Bugatti', color='Blue'] Car info- [Year: 2003, name='Ferrari', color='Red'] Car info- [Year: 2023, name='Mercedes', color='Black']

Explanation:

 

In this example, we have used an inline comparator using a lambda expression comparator to compare and sort the Car class objects according to their year. Here, we have created the comparator directly as inline to compare the Car class objects by their year.

Methods of Comparator Interface

A comparator interface only has various methods and they are as follows:  

  1. int compare(T o1, To2): This method compares its two arguments for order. When the first parameter is less than, equal to, or higher than the second, the function returns a negative integer, zero, or a positive integer.

 

Parameters:

 

  • o1: The first object to be compared.
  • o2: The second object to be compared.

 

2. boolean equals(Object o): This method determines if an object is “equal to” this comparator or not. Only if the given object imposes the same ordering as this comparator and is also a comparator can this function return true.

 

Parameters:

 

  • o: The reference object with which to compare.

 

3. Comparator comparing(Function keyExtractor): This method creates a comparator using a given keyExtractor function as a basis.

 

Parameters:

 

  • keyExtractor: The function used to extract the sort key.

 

1. default Comparator<T> thenComparing(Comparator<? super T> other)

This method returns a comparator in lexicographic order together with another comparator. When two components are deemed equal by this Comparator, i.e., compare(a, b) == 0, the other is utilised to establish the order.

 

Parameters:

  • other: When this comparator compares two equal items, the other comparator should be applied.

 

2. naturalOrder(T):

This method provides a comparator that performs a natural order comparison of comparable objects.

 

Parameters:

  • T: The Comparable type of element to be compared.

Limitations of Comparator Interface

The Comparator interface does provide more flexibility than the comparable interface, but still, there are some limitations of it, and need to be understood to have a better idea:

1. Extra Code for Sorting

Using Comparator brings in the additional burden of writing extra code whenever you want to sort objects by different orders. For multiple sorting tasks, you need multiple comparator implementations. For instance, If we are sorting a list of students by their roll_name followed by date of birth, and marks, then for each criterion, we would need separate comparators – thus increasing the complexity of our code.

2. Overhead of Anonymous Classes (Before Java 8)

Custom comparators can introduce a performance overhead, particularly if their implementation is not trivial in terms of just one field. In the past, this could worry you a lot about performance-critical applications processing large datasets.

For example, a comparator that compares multiple fields of a Car class (e.g., sorting by name, then by year, and then by chassis number) can be less inefficient than a single comparison.

3. Complexity with Chained Comparators

Although chaining comparators like thenComparing together is very powerful, it can introduce more complexity and the possibility of mistakes, when you’re not careful working with null values or comparison logic that’s not consistent with the ordering. For example, Chaining Comparators, if one of the Comparators does not deal with null values properly then it can lead to a NullPointerException or results of sorting being wrong.

4. Difficulty in Debugging

It is more difficult to debug custom comparators, especially when using complex chaining or lambda expression than it is when implementing the Comparable interface. For example, if a comparator chain does not work as expected, it can be quite hard to identify the issue within the comparison chain.

Difference Between Comparable and Comparator Interface

The Comparable and Comparator interfaces are the two main methods available to you when sorting objects in Java. Selecting the best method for your requirements can be made easier if you are aware of the differences between these two interfaces.

When to use Comparable and Comparator?

  • Use the Comparable interface, when the object has a natural ordering that is intrinsic to the class – numbers, strings, or any other class with a single logical sorting criterion.
  • Use the Comparator interface, when you need multiple sorting options or when the sorting logic is not intrinsic to the object. For example, when sorting by different fields such as name, age, and department.

 

Key differences between the Comparable and Comparator Interfaces in Java:

 

Basis Comparable Interface Comparator Interface
Definition A Comparable interface is defined in a separate class. A Comparator interface is defined inline within a method or expression.
Length of Code The code length for using the comparable interface is longer due to the presence of a dedicated class. The code length for using the comparator interface is concise and shorter compared to the comparable interface.
Package The Comparable interface is a part of java.lang package. The Comparator interface is a part of java.util package.
Type A Comparable interface is a normal interface in Java. A Comparator interface is a functional interface in Java.
Readability The code of comparable is more readable due to the clear separation of logic. The code of the comparator interface is also readable.
Reusability It is not reusable for different sorting criteria. It is highly reusable for different sorting.
Flexibility A comparable interface can define only one sort order. A comparator interface can define only multiple sort orders.
Nature of Sorting It is used for the natural sorting of objects. It is used for the natural sorting of objects.
Method to Implement compareTo(T o) method compare(T o1, T o2) method
Modification Requirement Comparable must be implemented by the class itself. There is no need to change the class in any way.
Implementation Location It is implemented within the class. It is generally in a different class.
Compatibility Less adaptable; adjusting the sorting order would require changing the class. More adaptable because you can alter the sorting order without changing the class.
Primary Method Purpose It defines the default order of objects. It allows custom sorting based on different criteria.
Multiple Criteria Sorting Multiple criteria sorting is not possible without changing the class. It is possible by creating multiple comparators.
Use case The use cases are default sorting like String, Integer, etc. The use cases are custom sorting like sorting by different fields.
Syntax Simpler, as it’s integrated into the class. More verbose, requiring a separate comparator class or lambda expression.
Sorting Sequence It is used for the default or natural ordering of objects in a class. It is used for custom ordering of objects in a class.

Performance Considerations

The Sorting Algorithms’ Efficiency

The size of the collection and the intricacy of the sorting logic determine how effective sorting algorithms are. TimSort is a hybrid sorting algorithm used by Java that is generally efficient. It is derived from merge sort and insertion sort.

 

Complexity of Time:

 

  • Best Case: O(n log n)
  • Avagee Situation: O(n log n)
  • Worst Case: O(n log n)

Effect of Sorting Methods on Performance

  • Comparable: When using a comparable interface, sorting based on a single criterion is faster because it doesn’t require extra items or overhead.
  • Comparator: When using a comparator interface, sorting becomes a little bit slower due to its versatility, although this difference is usually insignificant unless sorting very big datasets.

Best Practices for Using Comparable and Comparator

  • Ensure Stability in Sorting

 

When multiple comparators are used or in chained comparisons, ensure that the sort order is stable. A stable sort will keep the order of those items that have equal keys.

 

  • Implement Comparable When Appropriate

 

If a class has a natural ordering, then it should implement the Comparable interface. This allows objects of the class to be used as keys or values in a sorted map or entries in a sorted set.

 

  • Use Comparator Methods for Null Safety

 

Use methods like Comparator.nullsFirst and Comparator.nullsLast to gracefully handle null values in comparisons, ensure robustness, and avoid runtime exceptions.

 

  • Prefer Static Factory Methods

 

While creating common comparators, use static factory methods provided by Comparator such as Comparator.comparing. This will enhance readability and reduce boilerplate.

 

  • Avoid Creating Comparators on Fly

 

Reuse or define comparators in advance rather than constructing them inline in multiple places. This improves code maintainability and reduces redundancy.

 

  • Use Clear Logic

 

Ensure a clear alignment of the logic of the comparator with the equals method. If two objects are equal per the comparator, they should also be equal according to the equals method.

 

  • Handle Equality Correctly

 

Comparators that have complicated sorting criteria, favour readability over complex comparisons. This helps in understanding and later modifying such code.

 

  • Prioritise Readability for Complex Comparisons

 

For complex sorting logic, if need be, use well-named methods or separate comparator classes to make it more readable. This will allow others to understand the sorting logic while easily going through it.

 

  • Watch for Performance Impacts

 

Consider the comparator in terms of performance issues, especially with large datasets. Well-implemented comparators can help achieve a lot in sort performance.

 

  • Avoid Unnecessary Comparator Chaining

 

If you are chaining comparators using thenComparing method, ensure that each one plays a significant role in the complete sorting criteria. Redundant or conflicting logic should be kept away.

Conclusion

This article covers the differences between Comparable and Comparator interfaces in Java. Comparing when to use which interfaces depends, like Comparable is best used when there is a need for natural ordering that makes sense in the majority of use cases. Comparators, on the other hand, are helpful when a class lacks a natural ordering or when several sorting orders are required since they offer flexibility in sorting by multiple criteria.

 

To get a better understanding while learning these concepts, try implementing both the Comparable and Comparator interfaces in Java programs. Gaining proficiency with these interfaces will improve your productivity as a Java developer, regardless of the task at hand – sorting basic lists, interacting with complex data structures, or managing large datasets.

FAQs
Comparable interface is used to define a natural ordering for objects of the class by implementing the compareTo method within this class itself. It is appropriate when the class has one natural way to compare. Whereas the comparator interface is used to define multiple custom orderings for objects of a class. It is implemented in a separate class or as a lambda expression, which allows external code to provide different comparison strategies easily without modifying the class.
When you want to specify a class's natural ordering for its objects and the class itself has a single, obvious method of comparison, use Comparable. This usually applies to built-in sorting techniques that depend on natural ordering, such as Collections.sort. If you require unique sorting logic or various methods for comparing objects, use Comparator - especially if the class does not implement Comparable or you are unable to change it. A comparator can be used to provide alternative sorting techniques, including attribute-based sorting.
The methods of given interfaces are:  
  • Comparable Method
  1. int compareTo(T o): Compare the current object with other instances of the same type. Returns an int according to whether this object is less than, equal to, or greater by the value of the compared object.
 
  • Comparator Methods
  1. int compare(T o1, T o2): This method compares its two arguments for order.
  2. reversed(): This method returns a comparator that reverses the order of this comparison.
When a class implements the Comparable, this means that it can order objects of its own type and has to implement the compareTo method. This impacts the class because it adds a built-in comparison mechanism inside. So, any sorting or ordering operations that use natural ordering will call the compareTo method. It also harmonises the class with sorted collections and some utilities require natural ordering.
Yes, a class can have both Comparable implementations for its Natural ordering as well which can be used as an additional or alternative sorting strategy using Comparator. This allows the class to support a default comparison, while still allowing multiple custom comparisons through unlike Comparator implementations.

Updated on August 28, 2024

Link
left dot patternright dot pattern

Programs tailored for your success

Popular

Management

Data Science

Finance

Technology

Future Tech

Upskill with expert articles

View all
Hero Vired logo
Hero Vired is a leading LearnTech company dedicated to offering cutting-edge programs in collaboration with top-tier global institutions. As part of the esteemed Hero Group, we are committed to revolutionizing the skill development landscape in India. Our programs, delivered by industry experts, are designed to empower professionals and students with the skills they need to thrive in today’s competitive job market.
Blogs
Reviews
Events
In the News
About Us
Contact us
Learning Hub
18003093939     ·     hello@herovired.com     ·    Whatsapp
Privacy policy and Terms of use

|

Sitemap

© 2024 Hero Vired. All rights reserved