==
operator works, as well as the equals()
and hashCode()
methods.
Comparison is a little different. Previously, we most likely meant "checking objects for equality".
ArrayList<>
of numbers or strings, you would be able to handle this without any problems:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Main {
public static void main(String[] args) {
String name1 = "Masha";
String name2 = "Sasha";
String name3 = "Dasha";
List<String> names = new ArrayList<>();
names.add(name1);
names.add(name2);
names.add(name3);
Collections.sort(names);
System.out.println(names);
}
}
Console output:
[Dasha, Masha, Sasha]
If you remembered the Collections
class and its sort()
method, well done! I think you'll also have no trouble with numbers.
Here's a more challenging task for you:
public class Car {
private int manufactureYear;
private String model;
private int maxSpeed;
public Car(int manufactureYear, String model, int maxSpeed) {
this.manufactureYear = manufactureYear;
this.model = model;
this.maxSpeed = maxSpeed;
}
// ...getters, setters, toString()
}
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<Car> cars = new ArrayList<>();
Car ferrari = new Car(1990, "Ferrari 360 Spider", 310);
Car lambo = new Car(2012, "Lamborghini Gallardo", 290);
Car bugatti = new Car(2010, "Bugatti Veyron", 350);
cars.add(ferrari);
cars.add(bugatti);
cars.add(lambo);
}
}
The task is actually simple. We have a Car
class and 3 Car objects.
Would you kindly sort the cars in the list?
You will probably ask, "How should they be sorted?" By name? By year of manufacture? By maximum speed?
Excellent question. At the moment, we don't know how to sort the Car
objects.
And, quite naturally, Java doesn't know that either! When we try to pass a list of Car
objects to the Collections.sort()
method, we get an error:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<Car> cars = new ArrayList<>();
Car ferrari = new Car(1990, "Ferrari 360 Spider", 310);
Car lambo = new Car(20012, "Lamborghini Gallardo", 290);
Car bugatti = new Car(2010, "Bugatti Veyron", 350);
cars.add(ferrari);
cars.add(bugatti);
cars.add(lambo);
// Compilation error!
Collections.sort(cars);
}
}
And indeed, how would the language know how to sort objects of classes you've written?
This depends on what your program needs to do.
We must somehow teach Java to compare these objects. And to compare them just how we want.
Java does have a special mechanism for this: the Comparable
interface.
Java Comparable interface
In order to somehow compare and sort ourCar
objects, the class must implement this interface, which consists of a single method: compareTo()
:
public class Car implements Comparable<Car> {
private int manufactureYear;
private String model;
private int maxSpeed;
public Car(int manufactureYear, String model, int maxSpeed) {
this.manufactureYear = manufactureYear;
this.model = model;
this.maxSpeed = maxSpeed;
}
@Override
public int compareTo(Car o) {
return 0;
}
// ...getters, setters, toString()
}
Please note that we specified the Comparable<Car>
interface, not just Comparable
. This is a parameterized interface, that is, we must specify the specific associated class.
In principle, you can remove <Car>
from the interface, but then the comparing will be based on Object
objects by default. Instead of the compareTo(Car o)
method, our class will have:
@Override
public int compareTo(Object o) {
return 0;
}
Of course, it's much easier for us to work with Car
.
Inside the compareTo()
method, we implement our logic for comparing cars. Suppose we need to sort them by year of manufacture.
You probably noticed that the compareTo()
method returns an int
, not a boolean
.
Don't let this surprise you. When we compare two objects, there are 3 possibilities:
а < b
a > b
a == b
.
boolean
has only 2 values: true and false, which doesn't work well for comparing objects.
With int
, everything is much simpler. If the return value is > 0
, then a > b
.
If the result of compareTo
is < 0
, then a < b
.
And, if the result is == 0
, then two objects are equal: a == b
.
Teaching our class to sort cars by year of manufacture is easy peasy:
@Override
public int compareTo(Car o) {
return this.getManufactureYear() - o.getManufactureYear();
}
But what's going on here?
We take one Car object (this
), get this car's year of manufacture, and subtract from it the year of manufacture of another car (the one with which the object is being compared).
If the first car's year of manufacture is greater, the method will return an int > 0
.
This means that the this car >
the o
car.
Conversely, if the year of manufacture of the second car (о
) is greater, then the method will return a negative number, which means that o > this
.
Finally, if they are equal, then the method will return 0
. This simple mechanism is already enough for us to sort collections of Car
objects! You don't have to do anything else.
Check it out:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<Car> cars = new ArrayList<>();
Car ferrari = new Car(1990, "Ferrari 360 Spider", 310);
Car lambo = new Car(2012, "Lamborghini Gallardo", 290);
Car bugatti = new Car(2010, "Bugatti Veyron", 350);
cars.add(ferrari);
cars.add(bugatti);
cars.add(lambo);
// There was previously an error here
Collections.sort(cars);
System.out.println(cars);
}
}
Console output:
[Car{manufactureYear=1990, model='Ferrari 360 Spider', maxSpeed=310},
Car{manufactureYear=2010, model='Bugatti Veyron', maxSpeed=350},
Car{manufactureYear=2012, model='Lamborghini Gallardo', maxSpeed=290}]
The cars are sorted like we want! :)

