The Comparable interface in Java is a cornerstone for defining the natural ordering of objects, enabling effortless sorting and use in sorted collections. At compare.edu.vn, we provide in-depth analysis and comparisons to help you master this interface and elevate your Java programming skills. Discover how to implement, optimize, and troubleshoot the Comparable interface with our comprehensive guide. Enhance your understanding of object comparison and ordering.
1. What is the Comparable Interface in Java?
The Comparable interface in Java is a fundamental interface ( java.lang.Comparable
) that allows objects to be compared with each other, defining a natural ordering for instances of a class. Implementing this interface enables the use of methods like Collections.sort()
and Arrays.sort()
without needing a separate comparator. It’s essential for creating sorted collections and performing efficient searches. The Comparable interface includes a single method: compareTo()
.
1.1. Understanding the compareTo()
Method
The compareTo()
method is the heart of the Comparable interface. It compares the current object with another object of the same type and returns an integer indicating their relative order. The return values have the following meanings:
- Negative integer: The current object is less than the other object.
- Zero: The current object is equal to the other object.
- Positive integer: The current object is greater than the other object.
This method determines how objects are sorted in a collection or how they are arranged in a sorted data structure.
public interface Comparable<T> {
int compareTo(T o);
}
1.2. Why Use the Comparable Interface?
Using the Comparable interface offers several advantages:
- Natural Ordering: Defines a default way to compare objects, making it clear how they should be sorted.
- Ease of Use: Simplifies sorting operations using built-in methods like
Collections.sort()
andArrays.sort()
. - Compatibility: Enables objects to be used in sorted collections like
TreeSet
andTreeMap
without additional comparators. - Consistency: Provides a consistent approach to comparing objects throughout your application.
1.3. Implementing the Comparable Interface: A Step-by-Step Guide
To implement the Comparable interface, follow these steps:
- Implement the Interface: Declare that your class implements the
Comparable
interface, specifying the class type as the generic type parameter. - Override the
compareTo()
Method: Provide an implementation for thecompareTo()
method that compares the current object with the given object. - Define Comparison Logic: Implement the logic within the
compareTo()
method to determine the order of the objects based on relevant attributes. - Handle Null Values: Properly handle null values to avoid
NullPointerException
.
Here’s an example of implementing the Comparable interface in a Student
class:
class Student implements Comparable<Student> {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
@Override
public int compareTo(Student other) {
// Compare based on age
return Integer.compare(this.age, other.age);
}
@Override
public String toString() {
return "Student{" +
"name='" + name + ''' +
", age=" + age +
'}';
}
}
In this example, the Student
class implements the Comparable interface and overrides the compareTo()
method to compare students based on their age.
1.4. Key Considerations When Implementing Comparable
- Consistency with
equals()
: Ensure that yourcompareTo()
method is consistent with theequals()
method. If two objects are equal according toequals()
, theircompareTo()
method should return 0. - Transitivity: The comparison logic should be transitive. If
a.compareTo(b) < 0
andb.compareTo(c) < 0
, thena.compareTo(c)
must be less than 0. - Symmetry: If
a.compareTo(b) == 0
, thenb.compareTo(a)
must also be 0. - Null Handling: Handle null values appropriately to avoid
NullPointerException
.
2. Comparable Interface vs. Comparator Interface
The Comparable and Comparator interfaces in Java both serve the purpose of comparing objects, but they have distinct roles and use cases. Understanding the differences between them is crucial for effective Java programming.
2.1. Key Differences Between Comparable and Comparator
Feature | Comparable | Comparator |
---|---|---|
Interface | java.lang.Comparable |
java.util.Comparator |
Method | compareTo(T o) |
compare(T o1, T o2) |
Purpose | Defines natural ordering of the object | Defines a specific ordering of objects |
Implementation | Implemented by the class being compared | Implemented by a separate class |
Usage | Used for default sorting behavior | Used for custom sorting behavior |
Multiple Orders | Supports only one natural order | Supports multiple comparison strategies |
2.2. When to Use Comparable
Use Comparable when:
- You want to define the default way objects of a class should be compared.
- You need to enable sorting using
Collections.sort()
orArrays.sort()
without a separate comparator. - You want to use objects in sorted collections like
TreeSet
andTreeMap
without specifying a comparator.
2.3. When to Use Comparator
Use Comparator when:
- You need to define multiple comparison strategies for the same class.
- The class whose objects you want to compare cannot be modified to implement Comparable.
- You want to provide a custom sorting order that is different from the natural order defined by Comparable.
2.4. Example: Using Comparable and Comparator Together
Consider the Student
class from the previous example. Suppose you want to sort students by name in addition to sorting by age. You can use a Comparator to achieve this:
import java.util.Comparator;
class Student implements Comparable<Student> {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
@Override
public int compareTo(Student other) {
// Compare based on age (natural ordering)
return Integer.compare(this.age, other.age);
}
@Override
public String toString() {
return "Student{" +
"name='" + name + ''' +
", age=" + age +
'}';
}
public static class NameComparator implements Comparator<Student> {
@Override
public int compare(Student s1, Student s2) {
return s1.getName().compareTo(s2.getName());
}
}
}
In this example, the Student
class implements Comparable to define the natural ordering based on age. Additionally, a NameComparator
class is created to compare students based on their names.
2.5. Practical Usage: Sorting a List of Students
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<Student> students = new ArrayList<>();
students.add(new Student("Alice", 22));
students.add(new Student("Bob", 20));
students.add(new Student("Charlie", 21));
// Sort by age using Comparable (natural ordering)
Collections.sort(students);
System.out.println("Sorted by age: " + students);
// Sort by name using Comparator
Collections.sort(students, new Student.NameComparator());
System.out.println("Sorted by name: " + students);
}
}
This code demonstrates how to use both Comparable and Comparator to sort a list of Student objects. The Collections.sort()
method is used to sort the list first by age (using the natural ordering defined by Comparable) and then by name (using the custom ordering defined by the NameComparator).
2.6. Choosing Between Comparable and Comparator
Choosing between Comparable and Comparator depends on your specific requirements. If you need to define a natural ordering for a class, use Comparable. If you need to define multiple comparison strategies or compare objects of a class that you cannot modify, use Comparator. Understanding these distinctions will help you write more flexible and maintainable code.
3. Advanced Usage of Comparable in Java
The Comparable interface is not just for basic sorting. It can be used in more advanced scenarios to create complex comparison logic and integrate with other Java features. This section explores advanced usage patterns of the Comparable interface.
3.1. Implementing Complex Comparison Logic
The compareTo()
method can contain complex logic to compare objects based on multiple criteria. For example, you might want to compare objects based on one attribute first and then use another attribute to break ties.
Consider a Product
class with attributes for price
and rating
. You can implement the compareTo()
method to compare products first by price (ascending) and then by rating (descending) if the prices are the same:
class Product implements Comparable<Product> {
private String name;
private double price;
private double rating;
public Product(String name, double price, double rating) {
this.name = name;
this.price = price;
this.rating = rating;
}
public String getName() {
return name;
}
public double getPrice() {
return price;
}
public double getRating() {
return rating;
}
@Override
public int compareTo(Product other) {
// Compare by price (ascending)
int priceComparison = Double.compare(this.price, other.price);
// If prices are the same, compare by rating (descending)
if (priceComparison == 0) {
return Double.compare(other.rating, this.rating);
}
return priceComparison;
}
@Override
public String toString() {
return "Product{" +
"name='" + name + ''' +
", price=" + price +
", rating=" + rating +
'}';
}
}
In this example, the compareTo()
method first compares the prices of the products. If the prices are the same, it compares the ratings in descending order.
3.2. Using Comparable with Sorted Collections
The Comparable interface is essential for using objects in sorted collections like TreeSet
and TreeMap
. These collections maintain elements in a sorted order based on the natural ordering defined by the Comparable interface.
3.2.1. TreeSet
TreeSet
is a sorted set implementation that stores elements in a tree structure. It uses the compareTo()
method of the elements to maintain the sorted order.
import java.util.TreeSet;
public class Main {
public static void main(String[] args) {
TreeSet<Product> products = new TreeSet<>();
products.add(new Product("Laptop", 1200.00, 4.5));
products.add(new Product("Tablet", 300.00, 4.0));
products.add(new Product("Phone", 800.00, 4.8));
System.out.println("Sorted products: " + products);
}
}
In this example, the TreeSet
automatically sorts the Product
objects based on the natural ordering defined by the compareTo()
method.
3.2.2. TreeMap
TreeMap
is a sorted map implementation that stores key-value pairs in a tree structure. It uses the compareTo()
method of the keys to maintain the sorted order.
import java.util.TreeMap;
public class Main {
public static void main(String[] args) {
TreeMap<Product, Integer> productInventory = new TreeMap<>();
productInventory.put(new Product("Laptop", 1200.00, 4.5), 10);
productInventory.put(new Product("Tablet", 300.00, 4.0), 20);
productInventory.put(new Product("Phone", 800.00, 4.8), 15);
System.out.println("Sorted product inventory: " + productInventory);
}
}
In this example, the TreeMap
sorts the Product
keys based on the natural ordering defined by the compareTo()
method.
3.3. Handling Null Values in compareTo()
Handling null values in the compareTo()
method is crucial to avoid NullPointerException
. There are several ways to handle null values, depending on your specific requirements.
3.3.1. Throwing NullPointerException
The simplest approach is to throw a NullPointerException
if the other object is null. This indicates that the comparison is not possible.
@Override
public int compareTo(Product other) {
if (other == null) {
throw new NullPointerException("Cannot compare with null");
}
// ... comparison logic ...
}
3.3.2. Treating Null as the Smallest or Largest Value
Another approach is to treat null as the smallest or largest value. This can be useful in certain scenarios where you want to ensure that null values are always placed at the beginning or end of a sorted collection.
@Override
public int compareTo(Product other) {
if (other == null) {
return -1; // Treat null as the smallest value
}
// ... comparison logic ...
}
3.3.3. Using Objects.compare()
The Objects.compare()
method provides a convenient way to handle null values in the compareTo()
method. It takes two objects and a comparator as arguments and returns the result of the comparison, handling null values gracefully.
import java.util.Objects;
import java.util.Comparator;
@Override
public int compareTo(Product other) {
Comparator<Product> productComparator = Comparator.nullsFirst(Comparator.comparing(Product::getPrice).thenComparing(Product::getRating));
return Objects.compare(this, other, productComparator);
}
In this example, the Objects.compare()
method is used with a comparator that compares products by price and rating, handling null values using nullsFirst()
.
3.4. Best Practices for Implementing Comparable
- Consistency with
equals()
: Ensure that yourcompareTo()
method is consistent with theequals()
method. - Immutability: If possible, make the attributes used in the
compareTo()
method immutable to prevent unexpected behavior. - Clarity: Write clear and concise comparison logic to make it easy to understand and maintain.
- Thorough Testing: Test your
compareTo()
method thoroughly to ensure that it handles all possible scenarios correctly.
4. Common Mistakes and How to Avoid Them
Implementing the Comparable interface can sometimes lead to subtle errors that affect the behavior of sorted collections and comparison operations. Understanding these common mistakes and how to avoid them is essential for writing robust and reliable Java code.
4.1. Inconsistency with equals()
One of the most common mistakes is inconsistency between the compareTo()
method and the equals()
method. If two objects are equal according to equals()
, their compareTo()
method should return 0. Failing to maintain this consistency can lead to unexpected behavior in sorted collections like TreeSet
and TreeMap
.
Example of Inconsistency:
class Book implements Comparable<Book> {
private String title;
private String author;
private double price;
public Book(String title, String author, double price) {
this.title = title;
this.author = author;
this.price = price;
}
public String getTitle() {
return title;
}
public String getAuthor() {
return author;
}
public double getPrice() {
return price;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Book book = (Book) obj;
return Objects.equals(title, book.title) && Objects.equals(author, book.author);
}
@Override
public int hashCode() {
return Objects.hash(title, author);
}
@Override
public int compareTo(Book other) {
return Double.compare(this.price, other.price);
}
}
In this example, the equals()
method compares books based on their title and author, while the compareTo()
method compares books based on their price. This inconsistency can lead to unexpected behavior when using Book
objects in sorted collections.
How to Avoid This:
Ensure that your compareTo()
method and equals()
method are consistent. If you compare objects based on certain attributes in the equals()
method, use the same attributes in the compareTo()
method.
@Override
public int compareTo(Book other) {
int titleComparison = this.title.compareTo(other.title);
if (titleComparison != 0) {
return titleComparison;
}
return this.author.compareTo(other.author);
}
In this corrected example, the compareTo()
method compares books based on their title and author, just like the equals()
method.
4.2. Not Handling Null Values
Failing to handle null values in the compareTo()
method can lead to NullPointerException
when comparing objects. It’s essential to include null checks in your comparison logic to avoid this issue.
Example of Not Handling Null Values:
class Event implements Comparable<Event> {
private String name;
private Date startTime;
public Event(String name, Date startTime) {
this.name = name;
this.startTime = startTime;
}
public String getName() {
return name;
}
public Date getStartTime() {
return startTime;
}
@Override
public int compareTo(Event other) {
return this.startTime.compareTo(other.startTime);
}
}
If startTime
is null, this code will throw a NullPointerException
.
How to Avoid This:
Include null checks in your compareTo()
method to handle null values gracefully.
@Override
public int compareTo(Event other) {
if (this.startTime == null && other.startTime == null) {
return 0;
} else if (this.startTime == null) {
return -1; // Treat null as the smallest value
} else if (other.startTime == null) {
return 1; // Treat null as the smallest value
}
return this.startTime.compareTo(other.startTime);
}
4.3. Incorrect Comparison Logic
Incorrect comparison logic can lead to incorrect sorting and comparison results. It’s essential to carefully review your comparison logic to ensure that it is correct and meets your requirements.
Example of Incorrect Comparison Logic:
class Task implements Comparable<Task> {
private String description;
private int priority;
public Task(String description, int priority) {
this.description = description;
this.priority = priority;
}
public String getDescription() {
return description;
}
public int getPriority() {
return priority;
}
@Override
public int compareTo(Task other) {
return this.priority - other.priority; // Incorrect: can cause integer overflow
}
}
This implementation is vulnerable to integer overflow if the difference between this.priority
and other.priority
is too large.
How to Avoid This:
Use the Integer.compare()
method to compare integers safely and avoid integer overflow.
@Override
public int compareTo(Task other) {
return Integer.compare(this.priority, other.priority);
}
4.4. Not Maintaining Transitivity
The comparison logic should be transitive. If a.compareTo(b) < 0
and b.compareTo(c) < 0
, then a.compareTo(c)
must be less than 0. Failing to maintain transitivity can lead to unpredictable behavior in sorted collections.
Example of Not Maintaining Transitivity:
class Rectangle implements Comparable<Rectangle> {
private int width;
private int height;
public Rectangle(int width, int height) {
this.width = width;
this.height = height;
}
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
@Override
public int compareTo(Rectangle other) {
// Compare by area
return (this.width * this.height) - (other.width * other.height); // Can cause integer overflow
}
}
This implementation is vulnerable to integer overflow and does not maintain transitivity.
How to Avoid This:
Use appropriate comparison logic that maintains transitivity and avoids integer overflow.
@Override
public int compareTo(Rectangle other) {
long thisArea = (long) this.width * this.height;
long otherArea = (long) other.width * other.height;
return Long.compare(thisArea, otherArea);
}
4.5. Not Implementing hashCode()
Consistently with equals()
If you override the equals()
method, you must also override the hashCode()
method to ensure that objects that are equal have the same hash code. Failing to do so can lead to issues when using objects in hash-based collections like HashMap
and HashSet
.
Example of Not Implementing hashCode()
Consistently with equals()
:
class Point {
private int x;
private int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Point point = (Point) obj;
return x == point.x && y == point.y;
}
// Missing hashCode() implementation
}
How to Avoid This:
Implement the hashCode()
method consistently with the equals()
method.
@Override
public int hashCode() {
return Objects.hash(x, y);
}
By avoiding these common mistakes, you can ensure that your Comparable implementations are robust, reliable, and consistent with other parts of your Java code.
5. Real-World Examples of Comparable Interface Usage
The Comparable interface is widely used in various real-world applications to define the natural ordering of objects and enable efficient sorting and comparison operations. This section provides several real-world examples of how the Comparable interface is used in practice.
5.1. Sorting Products by Price and Rating in E-commerce Applications
In e-commerce applications, it’s common to sort products by price, rating, or other criteria to help customers find the best products. The Comparable interface can be used to define the natural ordering of products based on these attributes.
class Product implements Comparable<Product> {
private String name;
private double price;
private double rating;
public Product(String name, double price, double rating) {
this.name = name;
this.price = price;
this.rating = rating;
}
public String getName() {
return name;
}
public double getPrice() {
return price;
}
public double getRating() {
return rating;
}
@Override
public int compareTo(Product other) {
// Compare by price (ascending)
int priceComparison = Double.compare(this.price, other.price);
// If prices are the same, compare by rating (descending)
if (priceComparison == 0) {
return Double.compare(other.rating, this.rating);
}
return priceComparison;
}
@Override
public String toString() {
return "Product{" +
"name='" + name + ''' +
", price=" + price +
", rating=" + rating +
'}';
}
}
In this example, the Product
class implements the Comparable interface to compare products first by price (ascending) and then by rating (descending). This allows e-commerce applications to easily sort products based on these criteria.
5.2. Sorting Dates and Times in Calendar Applications
In calendar applications, it’s essential to sort events and appointments by date and time. The Comparable interface can be used to define the natural ordering of date and time objects.
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
class Event implements Comparable<Event> {
private String name;
private LocalDateTime startTime;
public Event(String name, LocalDateTime startTime) {
this.name = name;
this.startTime = startTime;
}
public String getName() {
return name;
}
public LocalDateTime getStartTime() {
return startTime;
}
@Override
public int compareTo(Event other) {
return this.startTime.compareTo(other.startTime);
}
@Override
public String toString() {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
return "Event{" +
"name='" + name + ''' +
", startTime='" + startTime.format(formatter) + ''' +
'}';
}
}
In this example, the Event
class implements the Comparable interface to compare events based on their start time. This allows calendar applications to easily sort events in chronological order.
5.3. Sorting Files by Name and Size in File Management Systems
In file management systems, it’s common to sort files by name, size, or other attributes. The Comparable interface can be used to define the natural ordering of file objects.
import java.io.File;
class FileInfo implements Comparable<FileInfo> {
private File file;
public FileInfo(File file) {
this.file = file;
}
public String getName() {
return file.getName();
}
public long getSize() {
return file.length();
}
@Override
public int compareTo(FileInfo other) {
// Compare by name (ascending)
return this.getName().compareTo(other.getName());
}
@Override
public String toString() {
return "FileInfo{" +
"name='" + getName() + ''' +
", size=" + getSize() +
'}';
}
}
In this example, the FileInfo
class implements the Comparable interface to compare files based on their names. This allows file management systems to easily sort files alphabetically.
5.4. Sorting Contacts by Name and Phone Number in Contact Management Applications
In contact management applications, it’s essential to sort contacts by name, phone number, or other attributes. The Comparable interface can be used to define the natural ordering of contact objects.
class Contact implements Comparable<Contact> {
private String name;
private String phoneNumber;
public Contact(String name, String phoneNumber) {
this.name = name;
this.phoneNumber = phoneNumber;
}
public String getName() {
return name;
}
public String getPhoneNumber() {
return phoneNumber;
}
@Override
public int compareTo(Contact other) {
// Compare by name (ascending)
return this.name.compareTo(other.name);
}
@Override
public String toString() {
return "Contact{" +
"name='" + name + ''' +
", phoneNumber='" + phoneNumber + ''' +
'}';
}
}
In this example, the Contact
class implements the Comparable interface to compare contacts based on their names. This allows contact management applications to easily sort contacts alphabetically.
5.5. Sorting Tasks by Priority and Due Date in Task Management Applications
In task management applications, it’s common to sort tasks by priority, due date, or other criteria. The Comparable interface can be used to define the natural ordering of task objects.
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
class Task implements Comparable<Task> {
private String description;
private int priority;
private LocalDate dueDate;
public Task(String description, int priority, LocalDate dueDate) {
this.description = description;
this.priority = priority;
this.dueDate = dueDate;
}
public String getDescription() {
return description;
}
public int getPriority() {
return priority;
}
public LocalDate getDueDate() {
return dueDate;
}
@Override
public int compareTo(Task other) {
// Compare by priority (ascending)
int priorityComparison = Integer.compare(this.priority, other.priority);
// If priorities are the same, compare by due date (ascending)
if (priorityComparison == 0) {
return this.dueDate.compareTo(other.dueDate);
}
return priorityComparison;
}
@Override
public String toString() {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
return "Task{" +
"description='" + description + ''' +
", priority=" + priority +
", dueDate='" + dueDate.format(formatter) + ''' +
'}';
}
}
In this example, the Task
class implements the Comparable interface to compare tasks first by priority (ascending) and then by due date (ascending). This allows task management applications to easily sort tasks based on these criteria.
These real-world examples demonstrate the versatility and importance of the Comparable interface in Java. By defining the natural ordering of objects, the Comparable interface enables efficient sorting and comparison operations in a wide range of applications.
6. Comparable Interface in Java 8 and Beyond
Java 8 introduced several enhancements to the Comparable interface and the way objects are compared, making it easier and more efficient to define comparison logic. This section explores the key features and improvements related to the Comparable interface in Java 8 and beyond.
6.1. Using Comparator.comparing()
Java 8 introduced the Comparator.comparing()
method, which allows you to create comparators using lambda expressions or method references. This simplifies the process of defining comparison logic and makes the code more readable.
import java.util.Comparator;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + ''' +
", age=" + age +
'}';
}
}
public class Main {
public static void main(String[] args) {
List<Person> people = new ArrayList<>();
people.add(new Person("Alice", 25));
people.add(new Person("Bob", 20));
people.add(new Person("Charlie", 30));
// Sort by name using Comparator.comparing()
Collections.sort(people, Comparator.comparing(Person::getName));
System.out.println("Sorted by name: " + people);
// Sort by age using Comparator.comparing()
Collections.sort(people, Comparator.comparing(Person::getAge));
System.out.println("Sorted by age: " + people);
}
}
In this example, the Comparator.comparing()
method is used to create comparators for sorting Person
objects by name and age.
6.2. Chaining Comparators with thenComparing()
Java 8 also introduced the thenComparing()
method, which allows you to chain multiple comparators together. This is useful when you want to compare objects based on multiple criteria.
import java.util.Comparator;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<Person> people = new ArrayList<>();
people.add(new Person("Alice", 25));
people.add(new Person("Bob", 20));
people.add(new Person("Alice", 30));
// Sort by name and then by age using thenComparing()
Collections.sort(people, Comparator.comparing(Person::getName).thenComparing(Person::getAge));
System.out.println("Sorted by name and age: " + people);
}
}
In this example, the thenComparing()
method is used to chain two comparators together, sorting Person
objects first by name and then by age.
6.3. Using Comparator.reverseOrder()
and Comparator.naturalOrder()
Java 8 provides the Comparator.reverseOrder()
and Comparator.naturalOrder()
methods, which allow you to reverse the order of a comparator or use the natural ordering of objects.
import java.util.Comparator;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<Person> people = new ArrayList<>();
people.add(new Person("Alice", 25));
people.add(new Person("Bob", 20));
people.add(new Person("Charlie", 30));
// Sort by name in reverse order using Comparator.reverseOrder()
Collections.sort(people, Comparator.comparing(Person::getName, Comparator.reverseOrder()));
System.out.println("Sorted by name in reverse order: " + people);
// Sort by age using natural order
Collections.sort(people, Comparator.comparing(Person::getAge, Comparator.naturalOrder()));
System.out.println("Sorted by age: " + people);
}