Java
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented Java
-
Section 4: Collections Framework
-
Section 5: Exception Handling
-
Section 6: Generics
-
Section 7: Functional Java
-
Section 8: Concurrency
-
Section 9: I/O and NIO
-
Section 10: JVM Internals
-
Section 11: Modern Java Features
-
Section 12: Build Tools and Project Structure
-
Section 13: Testing
-
Section 14: Databases and Persistence
-
Section 15: Networking
-
Section 16: Design and Best Practices
-
Section 17: Reflection and Annotations
-
Section 18: Logging and Diagnostics
-
Section 19: Date, Time, and Internationalization
-
Section 20: Java Platform Module System
-
Section 21: Security in Java
-
Section 22: Advanced Collections and Data Structures
-
Section 23: More Concurrency Patterns
-
Section 24: Compression, Files, and System Integration
-
Section 25: GUI Programming
-
Section 26: Practical Projects
-
Section 27: Data Structures and Algorithms
-
Section 28: Interview and Algorithm Practice
-
Section 29: JSON and Data Interchange
-
Section 30: More Concurrency Utilities
-
Section 31: More Collections and Streams Practice
-
Section 32: More File and System Programming
-
Section 33: Standard Library Deep Dive
-
Section 34: More Practice and Drills
-
Section 35: More Testing and Quality
-
Section 36: More Design Patterns and Architecture
-
Section 37: Career and Ecosystem
-
Section 38: More OOP and Architecture Practice
-
Section 39: More Enterprise Concepts
-
Section 40: Advanced JavaFX
-
Section 41: More Interview Practice
48: Comparators and Comparable
I've noticed a recurring pattern when developers first hit sorting in Java: they treat Comparable and Comparator as if they are two different names for the same thing. They'll spend an hour trying to figure out why they can't "just add another Comparable" to a class so they can sort a list of objects by three different fields. If you've tried to implement multiple Comparable interfaces on a single class, you've probably realized the compiler simply won't let you. You can't inherit from the same interface twice with different generic types.
Thinking Comparable and Comparator are Interchangeable
Here is the concrete reason why that mindset fails: Comparable defines the natural ordering of an object. A Movie object might naturally be sorted by its release year. That's its "default" state. But what happens when your user clicks a column header in your UI to sort those movies by rating, or alphabetically by title? You can't change the "natural" identity of the Movie class every time a user clicks a button.
If you try to force everything into Comparable, you're essentially saying, "This object has one, and only one, way to be compared to another object of its type." In the real world, that is almost never true.
Defining the "Natural" Order with Comparable
When you implement Comparable<T>, you are modifying the class itself. You're telling Java: "I know what this object is, and I know how it should behave by default when put into a TreeSet or passed to Collections.sort()."
public class Movie implements Comparable<Movie> {
private String title;
private int releaseYear;
public Movie(String title, int releaseYear) {
this.title = title;
this.releaseYear = releaseYear;
}
@Override
public int compareTo(Movie other) {
// Natural order: Oldest movies first
return Integer.compare(this.releaseYear, other.releaseYear);
}
public String getTitle() { return title; }
public int getReleaseYear() { return releaseYear; }
}
Now, any list of Movie objects can be sorted simply by calling Collections.sort(movieList). Java looks at the class, sees it implements Comparable, and uses compareTo. Simple, right? But we're still stuck with that release year logic.
Breaking the Rules with Comparator
This is where Comparator comes in. Think of a Comparator as a separate "judge" that stands outside the class. The Movie class doesn't need to know the Comparator exists. This allows you to define as many different sorting strategies as you want without touching the original class code.
In modern Java, we rarely write full classes for this. We use lambda expressions or the Comparator.comparing static methods, which are far more readable.
import java.util.*;
public class MovieSorter {
public static void main(String[] args) {
List<Movie> movies = new ArrayList<>();
movies.add(new Movie("Inception", 2010));
movies.add(new Movie("The Godfather", 1972));
movies.add(new Movie("Interstellar", 2014));
// 1. Use Natural Order (Comparable)
Collections.sort(movies);
// 2. Use a custom Comparator to sort by title alphabetically
movies.sort(Comparator.comparing(Movie::getTitle));
// 3. Need a complex one? Write a lambda.
// Sort by title length, then by year
movies.sort((m1, m2) -> {
int lenCompare = Integer.compare(m1.getTitle().length(), m2.getTitle().length());
return lenCompare != 0 ? lenCompare : m1.compareTo(m2);
});
}
}
I personally prefer the Comparator.comparing syntax whenever possible. It's declarative. It tells the next developer what you are sorting by, rather than forcing them to parse the subtraction or comparison logic inside a method body. Just remember: Comparable is for the object's own identity; Comparator is for external requirements.
📋 Practical Task
Build a Multi-Criteria Game Library Sorter
You are building a digital game library. You have a Game class with the following fields: String title, int releaseYear, and double metacriticScore.
Your task:
- Implement
Comparable<Game>in theGameclass so that the natural order is based on thereleaseYear(ascending). - Create a
GameLibraryclass with amainmethod that populates aList<Game>with at least four different games. - Demonstrate three different sorts on that list:
- Sort by natural order.
- Sort by
metacriticScorein descending order (highest score first). - Sort by
titlealphabetically.
- Print the list to the console after each sort to verify the results.
There are no comments for now.