When should I use Comparable?
The comparison method implemented inComparable
is called natural ordering. This is because in the compareTo()
method you define the most common, or natural, way of comparing objects of this class.
Java already has a natural ordering. For example, Java knows that strings are most often sorted alphabetically, and numbers by increasing numeric value. Therefore, if you call the sort()
method on a list of numbers or strings, they will be sorted.
If our program will usually compare and sort cars by year of manufacture, then we should define the natural sorting for Cars using the Comparable<Car>
interface and the compareTo()
method.
But what if this is not enough for us?
Let's imagine that our program is not so simple.
In most cases, the natural sorting of cars (which we have set to be performed by year of manufacture) suits us.
But sometimes our customers are aficionados of fast driving. If we are preparing a car catalog for them to peruse, the cars should be sorted by maximum speed.

Car
class's natural sorting to be by speed instead of by year of manufacture.
But we can't ignore 15% of our customers. So what do we do?
Another interface comes to our aid here: Comparator
. Just like Comparable
, it is a parameterized interface.
What's the difference?
Comparable
makes our objects "comparable" and defines their most natural sort order, i.e. the sort order that will be used in most cases.
Comparator
is a separate "comparing" interface.
If we need to implement some kind of special sorting order, we don't need to go into the Car
class and change the logic of compareTo()
.
Instead, we can create a separate class that implements Comparator and teach it how to perform the sorting we need!
import java.util.Comparator;
public class MaxSpeedCarComparator implements Comparator<Car> {
@Override
public int compare(Car o1, Car o2) {
return o1.getMaxSpeed() - o2.getMaxSpeed();
}
}
As you can see, our Comparator
is pretty simple.
We need to implement only one interface method: compare()
. It takes two Car
objects as inputs and compares their maximum speeds in the usual way (by subtraction). Like compareTo()
, it returns an int
, and the principle of comparison is the same.
How do we use this? It's all straightforward:
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<Car> cars = new ArrayList<>();
Car ferrari = new Car(1990, "Ferrari 360 Spider", 310);
Car lambo = new Car(2012, "Lamborghini Gallardo", 290);
Car bugatti = new Car(2010, "Bugatti Veyron", 350);
cars.add(ferrari);
cars.add(bugatti);
cars.add(lambo);
Comparator speedComparator = new MaxSpeedCarComparator();
Collections.sort(cars, speedComparator);
System.out.println(cars);
}
}
Console output:
[Car{manufactureYear=2012, model='Lamborghini Gallardo', maxSpeed=290},
Car{manufactureYear=1990, model='Ferrari 360 Spider', maxSpeed=310},
Car{manufactureYear=2010, model='Bugatti Veyron', maxSpeed=350}]
We simply create a comparator object and pass it to the Collections.sort()
method along with the list to be sorted.
When the sort()
method receives a comparator, it does not use the natural sorting defined in the Car
class's compareTo()
method. Instead, it applies the sorting algorithm defined by the comparator passed to it.
What are the advantages of doing this?
First, compatibility with existing code. We created a new, special sorting method, while retaining the existing one that will be used most of the time.
We didn't touch the Car
class at all. It was a Comparable
, and so it remains:
public class Car implements Comparable<Car> {
private int manufactureYear;
private String model;
private int maxSpeed;
public Car(int manufactureYear, String model, int maxSpeed) {
this.manufactureYear = manufactureYear;
this.model = model;
this.maxSpeed = maxSpeed;
}
@Override
public int compareTo(Car o) {
return this.getManufactureYear() - o.getManufactureYear();
}
// ...getters, setters, toString()
}
Second, flexibility. We can add as many sorting algorithms as we like.
For example, we can sort cars by color, speed, weight, or by how many times a car has been used in Batman movies. All we need to do is create an additional Comparator
.Mastering Advanced Sorting with Java's Comparator Interface
By now, you're probably comfortable with the basics of the Comparator
interface in Java. But wait—did you know that Comparator is way more powerful than just sorting numbers in ascending order or strings alphabetically? Oh yes! It can handle special sorting rules, dynamic sorting, complex objects, and even sleek lambda expressions. Let’s dive deeper and unleash the full power of Comparator
!
1. Special Sorting Rules for Strings and Numbers
Sometimes, default sorting just doesn’t cut it. Maybe you want to sort numbers in descending order or arrange strings by length. Sounds tricky? Not at all!
Sorting Numbers in Descending Order
import java.util.*;
public class DescendingSortExample {
public static void main(String[] args) {
List numbers = Arrays.asList(5, 2, 9, 1, 7);
// Sorting numbers in descending order
numbers.sort(Comparator.reverseOrder());
System.out.println("Descending order: " + numbers); // Output: [9, 7, 5, 2, 1]
}
}
Sorting Strings by Length
import java.util.*;
public class StringLengthSortExample {
public static void main(String[] args) {
List words = Arrays.asList("apple", "kiwi", "banana", "pear");
// Sorting strings by their length
words.sort(Comparator.comparingInt(String::length));
System.out.println("Sorted by length: " + words); // Output: [kiwi, pear, apple, banana]
}
}
Pretty cool, right? With just a few lines of code, you can sort things exactly how you want!
2. Dynamic Sorting with Comparator
Imagine you're building an app where users can choose how they want to sort their data—by name, by date, by size. How can you handle this? Simple! Comparator
lets you change sorting rules on the fly.
Example: Switching Sorting Rules
import java.util.*;
public class DynamicSortingExample {
public static void main(String[] args) {
List items = Arrays.asList("apple", "kiwi", "banana", "pear");
Scanner scanner = new Scanner(System.in);
System.out.println("Sort by (1) Alphabet or (2) Length:");
int choice = scanner.nextInt();
if (choice == 1) {
items.sort(String::compareTo); // Alphabetical order
} else if (choice == 2) {
items.sort(Comparator.comparingInt(String::length)); // By length
}
System.out.println("Sorted list: " + items);
}
}
Now your program can adapt and sort based on user preferences. That’s some next-level flexibility!
3. Custom Sorting Rules for Complex Objects
Sorting basic data types is one thing, but what about custom objects? Let’s say you have a list of books, and you want to sort them by title, author, or price. No problem!
Example: Sorting a List of Books
import java.util.*;
class Book {
String title;
String author;
double price;
Book(String title, String author, double price) {
this.title = title;
this.author = author;
this.price = price;
}
@Override
public String toString() {
return title + " by " + author + " - $" + price;
}
}
public class BookSortingExample {
public static void main(String[] args) {
List books = Arrays.asList(
new Book("Java Basics", "Alice", 29.99),
new Book("Advanced Java", "Bob", 49.99),
new Book("Spring Boot", "Charlie", 39.99)
);
// Sort by price
books.sort(Comparator.comparingDouble(book -> book.price));
System.out.println("Sorted by price:");
books.forEach(System.out::println);
}
}
Output:
- Java Basics by Alice - $29.99
- Spring Boot by Charlie - $39.99
- Advanced Java by Bob - $49.99
See how easy it is to sort by any field you want? You’re basically in charge of how the data behaves!
4. Simplify with Lambda Expressions
Let’s make things even simpler. Why not ditch the boilerplate code and use lambda expressions for sorting? It’s cleaner, shorter, and easier to read.
Example: Sorting with Lambdas
import java.util.*;
public class LambdaSortingExample {
public static void main(String[] args) {
List items = Arrays.asList("apple", "kiwi", "banana", "pear");
// Sort alphabetically using lambda
items.sort((a, b) -> a.compareTo(b));
System.out.println("Alphabetical: " + items);
// Sort by length using lambda
items.sort((a, b) -> Integer.compare(a.length(), b.length()));
System.out.println("By length: " + items);
}
}
Short, sweet, and powerful. This is why lambda expressions are a game-changer!
That's it! Today you have studied two very important mechanisms that you will often use in real projects at work. But, as you know, theory without practice is nothing. Now it's time to consolidate your knowledge and complete some tasks!
GO TO FULL VERSION